默认情况下的触摸事件分发
当用户手指触摸UIScrollView及其子类时,其属性 isTracking 设置为YES,同时内部会开启一个NSTimer, 在timer的一个极短的时间周期内,如果手指发生了较大距离的移动,UIScrollView接收这个事件开始滚动到相应的位置, isTracking 设置为NO。 如果手指没有发生较大距离的移动,而touch位置正好位于子视图上,并且其子视图接受touch事件,那么就触发子视图的touch事件。
Demo中创建一个tableView,为每一个cell 添加一个button
class ViewController: UIViewController {
@IBOutlet weak var tableView: YMTableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController:UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "test");
if (cell == nil) {
cell = UITableViewCell(style: .default , reuseIdentifier: "test")
let button = UIButton(type:.custom)
button.frame = CGRect(x: 40, y:0, width:100, height:44)
button.backgroundColor = UIColor.red
cell?.contentView .addSubview(button)
button .addTarget(self, action: #selector(self.click), for: .touchUpInside)
}
cell?.textLabel?.text = "\(indexPath.row)"
return cell!
}
func click() {
print("被点击了");
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
}
}
extension ViewController:UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("start to scrolling")
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print("Did end Decelerating")
}
}
运行代码,正如上面所描述的那样,当手指滑动时,UIScrollView开始滑动,如果点击在button上并且没有移动,那么就会触发button的selector,如果点击在其部分则会调用UITableView的 didSelectRowAtIndex。
delaysContentTouches
var delaysContentTouches: Bool { get set }
If the value of this property is true , the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false, the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true。
如果这个值为YES,scrollView会延迟处理手势事件直到它能决定是否要滚动。如果为No,那么它会直接调用 touchesShouldBegin(_:with:in:)来处理事件。
说白了这个值就是决定什么时候去调用touchesShouldBegin(:with:in:),如果值为YES,那么UIScrollView先去判断touch事件,如果手指有较大范围的移动,就让UIScrollView去滚动,如果没有就调用touchesShouldBegin(:with:in:)去处理, 而如果值为No,UIScrollView不会去判断touch事件,会直接调用touchesShouldBegin(_:with:in:),如果有滚动UIScrollView同时滚动。
新创建一个tableView的子类,YMTableView:
class YMTableView: UITableView {
override func awakeFromNib() {
self.delaysContentTouches = true // 默认值,为了强调
}
override func touchesShouldBegin(_ touches: Set<UITouch>,
with event: UIEvent?,
in view: UIView) -> Bool{
super.touchesShouldBegin(touches, with: event, in: view)
print("call touchShould Begin")
return true
}
}
当值为YES时,触摸在按钮上并且滑动tableView 输出如下:
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
点击按钮时输出:
call touchShould Begin
被点击了
如果值设置为NO, 触摸在按钮上并且滑动,输出如下:
call touchShould Begin
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
start to scrolling
立即调用了touchesShouldBegin,并没有去延迟判断
点击按钮时输出:
call touchShould Begin
被点击了
touchesShouldBegin的返回值
func touchesShouldBegin(_ touches: Set<UITouch>,
with event: UIEvent?,
in view: UIView) -> Bool
Return false if you don’t want the scroll view to send event messages to view. If you want view to receive those messages, return true (the default).
如果返回YES(默认),UIScrollView会把当前事件分发到子view去处理,如果NO就不分发此事件。
如果把上面的返回值改为NO,那么Button的点击和didSelect都不会被触发.
canCancelContentTouches
A Boolean value that controls whether touches in the content view always lead to tracking。
一个BOOL值来控制发生在 content view上的触摸事件能否总是触发追踪(tracking)。如果值为YES,那么istracking 会被设置成YES,NSTimer检测是否有手指移动,如果有就滚动,并且结束 content view 的touch 事件。
新建一个YMButton类,把上面的UIButton改为YMButton
import UIKit
class YMButton: UIButton {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("YMButton touch begin");
super.touchesBegan(touches, with: event);
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
print("YMButton touch touchesCancelled cancelled");
}
}
YMTable 中添加如下代码:
override func touchesShouldCancel(in view: UIView) -> Bool {
super.touchesShouldCancel(in: view)
print("YMTable touch should cancelled");
return true
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
print("YMTable touch touchesCancelled cancelled");
}
点击在button上然后移动手指,输出如下
YMTable touchShould Begin
YMButton touch begin
YMTable touch should cancelled
YMButton touch touchesCancelled cancelled
正如上面的输出,虽然触发了button的触摸事件,但是推算出其是滚动,然后把touch时间取消掉,执行滚动操作。
如果canCancelContentTouches = false,执行同样的操作 输出如下
YMTable touchShould Begin
YMButton touch begin
被点击了
当触摸发后在contet view 也就是button上时,直接执行的button的点击事件,ScrollView 并没有发生滚动.