本文对Responder Chain的机制进行讲解。
iOS系统的Events类型分为以下几种:Multitouch events, Accelerometer events, Remote control events。
The Responder Chain
当iOS系统识别一个Event,它会首先将其传递给对可能相关的对象,例如触碰的View,如果该View无法处理,则往更外层传递,直到找到一个可以处理的相应对象,这一设计模式成为响应链(The Responder Chain)。
当一个用户交互事件发生时,UIKit会创建一个Event对象,包含其信息来处理该事件,然后将其放置到App的Event队列中,UIApplication对象从队列中取出该事件,并对其进行分发。一般情况下,会发送给Key Window对象,Key Window对象再对其进行分发:
- 如果是Touch Events,则会分发到触碰发生的View(Hit-Test View)上;
- 如果是Motion and Remote Control Events,则会分发到第一响应者(First Responder)上。
Hit-Testing
Hit-Testing是iOS系统用于寻找到触碰View的方式。它通过递归subViews的方式,根据触碰的点是否在View的Bounds中,来判断最底层的触碰View,例如下图中:
触碰点在A中,遍历subViews,不在B中,在C中,则继续遍历subViews,不在D中,在E中,E没有subViews,确定Hit-Testing View就是E。
Hit-Testing的方法为:
1 | hitTest:withEvent: |
如果Hit-Testing View无法处理该事件,则会通过Responder Chain来传递,寻找到一个可以处理该事件的对象。
Responder Chains
许多Events类型的分发,是基于Responder Chain来完成的。The Responder Chain是由一系列连接在一起的Responder Objects构成的。The Responder Chain中的第一个为The First Responder,最后一个为UIApplication。
The Responder Chain的传递路径分为两种,分别如下,左图是ViewController间没有发生View嵌套的,右图是发生了嵌套的:
The Responder Chain不仅仅处理Events,还包含其他用途:
- Touch Events:如果Hit-Testing View无法处理,则会通过The Responder Chain传递该事件;
- Motion Events:The First Responder必须实现motionBegan:withEvent:或者motionEnded:withEvent:方法;
- Remote Control Events:The First Responder必须实现remoteControlReceivedWithEvent:方法;
- Action Messages:用户点击UIButton或者UISwitch,而其Target没有设置时,该消息会通过The Responder Chain传递;
- Editing-menu Messages:用户点击剪切、复制和粘贴面板时,该消息会通过The Responder Chain传递;
- Text Editing:用户点击输入框时,该View会自动成为The First Responder,自此虚拟键盘会出现,但是也可以设置自定义的View来替代键盘出现,设置UITextField的InputView即可。
Responder Objects
Responder Objects是可以响应和处理事件的对象,基类为UIResponder,其包含了通用的相应行为,不单单事件处理。UIApplication, UIViewController和UIView都继承了该类。
注意,Core Animation layers不是Responders。
The First Responder是第一个处理事件的对象,一般情况下,是一个UIView。一个对象成为The First Responder可以通过以下两个方式:
- 重写canBecomeFirstResponder方法,返回YES;
- 接收becomeFirstResponder消息,在必要时,可以给自身发送这个消息。
注意:UIKit会在用户点击时,自动设置TextField和TextView为The First Responder,而其他的对象,则需要Apps去显式调用becomeFirstResponder来设置。另外,不要在没有绘画完成的时候,调用becomeFirstResponder,例如要在viewDidAppear中,而不要在viewWillAppear中发送,否则不会产生效果,会返回NO。