美文网首页
iOS 事件响应链

iOS 事件响应链

作者: 磊Se | 来源:发表于2021-03-18 16:46 被阅读0次

    iOS中三种事件类型

    • 触屏事件(Touch Event)
    • 运动事件(Motion Event)
    • 远端控制事件(Remote-Control-Event)

    响应者对象(Responder Object)

    响应者对象指的是有响应和处理上述3种事件能力的对象。响应者链就是由一系列响应者对象构成一个层次结构。
    UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、UIWindow、UIViewController、UIView都直接或间接继承自UIResponder,所以它们都是可以构成响应者链的响应者对象。

    响应链的传递

    盗图真香

    这张图清晰的解释了响应链的传递过程:

    • 当发生触屏事件后,系统会将事件加到UIApplication管理的一个任务队列中,并将事件分发下去。
    • 通常先发送给keyWindowUIWindow继续在其视图层次结构中找到一个最合适的视图来处理事件。
    • UIWindow会在它视图上调用hitTest:withEvent:方法,hitTest:withEvent又会调用自身的pointInside:方法,若返回YES,说明点击区域在UIWindow范围内,然后UIWindow遍历它子视图(后添加的子视图先遍历)调用hitTest:WithEvent:方法。
    • 上图UIWindow遍历子视图MainViewMainView调用自身hitTest:withEvent方法,且pointInside:方法返回YES,继续遍历子视图ViewC
    • ViewC调用自身hitTest:withEvent:方法,结果发现pointInside:方法返回NO,hitTest:方法返回nil;轮到ViewB
    • ViewB调用自身hitTest:withEvent:方法,结果发现pointInside:方法返回YES,继续遍历子视图ViewB.2 ViewB.1
    • 遍历到ViewB.1无子视图可以遍历,遍历终止,hitTest:方法中返回自身即ViewB.1
    • 到此响应链结束,ViewB.1响应了事件。

    模拟系统的调用过程

    - (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]) {
                // 将像素point从view中转换到当前视图中,返回在当前视图中的像素值
                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 (CGRectContainsPoint(self.bounds, point)) {
            return YES;
        }
        return NO;
    }
    

    解决实际问题

    1、响应超出父视图外区域的事件

    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
        if (CGRectContainsPoint(self.bounds, point)) {
            return YES;
        }
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            // 将像素point从view中转换到当前视图中,返回在当前视图中的像素值
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            BOOL inside = [subview pointInside:convertedPoint withEvent:event];
            if (inside) {
                return YES;
            }
        }
        return NO;
    }
    

    2、面试题superView上添加viewA,viewA上添加viewB,viewB上添加viewC,且B、C都不在各自视图内。此时重写viewB的pointInside:方法并返回YES,点击A和点击B分别响应哪个视图的事件。

        [viewSuper addSubview:viewA];
        [viewA addSubview:viewB];
        [viewB addSubview:viewC];
    
        UITapGestureRecognizer *tapA = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapA:)];
        [viewA addGestureRecognizer:tapA];
        UITapGestureRecognizer *tapB = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapB:)];
        [viewB addGestureRecognizer:tapB];
        UITapGestureRecognizer *tapC = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapC:)];
        [viewC addGestureRecognizer:tapC];
        UITapGestureRecognizer *tapS = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapS:)];
        [viewSuper addGestureRecognizer:tapS];
        - (void)tapA:(UITapGestureRecognizer *)tap {
          NSLog(@"tapA");
        }
    
        - (void)tapB:(UITapGestureRecognizer *)tap {
          NSLog(@"tapB");
        }
    
        - (void)tapC:(UITapGestureRecognizer *)tap {
          NSLog(@"tapC");
        }
    
        - (void)tapS:(UITapGestureRecognizer *)tap {
          NSLog(@"tapSuper");
        }
      
    
    1616056611363.jpg

    答:点击A,响应事件B,打印tapB
    解:

    • 先viewSuper调用hitTest:方法并且pointInside:返回YES;
    • 遍历子视图ViewA,ViewA调用hitTest:并且点在范围内pointInside:返回YES;
    • 遍历子视图ViewB,ViewB调用hitTest:虽然点不在范围内,但pointInside:返回YES;
    • 接着遍历ViewC,点击的点不在ViewC范围内 pointInside:返回NO;
    • ViewB的hitTest:返回自身;所以响应了事件B;
      答:点击B,响应事件Super,打印tapSuper
      解:
    • 先viewSuper调用hitTest:方法并且pointInside:返回YES;
    • 遍历子视图ViewA,因为ViewB上的点不在ViewA范围内,所以pointInside:返回NO;
    • viewSuper的hitTest:返回自身;所以响应了事件Super;

    相关文章

      网友评论

          本文标题:iOS 事件响应链

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