关于iOS Responder Chain 的一些理解

基本概念

  • 响应者:它是 UIResponder/UIView/UIViewController/UIApplication 的实例。它会接受事件,而且它必须处理事件或将事件传递给下一个响应者。UIKit 会自动决定哪一个对象为最合适的响应者,即第一响应者。
  • 响应链:响应者传递事件的过程。

响应链的传递流程

以官方文档的图举例:ios

若是 text field 没有处理事件, UIKit 会将事件传递给它的父视图 UIView 对象,依次传递到 UIViewController 的根视图,若还不能处理,则传递给 UIWindow 。若是 window 不能处理事件,则将该事件再传递给 UIApplication。bash

如何决定哪个响应者包含 touch 事件

UIKit 使用基于 view 的 hit-testing 去决定 touch 事件发生的位置。在发生 touch 事件后,UIKit 会比较 touch 的位置所在的视图是否在视图层级中。hitTest(_:with:)会将包含 touch 事件的最上层的视图变为第一响应者。app

若是 touch 的位置超出视图的边界,那 hitTest(_:with:) 方法会忽略该视图的全部子视图。所以,当一个视图的 clipsToBounds 为 false 时,即便 touch 的位置未超出子视图的边界,子视图也不会响应该事件。ide

下面解释一下若是 touch 的位置超出视图的边界,那 hitTest(_:with:) 方法会忽略该视图的全部子视图:函数

若是咱们有上图层级结构,blueView 是 greenView 的子视图,grayView 是 blueView 的子视图,三个视图都添加 UITapGestureRecognizer 手势,若是你点击 grayView 中超出 blueView 的部分它是不会响应 grayView 的 action,由于该部分已经超出了 blueView 部分,因此 blueView 及其全部子视图都会被响应链忽略。因此,你点击 grayView 中超出 blueView 的部分,响应者是 greenView,会响应 greenView 的 action 。ui

当发生一个 touch 事件时,UIKit 会建立一个 UITouch 的对象,并将它与一个视图联系起来。须要注意的是,当 touch 的位置或者其余参数改变时, UIKit 会将 UITouch 对象的信息更新。可是 UITouch 对象的 view 属性并不会变。即便 UITouch 对象的位置已经超出原始视图的边界,UITouch 对象的 view 属性值也不会改变。当 touch 结束,UIKit 会释放 UITouch 对象。spa

hitTest(_:with:)

该方法主要用来返回接受 touch 事件的视图层中最上层的且包含当前 point 的子视图。code

它经过 point(inside:with:) 函数来寻找包含 point 的子视图,若是 point(inside:with:) 返回 true,则再往上一直找到最上层且包含 point 的子视图。cdn

你能够经过重写它来对某些子视图隐藏响应事件。当视图的 hidden 为 true 或 isUserInteractionEnabled 为 false 或 alpha 的值小于 0.01 的时候,这些视图将被该方法忽略。对象

如何修改响应链

你能够经过重写响应者的 next 属性来修改响应链。当你修改以后,下一个响应者即你返回的那个对象。

下面是 UIKit 各种默认下一个响应者:

  • UIView:若 view 是 Controller 的根视图,下一个响应者为 Controller;若不是根视图,则下一个响应者为它的父视图。
  • UIViewController:若是 Controller 是 window 的根视图,下一个响应者是 window 对象;若是 Controller1 是被 Controller2 present 的,那下一个响应者即Controller2。
  • UIWindow:下一个响应者为 UIApplication 对象、
  • UIApplication:当 app delegate 是 UIResponder 一个实例,而且不是一个 view 、 view controller 或者 app 它自己时。UIApplication 的下一个响应者为 app delegate。

关于 UIControl

若是咱们在添加手势的视图上面添加一个带有 action 的 button ,若是咱们点击 button 会触发 视图的手势事件 仍是 button 的 action 呢?

答案是 button 的 action 。看官方文档咱们会看见这么一句话手势处理器不会影响 UIKit controls 处理事件的能力。

不单单是 UIButton ,其余下面的对象也不会受手势识别器的影响:

  • UISwitch, UIStepper, UISegmentedControl, UIPageControl, UISlider UISwitch

以上这些皆为 UIControl 的子类。

实际应用

扩大UIButton的点击范围

  • 建立一个 UIButton 的子类,重写 point(inside:with:) 方法
// 将按钮的点击范围上下左右扩大10pt的范围,注意范围不要超出父视图的边界,超出范围依旧无效。
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    let newFrame = CGRect(x: bounds.origin.x - 10, y: bounds.origin.y - 10, width: bounds.size.width + 20, height: bounds.size.height + 20)
    return newFrame.contains(point)
}
复制代码
  • 设置 button 的 contentEdgeInsets 属性
button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
复制代码

解决 scrollView及子类TableView/CollectionView 右滑手势和系统的右滑返回手势的冲突

// 经过修改响应者依赖 来使 scrollView及子类 的手势响应失效
if let rec = navigationController?.interactivePopGestureRecognizer {
    collectionView.panGestureRecognizer.require(toFail: rec)
}
复制代码

总结

  • 如何查找第一响应者:
1.UIWindow 接受到一个事件,执行 hit-test 去寻找应该接受该事件的对象
二、hitTest:withEvent 将会经过调用 pointInside :withEvent: 方法来判断
视图是否包含当前事件的位置。
三、一直递归调用 hitTest:withEvent 直到找到最上层且包含 point 的对象,即为第一响应者。
复制代码
  • 事件不能处理的时响应链的传递过程
subview -> superview ... -> root view -> view controller -> root view controller -> window -> UIAPPlication - app delegate
复制代码
  • UIControl 的子类处理事件的能力不收手势识别器的影响

参考

相关文章
相关标签/搜索