事件传递和响应机制

事件传递机制

  1. 当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
  2. UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
  3. UIWindow将事件向下分发,即UIView。
  4. UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
  5. 遍历子控件,重复3,4两步。
  6. 如果没有找到,那么自己就是事件处理者。如果自己不能处理,那么不做任何处理。
  • 注意:其中 UIView不接受事件处理的情况主要有以下三种
  1. alpha <0.01
  2. userInteractionEnabled = NO
  3. hidden = YES

此外在遍历子控件的时候是倒序遍历,此目的是为了让最上层添加的view最先响应事件

整理整个流程如下:

UIApplication -> UIWindow -> 倒序遍历子控件 ->找到能响应的view -> 倒序遍历子控件 ->....->找到最终响应的view

事件传递主要依赖的是以下两个函数:
hitTest:(CGPoint)point withEvent:(UIEvent *)event
pointInSide:(CGPoint)point withEvent:(UIEvent *)event
如果想要自行更改响应视图,可以重写hitTest:withEvent方法,指定要响应事件的view即可

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"-----%@",self.nextResponder.class);
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) return nil;
    //判断点在不在这个视图里
    if ([self pointInside:point withEvent:event]) {
        //在这个视图 遍历该视图的子视图
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            //转换坐标到子视图
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            //递归调用hitTest:withEvent继续判断
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                //在这里打印self.class可以看到递归返回的顺序。
                return hitTestView;
            }
        }
        //这里就是该视图没有子视图了 点在该视图中,所以直接返回本身,上面的hitTestView就是这个。
        NSLog(@"命中的view:%@",self.class);
        return self;
    }
    //不在这个视图直接返回nil
    return nil;
}
下面示例是定义一个button的可点击区域
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    NSLog(@"%@ -- pointInside",self.class);
    CGRect bounds = self.bounds;
    //若原热区小于200x200,则放大热区,否则保持原大小不变
    //一般热区范围为40x40 ,此处200是为了便于检测
    CGFloat widthDelta = MAX(200 - bounds.size.width, 0);
    CGFloat heightDelta = MAX(200 - bounds.size.height, 0);
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
    return CGRectContainsPoint(bounds, point);
    
}

响应机制

响应机制的顺序和事件传递是反着的,事件传递是从上层往下层传递,像剥洋葱一样,找到最终响应事件的view,响应机制则是从下层向上层,响应机制主要依赖的方法是:
touchesBegin , touchesMoved, touchesEnded 等方法

截屏2020-04-08 上午9.52.00.png

响应链是通过nextResponder属性组成的一个链表。
点击的view有 superView,nextResponder就是superView;
view.nextResponder.nextResponder是viewController 或者是 view.superView. view
view.nextResponder.nextResponder.nextResponder是 UIWindow (非严谨,便于理解)
view.nextResponder.nextResponder.nextResponder. nextResponder是UIApplication、UIAppdelate、直到nil (非严谨,便于理解)
touch事件就是根据响应链的关系来层层调用(我们重写touch 要记得 super 调用,不然响应链会中断)。
比如我们监听self.view的touch事件,也是因为subviews的touch都在同一个响应链里。
要注意的是,UIControl, UIScrollView和手势都可以阻断响应链,如果不想被阻断,则可以重写hitTest:withEvent方法

你可能感兴趣的:(事件传递和响应机制)