iOS Responder Chain

不知你是否有思考过,你在Touch一个application的某一点之后,iOS系统是怎么处理这些事件,并且如何准确响应的?那么接下来的知识,将会向你清楚的讲解结合demo关于iOS中事件的响应链:在我们点击屏幕的时候,iPhone OS获取到了用户进行操作行为,操作系统把包含这些点击事件的信息包装成UITouch和UIEvent形式的实例,然后找到当前运行的程序,逐级寻找能够响应这个事件的对象,直到没有响应者响应。

响应者

在iOS中,能够响应事件的都是UIResponder的子类对象,其相关的方法如下:

1
2
3
4
5
6
7
8
9
// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application. Failure to
// do so is very likely to lead to incorrect behavior or crashes.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//touch开始
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//touch移动
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//touch结束
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//取消touch事件

通过上面我们可以看到每一个响应者的委托方法中返回的有UITouch触发对象集合,以及UIEvent触发的事件类型。

  • UITouch点击对象

    表示单个点击,相关属性和方法如下:

    1
    2
    3
    4
    5
    	@property(nonatomic,readonly) UITouchPhase        phase;//包含touch的状态(UITouchPhaseBegan,UITouchPhaseMoved,UITouchPhaseStationary,UITouchPhaseEnded,UITouchPhaseCancelled)
    @property(nonatomic,readonly) UITouchType type
    - (CGPoint)locationInView:(nullable UIView *)view;//获取当前点击坐标点
    - (CGPoint)previousLocationInView:(nullable UIView *)view;//获取上个点击位置的坐标点
    ......
  • UIEvent点击事件
    iOS中使用UIEvent表示用户交互的事件对象,相关属性方法如下:

    1
    2
    3
    4
    5
    	@property(nonatomic,readonly) UIEventType     type NS_AVAILABLE_IOS(3_0);//包含事件的响应类型,分别有多点触控、摇一摇以及远程操作等
    @property(nonatomic,readonly) UIEventSubtype subtype NS_AVAILABLE_IOS(3_0);//subtype事件等
    @property(nonatomic,readonly) NSTimeInterval timestamp;//时间戳
    - (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;//可以通过event事件来获取相应view的UITouch的对象集合
    ......

需要注意的是UIView除了继承自UIResponder的touches事件外,还可以被绑定一些复杂的手势事件,如:UIGestureRecognizer,UITapGestureRecognizer等。在给UIView添加了UITapGestureRecognizer手势后,原本正常的touches事件流程touchesBegan,存在move(touchesMoved),touchesEnded。变成了touchesBegan,UITapGestureRecognizer的selector方法,然后是touchesCancelled。由此可说明手势的事件优先级高于touches的事件。

Hit-Testing 返回触摸事件发生的视图

iOS 使用hit-testing来找到事件发生的视图。 Hit-testing包括检查触摸事件是否发生在任何相关视图对象的范围内, 如果是,则递归地检查所有视图的子视图。在视图层次中的最底层视图,如果它包含了触摸点,那么它就是hit-test视图。等 iOS决定了hit-test视图之后,它把触摸事件传递给该视图以便处理。如下图:

General preferences pane

如果传递到hitTest:withEvent:方法的点不在视图的范围内,pointInside:withEvent:方法返回NO,点被忽视,并且hitTest:withEvent:返回nil. 如果一个子视图返回NO,则视图层次的整个分支都被忽视,因为如果触摸事件没有发生在那个子视图中,那么事件也不会在任何一个该子视图的子视图中发生。 这意味着在一个子视图中的任何点,如果它在其父视图的外面,那么它不能接收触摸事件,因为触摸点必须在父视图和子视图的范围内。

hit-test是第一处理触摸事件的视图。如果hit-test视图不能处理该事件,事件沿着视图的响应链传递直到系统找到可以处理该事件的视图,如果没有找到处理者则丢弃该事件。

响应链传递

响应链是一系列相连的响应者对象。 它由第一个响应者开始,以应用对象结束。 如果第一响应者不能处理该事件,它把事件传递给响应链中的下一个响应者。

在iOS系统检测到手指触摸后,会将其打包成UITouch和UIEvent加入当前活动的Application的事件队列。同时UIApplication会从事件队列中取出触摸事件并传递到keyWindow。

在UIView.h中存在如下定义的内容:

1
2
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds

hitTest:withEvent: 方法为给定的CGPoint 和 UIEvent返回hit test 视图。hitTest:withEvent:方法通过在自身调用pointInside:withEvent: 方法开始。 如果传递到方法hitTest:withEvent:内的点在视图的范围内,pointInside:withEvent:返回YES。同时将该响应者加入响应者栈,然后通过递归地给每个子视图调用hitTest:withEvent:方法,如果该防返回YES,则加入响应者栈。如下图(需要注意ViewController,AppDelegate,UIApplication并没有上面View的相关方法,但是由于其是UIResponder的相关类):

General preferences pane

很多类型的事件都在事件传递中依赖响应链。 响应链是一系列相连的响应者对象。 它由第一个响应者开始,以应用对象结束。 如果第一响应者不能处理该事件,它把事件传递给响应链中的下一个响应者。

响应者对象是可以响应并处理各种事件的对象。 UIResponder 类是所有响应者对象的基类,它为事件处理和通用响应者行为都定义了可编程接口。 UIApplication, UIViewController, 和UIView的实例都是响应者,就是说所有的视图和大多数主要对象都是响应者。 请注意Core 动画层不是响应者。

参考

1.事件传递:响应链

2.iOS-hitTest:withEvent与自定义hit-testing规则

3.iOS开发 - 事件传递响应链

3.下载demo