事件处理指南(Event Handling Guide for iOS) 阅读笔记 (二) 响应链

Event Delivery: The Responder Chain

我们希望在我们的app中可以动态的响应触摸事件.比如一个触摸可能会发生在屏幕上不同的位置和不同的组件上, 我们需要判断哪个组件响应这个触摸并且了解这个组件是如何接受到触摸事件的.

当一个用户触摸事件发生了, UIKit会创建一个包含需要被处理的事件信息的对象.然后将这个对象放入当前的事件循环队列中,对于触摸事件,这个对象被创建为 UIEvent 对象,对于移动事件这个对象会依赖于你使用的 Framework和你所关心的移动事件.

我们假设这个事件对象为 EventA

EventA会通过一条特殊的路径进行传递, 直到它能被处理为止. 首先 单例的 UIApplication 对象从事件队列中拿到了EventA并且分发这个事件对象. 通常, UIApplication 会将 EventA 分配到创建EventA的 应用key Window 对象中去处理. EventA 是依赖于发生事件的类型, 共有两种类型:

  • 触摸事件(Touch events) 对于触摸事件, Window 对象会尝试将事件传递给触摸发生的view. 这个view被称为 hit-test view. 那么, 找到这个view的过程叫做 hit-testing. 找到view的过程会在下面说明.

  • 摇晃和远程控制事件(Motion and remote control events) 处理这个事件, Window 对象会发出一个 晃动事件 或者 远程控制事件给第一响应者, 如何找到第一响应者,会在下面说明.

那么, 这些事件传递路径最终的目的是找到一个能处理它的对象.所以, UIKit 会将这些事件发送给最适合的对象来处理它们.对于触摸事件, 这个处理对象是 hit-test view, 对于其他的事件, 这个对象是 第一响应者 first responder.

Hit-testing 返回一个触摸事件发生的view

iOS使用 hit-testing 找到被触摸到view. Hit-testing 操作包括检查触摸是否在相关的view的大小之内(bounds), 如果是, 会循环遍历这个view的所有子视图和子视图的子视图, 在所有视图层级中,层级最低的并且包含这个触摸事件的视图就是 hit-test view 当iOS找到这个视图后, 会讲事件对象交给这个视图去处理.
下面有个图示, 我们说明一下如何找到 hit-testing view
技术分享

假设我们点击个View E 视图, 系统首先调用 hitTest:withEvent:, 将View A 和 点击坐标点传入,遍历寻找子view, 这时, 找到触摸点在View C上, 继续寻找View C的子View, 然后找到触摸点在View E上, 并且View E 没有子视图了, 所有 方法返回 View E作为 hit-test View, 成为第一响应者.

hitTest:withEvent: 是UIView的方法,它通过给定的 点击坐标和事 CGPoint and UIEvent ,该方法内部调用pointInside:withEvent: 来检查触摸点是否在一个view上,如果返回YES, 说明在view上, 然后hitTest:withEvent: 循环调用pointInside:withEvent: 返回为YES的view.
如果触摸点没有在调用hitTest:withEvent: view上, 内部调用pointInside:withEvent: 将返回NO, hitTest:withEvent: 将返回nil. 如果一个子视图的hitTest:withEvent: 返回NO, 那么它下面的所有视图层级都讲倍忽略,因为触摸点没有在任何一个它的子视图上,

* 注意: 一个触摸事件在他的生命周期内都与这个hit-test view绑定, 即使触摸点随后移出了view的边界 *

这个 hit-test view 是第一个有机会处理触摸事件的视图, 如果这个view不能处理这个事件, 事件将会沿着view的层级链向上查找第一个能处理他的视图,找到并处理后就停止传递.

响应链是由响应对象组成的

许多事件都需要依靠响应链来传递消息. 响应者链是一系列响应者连接起来的序列.它从第一响应者开始到 application对象结束.如果第一响应者无法处理事件, 事件会被传递到响应者链上的下一个响应者处理.
一个响应者是一个能够响应和处理事件的对象. UIResponder 类是所有响应者的基类,它为事件处理和响应者一般的操作定义了一些接口.像UIApplication, UIViewController 和 UIView 的对象都是响应者. 注意, Core Animation layers 不是响应者.

