事件传递与响应链探索
iOS App 使用响应者对象( Responder)来接收和处理事件。一个响应者对象是 UIResponder 类的实例,它常见的子类包括 UIView 、UIViewController 和 UIApplication。响应者对象接收原始事件数据,并且必须处理该事件或将它转发给另一个响应者对象。
当你的 App 接收到一个事件时,UIKit 自动将它发送给最适合的响应者对象,即第一响应者对象(First Responder)。未处理的事件从响应者对象被发送到正在活动的响应链中的响应者对象(响应者链是应用程序的响应者对象的动态配置,在应用程序中没有单个响应者链)。
UIKit 为事件如何从一个响应者对象传递到另一个响应者对象预定义了规则,但是你可以通过覆盖响应者对象中的属性来改变这些规则。
响应者对象 (Responder Object)
Responder 对象是 UIResponder 类的实例,构成了 UIKit 应用程序事件处理的主干。许多重要对象也是响应者,包括 UIApplication 对象,UIViewController 对象和所有 UIView 对象(包括 UIWindow)。当事件发生时,UIKit 将它们分派给你的应用程序的响应者对象进行处理。UIResponder 对象能处理的事件,包括触摸事件,动作事件,远程控制事件和新闻事件。
为了处理特定类型的事件,响应者必须覆盖相应的方法。例如,为了处理触摸事件,响应者实现了 touchesBegan:with:,touchesMoved:with:,touchesEnded:with: 和 touchesCancelled:with:方法。在触摸事件发生时,响应者使用 UIKit 提供的事件信息来跟踪触摸的变化,并适当地更新应用的界面。
除了处理事件之外,UIKit响应者还负责将未处理事件转发到应用程序的其他部分。如果给定的响应者不处理事件,则将该事件转发给响应者链中的下一个响应者对象。 UIKit 动态地管理响应者链,使用预定义的规则来确定接下来哪个对象应该接收事件。
响应者处理 UIEvent 对象,但也可以通过输入视图接受自定义输入。系统的键盘是输入视图最明显的例子。当用户点击屏幕上的 UITextField 和 UITextView 对象时,视图成为第一响应者并显示其输入视图,这是系统键盘。同样,你可以创建自定义输入视图,并在其它响应者激活时显示它们。要将自定义输入视图与响应者相关联,请将该视图分配给响应者的 inputView 属性。
NOTE
与加速度计、陀螺仪和磁力计相关的运动事件不遵循响应者链。Core Motion 会直接将这些事件发送给你指定的对象。
第一响应者对象 (The First Responder)
UIKit 为各种类型的事件指定第一响应者对象并在事件发生时首先发送到第一响应者对象。
Touch events
第一响应者对象是触摸发生的视图
Press events
第一响应者对象是获得焦点的响应者对象
Shake-motion events
第一响应者对象是你或
UIKit指定的对象Remote-control events
第一响应者对象是你或
UIKit指定的对象Editing menu messages
第一响应者对象是你或
UIKit指定的对象
控件使用动作消息 (Action message) 与其关联的目标对象 (Target Object) 直接进行通信。当用户与控件进行交互时,控件调用其目标对象的动作方法 (Action Method),向目标对象(Target object)发送一个动作消息。动作消息不是一个事件,但它们仍可利用响应链。当控件的目标对象为 nil 时,UIKit 从目标对象开始,遍历响应者链,直到寻找到实现了相应方法的对象。
如果视图中有添加手势,那么手势会在视图接收之前接收触摸和按下事件。如果视图中的所有手势都无法处理这个事件,则将事件传递给视图进行处理。如果视图也不能处理,则 UIKit 会将事件传递给响应者链。
基于视图的点击测试 (View-Based Hit-Testing)
UIKit 使用基于视图的点击测试来确定触摸事件到底发生在哪里。UIKit 将触摸位置与处在视图层级中的视图的 bounds 进行比较。hitTest:withEvent: 方法遍历视图层级,寻找包含指定触摸的层级最深的子视图。 然后这个视图就成为第一响应者对象。
当用户触摸屏幕进行交互时,系统检测到手指触摸操作,并将触摸以 UIEvent 的方式加入 UIApplication 的事件队列中。UIApplication 从事件队列中取出最新的触摸事件进行分发传递到 UIWindow 进行处理。而 UIWindow 会通过 hitTest:withEvent: 方法寻找触点所在的视图,这个过程称之为 Hit-Test。
UIKit 将每个触摸永久绑定到包含它的视图对象。UIKit 在触摸第一次发生时创建 UITouch 对象,并只在触摸结束时释放它。当触摸位置或其它参数改变时,UIKit 使用新的信息来更新 UITouch 对象。唯一不改变的属性就是包含它的视图,即使触摸位置已经移出原始的视图。
Hit-Test 从顶级视图开始调用 pointInside:withEvevt: 方法判断触摸点是否在当前视图内,如果返回为 NO ,则 hitTest:withEvent: 方法返回 nil;如果返回 YES ,则向当前视图的所有子视图发送 hitTest:withEvent: 消息,所有子视图的遍历顺序是从最顶层视图一直到最底层视图,直到有子视图返回非空对象或者全部子视图遍历完毕。
NOTE
如果一个触摸的位置超出了视图的边界,
hitTest:withEvent:方法就会忽略这个视图及其所有子视图。
响应者链 (Resoponder Chain)
当系统通过 Hit-Test 找到触摸点所在的视图,但是这个视图并没有或者无法正常处理此次触摸事件,这个时候,系统便会通过响应者链寻找下一个响应者,以对此次触摸事件进行响应。

如果一个 View 有一个视图控制器,它的下一个响应者就是这个视图控制器,然后才是它的父视图,如果一直到根视图都没能处理这个事件,事件会传递到 UIWindow ,若在 UIWindow 中也没有处理,则会传递给 UIApplication ,它是一个响应者链的终点,它的下一个响应者指向 nil ,以劫数整个循环。
改变响应者链 (Altering the Responder Chain)
你可以通过覆盖你的响应者对象中的 nextResponder 属性来改变响应者链。当你这样做时,下一个响应者就是你返回的对象。
很多 UIKit 类已经覆盖了这个属性 :
UIView对象。如果视图是视图控制器的根视图,那它的nextResponder是视图控制器,否则是视图的父视图。UIViewController对象。- 如果视图控制器的视图是一个窗口的根视图,
nextResponder是窗口对象。 - 如果一个视图是被其它视图控制器推出的,
nextResponder是推出它的视图控制器。
- 如果视图控制器的视图是一个窗口的根视图,
UIWindow对象。窗口的nextResponder是UIApplication对象。UIApplication对象。只有在应用代理是UIResponder的实例而不是视图、视图控制器或者应用对象自身时,UIApplication的nextResponder是应用程序代理。