-
概述
Responder -- 可以接收事件,处理事件或把事件传递给其他 Responder 的对象。
Responder 可以是任何 UIResponder 的实例对象或者是继承于 UIResponder 的子类的实例对象,例如常见的 UIView, UIViewController, UIWindow, UIApplication。
Responder Chain -- 由 Responder 组成的链表
当系统检测到一个事件时,会创建一个 UIEvent 对象,并调用UIApplication.shared.sendEvent()
把该对象放到事件队列中,当事件从队列中弹出时,UIKit 会把这个事件先传递给最合适的 Responder 进行处理,即 First Responder
没有被处理的事件,就会沿着 Responder Chain 传递下去,直至找到一个可以处理该事件的 Responder 为止,如果没有找到,该事件就会被抛弃。
Responder 通过重写下列的方法声明自己可以处理某些事件
open func touchesBegan(_ touches: Set, with event: UIEvent?)
open func touchesMoved(_ touches: Set, with event: UIEvent?)
open func touchesEnded(_ touches: Set, with event: UIEvent?)
open func touchesCancelled(_ touches: Set, with event: UIEvent?)
open func pressesBegan(_ presses: Set, with event: UIPressesEvent?)
open func pressesChanged(_ presses: Set, with event: UIPressesEvent?)
open func pressesEnded(_ presses: Set, with event: UIPressesEvent?)
open func pressesCancelled(_ presses: Set, with event: UIPressesEvent?)
open func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
open func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
open func remoteControlReceived(with event: UIEvent?)
下图中的页面包含了一个UITextField, 一个UIButton, 一个UILabel和一个作为背景的UIView,右侧为相应的 Responder Chain
触摸事件会首先被传递给 First Responder,例如用户点击了UITextField,那么UITextField就是 First Responder,如果UITextField没有处理这个事件,那么 UIKit 就会把该事件传递给 UITextField 的父视图,即作为背景的 UIView。如果该事件也没有被处理,UIKit 接着把该事件传递给 UIView 的父视图,即 Root View ;后面的传递次序为(事件未被处理的情况下):UIViewController(如果有的话) --> UIWindow --> UIApplication,如果 UIApplicationDelegate 也是 UIResponder 的一个实例对象且不在传递链上的时候,UIKit还会把该事件传递给 UIApplicationDelegate。此时,事件已经被传递到了传递链的末端,如果事件得不到处理,那么该事件就会被抛弃。
-
确定事件对应的 First Responder
UIKit根据事件的类型指定一个对象作为事件的 First Responder
注意:和加速器(accelerometers),陀螺仪(gyroscopes),磁力计(magnetometer)相关的运动事件并不是沿着 Responder Chain 传递的,Core Motion 会直接把这类运动事件传递给指定的对象进行处理
-
确定哪一个 Responder 包含了触摸事件
从上述表中,我们可以看出触摸事件的 First Responder 是该触摸事件所在的 View
那么如何确定触摸事件出现在哪一个 View 上面呢?
UIKit 使用 hitTest(_:with:)
来确定触摸事件所在的 View,该方法会沿着 View 的层次逐层比较触摸发生的位置和该层中各个 View 的边界,直到找到包含触摸位置的最深层的 View ,该 View 就是触摸事件的 First Responder
注意:如果触摸位置在某一个 View 的边界外,那么该 View 和它的所有子视图都会被忽略。即使clipsToBounds
的值为false
(即子视图在父视图的边界外是可以被看见的),而在 View 边界外的子视图恰好包含了触摸的位置,该子视图也会被忽略(因为父视图并不包含触摸的位置)
当一个触摸事件发生的时候,UIKit 会创建一个 UITouch 对象,并将该对象与触摸所在的 View 关联起来,当触摸位置发生变化时,UITouch 包含的信息会被更新,而 View 的相关信息不变。触摸结束时,UITouch 对象会被释放。
-
修改 Responder Chain
通过修改 Responder 的next
属性可以修改 Responder Chain(可以想象成更新链表上某一个节点的下一个节点)
UIKit 很多类已经重写了next
属性,例如
- UIView -- 如果 UIView 是 root view,
next
指向 UIViewController,否则指向父视图 - UIViewController -- 如果 UIViewController 的视图是 Root View,
next
指向 UIWindow;如果当前的 UIViewController 被特定的 UIViewController 弹出的(present),那么next
指向该特定的 UIViewController - UIWindow 的
next
指向 UIApplication - UIApplication 的
next
指向 App Delegate,但只有 App Delegate 是 UIResponder 的实例对象,且不是 UIView,不是 UIViewController,也不是 App 自己的时候才成立
becomeFirstResponder()
让 Responder 成为 First Responder
resignFirstResponder()
让当前的 First Responder 回到原来的位置
这两种方法常用于点击回车键时在不同的 UITextField 之间切换,即让不同的 UITextField 成为 First Responder,此外 Responder 有一个可选的inputView
属性,当 Responder 成为 First Responder 时,会显示这个 View,在 UITextField 中,inputView
指的是键盘
-
Controls
如果hitTest(_:with:)
最后选择的 View 是 UIControl 的实例对象,那么 UIKit 将调用UIApplication.shared.sendAction(_:to:from:for:)
将 Action Messages 传递给与 UIControl 相关联的 Target,进而调用 Target 中定义的 Action Methods
这里利用的是 Target-Action 机制,通过直接将 Action Messages 传递给 Target 可以简化事件在 Responder Chain 传递的流程和降低处理事件的难度。注意!Action Messages 并不是事件
虽然 Action Messages 不是事件,不能沿着 Responder Chain 传递,但仍然可以利用 Responder Chain 提供的便利。当 UIControl 的 Target 为 nil 的时候,UIKit 会遍历 Responder Chain,直到找到一个实现了对应的 Action Methods 的 Responder 为止。此处调用的是 Responder 的canPerformAction(_:with:)
方法来确定 Responder 是否实现了相应的 Action Methods
例如 UIKit 提供的编辑菜单就是使用这样的机制找到合适的 Responder,调用其中的cut(_:)
, copy(_:)
, paste(_:)
等 Action Methods
关于 UIControl 的介绍请看 Swift -- UIControl
-
Gesture Recognizer
手势识别(Gesture Recognizer)会先于它们的View接收触摸或点按事件,如果 Gesture Recognizer 不能处理这些事件,那么就会把事件传递给它们的 View,如果 View 也不能处理事件,那事件将会沿着 responder chain 传递下去
有关手势识别的介绍请看:Swift -- UIGestureRecognizer
-
参考文献
Using Responders and the Responder Chain to Handle Events
UIControl
iOS Responder Chain: UIResponder, UIEvent, UIControl and uses