美文网首页
OC的事件传递及响应者链条

OC的事件传递及响应者链条

作者: 三国韩信 | 来源:发表于2020-07-21 21:16 被阅读0次

    当我们点击屏幕时,iOS怎么找到它,如何找到响应者的?
    比如下面这张图,我们点击了一下屏幕,iOS是怎么知道我们是点击了哪个View?红View、绿View、蓝View,系统怎么区分谁来相应的?

    image.png
    其实在发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理。通常,会先发送事件给应用程序的keyWindow,主窗口会在其视图层次结构中找到一个最合适的视图来处理触摸事件,这个找寻的过程就是事件传递。
    比如触摸事件的传递是从父控件传递到子控件:
    点击了绿色的view:UIApplication -> UIWindow -> 白色 -> 绿色
    点击了蓝色的view:UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色
    点击了红色的view:UIApplication -> UIWindow -> 白色 -> 橙色 -> 红色
    传递过程详解:

    keyWindow会在它的内容视图上调用hitTest:withEvent:(该方法返回的就是处理此触摸事件的最合适view)来完成这个找寻过程。
    hitTest:withEvent:在内部首先会判断该视图是否能响应触摸事件,如果不能响应,返回nil,表示该视图不响应此触摸事件。然后再调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内)。如果pointInside:withEvent:返回NO,那么hiteTest:withEvent:也直接返回nil。
    如果pointInside:withEvent:返回YES,则向当前视图的所有子视图发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历。直到有子视图返回非空对象或者全部子视图遍历完毕;若第一次有子视图返回非空对象,则 hitTest:withEvent:方法返回此对象,处理结束;如所有子视图都返回非,则hitTest:withEvent:方法返回该视图自身。
    简而言之主要是2个回调方法来寻找能相应事件的View
    1、- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event //这个方法是返回响应者,就是返回哪个View来处理你的点击。
    2、- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event //用于判断当前的view是否相应事件的,返回YES代表可以。

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        if (self.userInteractionEnabled
            && !self.hidden
            && [self pointInside:point withEvent:event]) {
            // 使用reverseObjectEnumerator进行倒序遍历
            for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
                CGPoint convertedPoint = [subview convertPoint:point fromView:self];
                UIView *responseView = [subview hitTest:convertedPoint withEvent:event];
                if (responseView) {
                    return responseView;
                }
            }
            return self;
        }
        return nil;
    }
    
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
        if (self.userInteractionEnabled
            && !self.hidden
            && CGRectContainsPoint(self.bounds, point)) {
            return YES;
        }
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            BOOL inside = [subview pointInside:convertedPoint withEvent:event];
            if (inside) {
                return YES;
            }
        }
        return NO;
    }
    
    相关概念

    响应者:继承UIResponder的对象称之为响应者对象,能够处理touchesBegan等触摸事件。
    响应者链条:由很多响应者链接在一起组合起来的一个链条称之为响应者链条。
    响应者链条其实还包括视图控制器、UIWindow和UIApplication,上述例子并没有表现出来。
    通过事件传递找到最合适的处理触摸事件的view后(就是最后一个pointInside返回YES的view,它是第一响应者),如果该view是控制器view,那么上一个响应者就是控制器。如果它不是控制器view,那么上一个响应者就是前面一个pointInside返回YES的view(其实就是它的父控件)。 最后这些所有pointInside返回YES的view加上它们的控制器、UIWindow和UIApplication共同构成响应者链条。响应者链条是自上而下的(我把window上最外面的那个view称为上),前面的事件传递是自下而上的。

    事件的响应

    经过以上的事件的传递过程,事件已经传递给系统认为最适合的View了。接下来就是处理这个事件。处理事件方法:
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ }
    但是最适合的,不一定就能一定能处理,如果,这个View不能处理这个事件则会将这个事件上抛,就是按照事件传递下来的路线上抛。

    • 不能处理将事件传递给其上级视图(View的superView);
    • 如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller;
    • 首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传递;
    • 一直到 window,如果window还是不能处理此事件则继续交给application处理;
    • 如果最后application还是不能处理此事件则将其丢弃;
      整个找事件相应的流程就是这样,最后没人处理会被丢弃掉。

    参考文章:
    OC事件传递响应链-原理篇
    iOS触摸事件响应链剖析

    相关文章

      网友评论

          本文标题:OC的事件传递及响应者链条

          本文链接:https://www.haomeiwen.com/subject/hjvwkktx.html