Event Handling Guide for iOS(三)

事件传递:响应者链

在设计APP的过程当中,你可能希望动态的响应事件。例如,一个触摸可以发生在许多不同对象的表面,而你必须了解对象是如何接收到事件的并决定哪一个对象来响应已有的事件。

当用户触摸事件发生,UIKit创建一个包含用于处理事件信息的事件(Event)对象。然后将事件对象加入到当前的事件队列中。触摸事件对象,包含了一系列触摸对象的UIEvent对象;移动事件,该事件对象的变化取决于你使用的框架和移动事件的类型。

一个事件沿着一条特定的路径进行传递,直到被传递给一个能处理事件的对象。首先,单例的UIApplication对象会从队列的顶端拿到事件对象,然后派发该对象寻求处理。具有代表性的是,UIApplication将事件对象发送给APP的keyWindow对象,该keyWindow对象传递事件对象给初始化对像寻求处理。 所谓的初始化对象取决于事件的类型。

  • 触摸事件。对于触摸事件而言,window对象首先试图将事件传递给触摸发生处的视图。众所周知,该视图为hit-test视图。寻找hit-test视图的过程叫做hit-testing,参见"Hit-testing返回触摸发生处的视图"(后序文章)。

  • 移动和远程控制事件。对于这些事件,window对象将震动或者远程控制事件发送给第一响应者寻求处理。参见"响应者链由响应者组成"(后序文章)的第一响应者描述。

这一事件路径的终极目标是为了寻找一个能够响应和处理事件的对象。因此,UIKit会首先将事件对象发送给最适合处理事件的对象。对于触摸事件对象,这个对象是hit-test视图,而对于其他事件,该对象是第一响应者。接下的章节详细介绍了hit-test视图和第一响应者是如何被确认的。

Hit-testing返回触摸发生处的视图

iOS通过hit-testing来寻找触摸之下的视图。Hit-testing包括检查触摸是否在相关视图的边界之内。如果在,则会递归检查视图的所有子视图。视图层级中包含触摸点的最低层的视图成为hit-test视图。然后iOS确定hit-test视图,并将触摸事件传递给该视图寻求处理。

(我的备注:这里说的是图层,至于会不会忽略alpha为零、隐藏的视图,要看点击的位置是否在隐藏的视图的边界内,若在则hitTest依然有效,但是pointInside方法会进行忽略。不在边界内则会进行忽略。我验证过了。这与网上绝大多数说法是不同的。 )

为了举例说明,假设用户触摸了图2-1中的视图E。iOS按照如下检查子视图的顺序来寻找hit-test视图:

  1. 触摸在视图A的边界内,因此检查子视图B和C。
  2. 触摸不在视图B的边界内,但是在视图C的边界 内,因此检查子视图D和E。
  3. 触摸在视图D的边界内,但是在视图E的边界内。视图E是图层中包含触摸点的最低层视图,因此它成为hit-test视图。

图2-1 Hit-testing返回触摸处的子视图

Event Handling Guide for iOS(三)_第1张图片
Snip20170914_14.png

hitTest:withEvent:方法根据给定的CGpointUIEvent返回hit-test视图。hitTest:withEvent:方法首先会调用pointInside:withEvent:方法。如果传递进hitTest:withEvent:方法的坐标点在视图的边界内,pointInside:withEvent:方法返回YES。然后,hitTest:withEvent:递归调用每一个返回YES的子视图的hitTest:withEvent:方法。

如果传递进hitTest:withEvent:方法的坐标点不在视图的边界内,首先会调用pointInside:withEvent:返回NO,忽略该坐标点,pointInside:withEvent:返回nil。如果一个子视图返回NO,该视图所有的分支视图将会被忽略,因为如果触摸发生的点不在子视图内,它更不会在子视图的任何子视图内。这也意味在任意一个在子视图内却在父视图之外的触摸点,父视图不会收到触摸事件,因为触摸点已被判定为不在父视图边界之外更不会在子视图边界内。这种情况会在子视图的clipsToBounds属性被设置为NO出现。

注意:一个触摸对象伴随着它的hit-test视图的生命周期,即便触摸在随后阶段移出了视图。

hit-test视图会首先尝试去处理一个触摸事件。如果该hit-test视图无法处理事件,该触摸事件会继续在视图的响应者链(后续文章"响应者链由响应对者组成"有描述)中传递,直到系统找到一个处理事件的对象。

响应者链由响应者组成

多种类型的事件依赖于传递事件的响应者链。响应者链就是一系列相连接的响应者。以第一响应者开始结束于application对象。如果第一响应者无法处理事件,事件会被转发到响应者链中的下移响应者。

一个能够响应和处理事件的对象称之为响应者。UIResponder是所有的响应者的基类,它定义了事件处理的程序接口和公有的响应者行为。 UIApplicationUIViewController, 和 UIView类的实例对象都是响应者,这也意味着所有的视图和主控制器是响应者。要注意的是,核心动画的layer不是响应者。

第一响应者被设计为第一个接收到事件的对象。一般来说,第一响应者是一个视图对象。成为第一响应者的对象需要满足两个条件:

  1. 覆写canBecomeFirstResponder方法并返回YES。
  2. 收到becomeFirstResponder消息。如果有必要,对象可以发送这条消息给自己。

待续.....

你可能感兴趣的:(Event Handling Guide for iOS(三))