iOS之响应者链

事件传递:

  1. 当触摸到屏幕时会生成一个Touch Event(触摸事件),添加到UIApplication管理的事件队列中;
  2. UIApplication会从事件队列中依次取出事件来分发到应响应的视图去处理;
  3. 当触摸事件被UIApplication发出后,会从程序的keyWindow开始,然后依次向上传递,包括各种视图控制器以及视图,最后找到合适的处理该事件的视图来响应。

涉及的两个方法

1
2
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
-(BOOL)pointInSide:(CGPoint)point withEvent:(UIEvent *)event;

传递过程中的函数调用

UIApplication发送事件到keyWindow时,keyWindow会调用-hitTest:withEvent:方法来寻找最适合处理事件的视图,当事件传递到某个视图时:

  1. 判断当前视图自身能否接收事件,如果不能,则返回nil

  2. 调用当前view的pointInside:withEvent:方法来断定触摸点是否在当前view内部:

    • 若是返回false,则hitTest:withEvent:返回nil
    • 若是返回true,则向当前view内的subViews发送hitTest:withEvent:消息,subViews的遍历顺序是从数组的末尾向前遍历,直到有subView返回非空对象或遍历完成
  3. 若是有subView返回非空对象,hitTest方法会返回这个对象;若是每一个subView返回都是nil,则返回当前视图自身。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // point是该视图的坐标系上的点
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2. 判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    // 3.从后往前遍历自己的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
    UIView *childView = self.subviews[i];
    // 把当前控件上的坐标系转换成子控件上的坐标系
    CGPoint childP = [self convertPoint:point toView:childView];
    UIView *fitView = [childView hitTest:childP withEvent:event];
    if (fitView) { // 寻找到最合适的view
    return fitView;
    }
    }
    // 循环结束,表示没有比自己更合适的view
    return self;
    }

事件的处理:响应者链

响应者链关联着一系列的响应者对象,由第一个响应者对象开始一直到 application对象结束,若是第一个响应者无法处理事件,事件将会被传递到响应者链中的下一个响应者对象。

  • hit-test视图或者第一响应者 (first responder) 无法处理事件时, UIKit 将传递事件到响应者链中的下一个响应者。
  • 每个响应者都会决定是否处理事件,无法处理时会调用nextResponder方法将事件传递给下级响应者。
  • 该过程一直到有一个响应者对象可以处理事件或者没有下级响应者为止。