第一响应者是被指定为第一个接收事件的响应者, 通常, 第一响应者是一个UIView 对象. 一个对象要成为第一响应者, 必须做两件事:

  1. 设置属性 canBecomeFirstResponder 为YES
  2. 接收 becomeFirstResponder 发来的消息

* 注意: 确保一个对象被指定为第一响应者之前, 已经建立了对象或视图. 比如: 你通常调用了 becomeFirstResponderviewDidAppear: 方法中, 如果你尝试指定第一响应者在 viewWillAppear: 方法中, 但是你的响应对象没有建立, 就会导致 becomeFirstResponder 返回 NO*

响应者链还用于下面的事件:

  • 触摸事件 Touch events. 如果hit-test view 无法处理事件, 事件会沿着hit-test view 传递寻找处理者, 若无人处理,则丢弃.
  • 远程控制事件 Remote control events. 为了处理事件, 第一响应者必须实现UIResponder的方法remoteControlReceivedWithEvent:
  • 点击事件 Action messages. 当用户使用一个控制视图,比如UIButton 或者 UISwitch, target的响应方法为 nil 时, 事件会沿着响应链传递.
  • 编辑菜单事件 Editing-menu messages. 当用户使用编辑菜单, iOS会在响应者链中寻找必要方法的响应者,如 cut: copy: paste: 方法.
  • 文字编辑 Text editing. 当用户使用 text field 或 text view是, view会自动变成第一响应者. 默认的虚拟键盘弹出并且view会成为编辑焦点.你也可以弹出自定义的输入框来代替系统键盘.

UIKit 会自动的将被点击的text field 或 text view 设置为第一响应者,我们必须显示的调用 becomeFirstResponder 来设置其他的对象成为第一响应者.

响应者链遵循一个特定的传递路径

如果初始化的对象-不是hit-test view, 就是第一响应者, 没有处理事件, UIKit 会将事件沿着响者链传递下去. 每个响应者都要决定是否要处理事件或者是通过调用 nextResponder 方法继续传递给下一个响应者. 这个过程一直持续到一个响应者处理了事件或者没有下一个响应者为止.

响应者链开始在iOS发现一个事件并传递给初始化的对象, 通常是一个View. 这个初始化的对象是第一个有机会处理事件. 下图为两种事件传递路径, 取决于不同的视图构造.
技术分享

对于左边的app, 事件传递如下:

  1. initial view 尝试处理事件或消息. 如果它不能处理, 它将事件传递给它的父视图super view, 因为 inital view 不是它的 viewController的视图层级中最上的视图(离viewController最近的view,试图栈中栈底元素).
  2. super view 尝试处理事件或消息. 如果它不能处理, 它将事件传递给它的父视图,因为 super view 仍然不是viewController的视图层级中最上的视图.
  3. viewController的最上视图 topmost view 尝试处理事件或消息. 如果它不能处理, 它将事件传递给viewController
  4. viewController尝试处理时间, 如果它处理不了, 将事件传递个Window.
  5. 如果window对象不能处理事件, 它把时间传递给 Application
  6. 如果Application 不能处理事件, 那么事件被丢弃.

右边的app 传递路径有一点不一样, 但是传递规则遵循下面的尝试算法:

  1. 一个view沿着它的viewController的视图层级传递事件, 直到传递到最上视图.
  2. 最上视图传递事件给它的viewController.
  3. viewController将事件传递给最上视图的父视图, 循环1-3的步骤, 直到事件到达root viewController.
  4. root viewController 将事件传递给 window对象
  5. window将事件传递给 UIApplication对象.

重要提示: 如果你的自定义view来处理 remotecontrolevents,actionmessages,shake-motion events, or editing-menu messages 这些消息或事件, 不要将事件或消息直接通过 nextResponder 转发到响应链中. 而应该是调用父类来实现当前的事件处理, 让UIKit 来处理事件在响应链中的转发

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。