Swift -- Responder Chain

  • 概述

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

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

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

通过修改 Respondernext 属性可以修改 Responder Chain(可以想象成更新链表上某一个节点的下一个节点)

UIKit 很多类已经重写了next属性,例如

  • UIView -- 如果 UIViewroot viewnext指向 UIViewController,否则指向父视图
  • UIViewController -- 如果 UIViewController 的视图是 Root Viewnext指向 UIWindow;如果当前的 UIViewController 被特定的 UIViewController 弹出的(present),那么next指向该特定的 UIViewController
  • UIWindownext指向 UIApplication
  • UIApplicationnext指向 App Delegate,但只有 App DelegateUIResponder 的实例对象,且不是 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:)最后选择的 ViewUIControl 的实例对象,那么 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 提供的便利。当 UIControlTargetnil 的时候,UIKit 会遍历 Responder Chain,直到找到一个实现了对应的 Action MethodsResponder 为止。此处调用的是 RespondercanPerformAction(_: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

你可能感兴趣的:(Swift -- Responder Chain)