美文网首页
iOS事件传递及响应链

iOS事件传递及响应链

作者: limeng99 | 来源:发表于2020-02-26 15:57 被阅读0次

    iOS的应用中,用户与App进行交互,会产生很多事件,这些事件是如何产生,响应的链条又是怎样传递的呢,下面将会一一解答以上的问题。

    一. 事件传递&响应流程

    事件传递&响应详情图:

    event-process.png
    • IOKit.framework 为系统内核的库

    • SpringBoard.app 相当于手机的桌面

    • Source1 主要接收系统的消息

    • Source0 - UIApplication -UIWindow

    • 从window开始系统会调用hitTest:withEvent:pointInside来找到最优响应者,具体过程可参考下图:

    event-process1
    • 比如我们在self.view上依次添加view1view2view3 (3个view是同级关系),那么系统用-hitTest:withEvent:以及-pointInside:withEvent:时会先从view3开始便利,如果-pointInside:withEvent:返回YES就继续遍历view3subviews (如果view3没有子视图,那么会返回view3),如果-pointInside:withEvent:返回NO就开始遍历view2。反序遍历,最后一个添加的subview开始。也算是一种算法优化。

    • UITouch会给gestureRecognizers和最优响应者也就是hitTestView发送消息

    • 默认view会走其touchBegan:withEvent:等方法,当gestureRecognizers找到识别的gestureRecognizer后,将会独自占有该touch,即会调用其他gestureRecognizerhitTestViewtouchCancelled:withEvent:方法,并且它们不再收到该touche事件,也就不会走响应链流程。下面会具体阐述UIContolUIScrollView和其子类与手势之间的冲突和关系。

    • 当该事件响应完毕,主线程的Runloop开始睡眠,等待下一个事件。

    二. 事件传递

    2.1 hitTest:withEvent:和pointInside:withEvent:

    示例图:

    event-test

    示例代码:

    LMGrayView *grayView = [[LMGrayView alloc] initWithFrame:CGRectMake(100, 200, kScreenWidth/2, 400)];
    grayView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:grayView];
    
    LMRedView *redView = [[LMRedView alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3)];
    redView.backgroundColor = [UIColor redColor];
    [grayView addSubview:redView];
    
    LMBlueView *blueView = [[LMBlueView alloc] initWithFrame:CGRectMake(grayView.bounds.size.width/2, grayView.bounds.size.height * 2/3, grayView.bounds.size.width/2, grayView.bounds.size.height/3)];
    blueView.backgroundColor = [UIColor blueColor];
    // blueView.userInteractionEnabled = NO;
    // blueView.hidden = YES;
    // blueView.alpha = 0.1;
    [grayView addSubview:blueView];
    
    LMYellowView *yellowView = [[LMYellowView alloc] initWithFrame:CGRectMake(CGRectGetMinX(grayView.frame), CGRectGetMaxY(grayView.frame) + 20, grayView.bounds.size.width, 100)];
    yellowView.backgroundColor = [UIColor yellowColor];
    [self.view addSubview:yellowView];
    

    LMGrayView中的代码:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        NSLog(@"method: %s", __PRETTY_FUNCTION__);
        return [super hitTest:point withEvent:event];
    }
    
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
        NSLog(@"method: %s", __PRETTY_FUNCTION__);
        return [super pointInside:point withEvent:event];
    }
    

    点击redView,打印日志:

    2019-12-30 14:13:37.206432+0800 Event-Demo[39601:999578] method: -[LMYellowView hitTest:withEvent:]
    2019-12-30 14:13:37.206691+0800 Event-Demo[39601:999578] method: -[LMYellowView pointInside:withEvent:]
    2019-12-30 14:13:37.206888+0800 Event-Demo[39601:999578] method: -[LMGrayView hitTest:withEvent:]
    2019-12-30 14:13:37.207052+0800 Event-Demo[39601:999578] method: -[LMGrayView pointInside:withEvent:]
    2019-12-30 14:13:37.207243+0800 Event-Demo[39601:999578] method: -[LMBlueView hitTest:withEvent:]
    2019-12-30 14:13:37.207425+0800 Event-Demo[39601:999578] method: -[LMBlueView pointInside:withEvent:]
    2019-12-30 14:13:37.207581+0800 Event-Demo[39601:999578] method: -[LMRedView hitTest:withEvent:]
    2019-12-30 14:13:37.207730+0800 Event-Demo[39601:999578] method: -[LMRedView pointInside:withEvent:]
    2019-12-30 14:13:37.208041+0800 Event-Demo[39601:999578] method: -[LMYellowView hitTest:withEvent:]
    2019-12-30 14:13:37.208186+0800 Event-Demo[39601:999578] method: -[LMYellowView pointInside:withEvent:]
    2019-12-30 14:13:37.208336+0800 Event-Demo[39601:999578] method: -[LMGrayView hitTest:withEvent:]
    2019-12-30 14:13:37.208502+0800 Event-Demo[39601:999578] method: -[LMGrayView pointInside:withEvent:]
    2019-12-30 14:13:37.208863+0800 Event-Demo[39601:999578] method: -[LMBlueView hitTest:withEvent:]
    2019-12-30 14:13:37.209127+0800 Event-Demo[39601:999578] method: -[LMBlueView pointInside:withEvent:]
    2019-12-30 14:13:37.209373+0800 Event-Demo[39601:999578] method: -[LMRedView hitTest:withEvent:]
    2019-12-30 14:13:37.225252+0800 Event-Demo[39601:999578] method: -[LMRedView pointInside:withEvent:]
    

    经测试可得知:

    • -hitTest:withEvent:方法内部会调用 -pointInside:withEvent: 方法。
    • 一次点击会触发两次-hitTest:withEvent: 方法。
    • -pointInside:withEvent: 方法会判断触摸点是否在当前视图范围内。
    • -hitTest:withEvent: 方法根据-pointInside:withEvent: 的返回值决定是否返回当前视图。
    • 调用顺序是,如果跨级,就从父视图逐级向其子视图遍历调用;如果同级,就按照同级子视图添加到其父视图上的顺序,从后向前遍历,即后添加的先遍历,先添加的后遍历的 FILO模式。
    • 如果父视图的-pointInside:withEvent: 方法返回 NO,就不会遍历其子视图;如果返回 YES,就会遍历其子视图,即调用其子视图的-hitTest:withEvent: 方法。
    • 如果一个子视图的-hitTest:withEvent:方法返回值不为 nil,那么就停止遍历,不会继续调用尚未遍历过的同级子视图的-hitTest:withEvent:方法。
    • -hitTest:withEvent:方法返回的视图决定最后响应事件的对象。

    总结:在默认情况下,即不重写上述两个方法的情况下,当点击屏幕时,会从最底层的父视图开始向上逐级遍历,直到最顶层的子视图。如果发现触摸点在当前视图范围内,就会遍历当前视图的子视图,检测触点是否在其子视图范围内,如果在子视图范围内就检测该子视图的子视图,重复这个过程直到找到一个视图它没有子视图满足触摸点在其范围内,换句话说就是找到一个触点在其范围内的最顶层的视图。

    2.2 hitTest:withEvent:内部实现代码还原

    - (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;
    }
    

    2.3 hitTest:withEvent:应用实例

    1、扩大Button的点击区域

    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
        if (CGRectContainsPoint(CGRectInset(self.bounds, -20, -20), point)) {
            return YES;
        }
        return NO;
    }
    

    2、子view超出了父viewbounds响应事件

    第一种方法:
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
        BOOL flag = NO;
        for (UIView *view in self.subviews) {
            if (CGRectContainsPoint(view.frame, point)){
                flag = YES;
                break;
            }
        }
        return flag;
    }
    
    第二种方法:
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        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];
                UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
                if (hitTestView) {
                    return hitTestView;
                }
            }
            return self;
        }
        return nil;
    }
    

    3、如果Button被一个View遮住,在触摸View时,希望该Button能够响应事件

    in View.m里,第一种方法:
    击View及View的非交互子View(例如UIImageView),则该Button可以响应事件
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
        BOOL next = YES;
        for (UIView *view in self.subviews) {
            if ([view isKindOfClass:[UIControl class]]) {
                if (CGRectContainsPoint(view.frame, point)){
                    next = NO;
                    break;
                }
            }
        }
        return !next;
    }
    
    in View.m里,第二种方法:
    点击View本身Button会响应该事件,点击View的任何一个子View,Button不会响应事件
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *view = [super hitTest:point withEvent:event];
        if (view == self) {
            return nil;
        }
        return view;
    }
    

    4、ScrollView page滑动

    in scrollView.m,第一种方法:
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
        if (CGRectContainsPoint(CGRectInset(self.bounds, -40, 0), point)) {
            return YES;
        }
        return NO;
    }
    
    in scrollView.superView.m,第二种方法:
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *hitTestView = [super hitTest:point withEvent:event];
        if (hitTestView) {
            hitTestView = self.scrollView;
        }
        return hitTestView;
    }
    

    三. 事件响应

    3.1 响应链的组成

    event-response

    之前示例代码,LMGrayView中的代码:

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"method: %s", __PRETTY_FUNCTION__);
        [super touchesBegan:touches withEvent:event];
    }
    
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"method: %s", __PRETTY_FUNCTION__);
        [super touchesEnded:touches withEvent:event];
    }
    

    点击redView,打印日志:

    2019-12-30 15:17:02.486239+0800 Event-Demo[39956:1042564] method: -[LMRedView touchesBegan:withEvent:]
    2019-12-30 15:17:02.486401+0800 Event-Demo[39956:1042564] method: -[LMGrayView touchesBegan:withEvent:]
    2019-12-30 15:17:02.486529+0800 Event-Demo[39956:1042564] method: -[ViewController touchesBegan:withEvent:]
    2019-12-30 15:17:02.486969+0800 Event-Demo[39956:1042564] method: -[LMRedView touchesEnded:withEvent:]
    2019-12-30 15:17:02.487088+0800 Event-Demo[39956:1042564] method: -[LMGrayView touchesEnded:withEvent:]
    2019-12-30 15:17:02.487198+0800 Event-Demo[39956:1042564] method: -[ViewController touchesEnded:withEvent:]
    

    因为只实现到controllertouches事件方法因此只打印到controller

    经测试可得知:

    • 响应链是通过nextResponder属性组成的一个链表。

    • 在事件的响应中,如果某个控件实现了touches...方法,则这个事件将由该控件来接受,如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches….方法。

    • 如果当前view是控制器的view,那么控制器就上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图。

    • 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理。

    • 如果window对象也不处理,则其将事件或消息传递给UIApplication对象。

    • 如果UIApplication也不能处理该事件或消息,则将其丢弃。

    3.2 UIControl与UIScrollView阻断响应链

    将实例中的grayView替换成button或scrollView

    LMButton *button = [[LMButton alloc] initWithFrame:CGRectMake(100, 200, kScreenWidth/2, 400)];
    button.backgroundColor = [UIColor grayColor];
    [self.view addSubview:button];
    
    // LMScrollView *scrollView = [[LMScrollView alloc] initWithFrame:CGRectMake(100, 200, kScreenWidth/2, 400)];
    // scrollView.backgroundColor = [UIColor grayColor];
    // [self.view addSubview:scrollView];
    
    LMRedView *redView = [[LMRedView alloc] initWithFrame:CGRectMake(0, 0, button.bounds.size.width / 2, button.bounds.size.height / 3)];
    redView.backgroundColor = [UIColor redColor];
    [button addSubview:redView];
    

    点击redView,打印日志:

    button时:
    2019-12-30 15:43:52.429658+0800 Event-Demo[40130:1060351] method: -[LMRedView touchesBegan:withEvent:]
    2019-12-30 15:43:52.429797+0800 Event-Demo[40130:1060351] method: -[LMButton touchesBegan:withEvent:]
    2019-12-30 15:43:52.434574+0800 Event-Demo[40130:1060351] method: -[LMRedView touchesEnded:withEvent:]
    2019-12-30 15:43:52.434771+0800 Event-Demo[40130:1060351] method: -[LMButton touchesEnded:withEvent:]
    
    scrollView时:
    2019-12-30 15:47:26.362262+0800 Event-Demo[40167:1063630] method: -[LMRedView touchesBegan:withEvent:]
    2019-12-30 15:47:26.362456+0800 Event-Demo[40167:1063630] method: -[LMScrollView touchesBegan:withEvent:]
    2019-12-30 15:47:26.362628+0800 Event-Demo[40167:1063630] method: -[LMRedView touchesEnded:withEvent:]
    2019-12-30 15:47:26.362793+0800 Event-Demo[40167:1063630] method: -[LMScrollView touchesEnded:withEvent:]
    

    经测试可得知:

    • UIControlUIScrollView会阻断响应链,也就是说在响应UIControlUIScrollView自身对touch的处理方式并不会调用nextResponder对应的方法。

    • 通过在Button子类中重写touches的方法,发现如果不调用supertouches对应的方法则不会响应点击事件。由此可以大致推断出UIControl其子类响应点击原理大致为:根据添加target:action:时设置的UIControlEvents,在touches的合适方法调用target的action方法。

    • 通过重写tableView子类touches方法,发现如果不调用supertouches对应的方法则不会走tableview:didSelectRowAtIndexPath:方法。由此可以大致推断出UIScrollView其子类是在其touches方法中处理点击事件的。

    四. 手势Gesture

    4.1 手势Gesture与Touch事件的关系

    将示例中的grayView/redView/blueView添加手势:

    LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
    [grayView addGestureRecognizer:grayGesture];
    
    LMRedGestureRecognizer *redGesture = [[LMRedGestureRecognizer alloc] initWithTarget:self action:@selector(redViewClick:)];
    [redView addGestureRecognizer:redGesture];
    
    LMBlueGestureRecognizer *blueGesture = [[LMBlueGestureRecognizer alloc] initWithTarget:self action:@selector(blueViewClick:)];
    [blueView addGestureRecognizer:blueGesture];
    

    点击redView,打印日志:

    2019-12-30 16:18:28.629710+0800 Event-Demo[40396:1087484] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
    2019-12-30 16:18:28.629952+0800 Event-Demo[40396:1087484] method: -[LMRedGestureRecognizer touchesBegan:withEvent:]
    2019-12-30 16:18:28.630483+0800 Event-Demo[40396:1087484] method: -[LMRedView touchesBegan:withEvent:]
    2019-12-30 16:18:28.630679+0800 Event-Demo[40396:1087484] method: -[LMGrayView touchesBegan:withEvent:]
    2019-12-30 16:18:28.630829+0800 Event-Demo[40396:1087484] method: -[ViewController touchesBegan:withEvent:]
    2019-12-30 16:18:28.666787+0800 Event-Demo[40396:1087484] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
    2019-12-30 16:18:28.667075+0800 Event-Demo[40396:1087484] method: -[LMRedGestureRecognizer touchesEnded:withEvent:]
    2019-12-30 16:18:28.667517+0800 Event-Demo[40396:1087484] method: -[ViewController redViewClick:]
    2019-12-30 16:18:28.667757+0800 Event-Demo[40396:1087484] method: -[LMRedView touchesCancelled:withEvent:]
    2019-12-30 16:18:28.667937+0800 Event-Demo[40396:1087484] method: -[LMGrayView touchesCancelled:withEvent:]
    2019-12-30 16:18:28.668081+0800 Event-Demo[40396:1087484] method: -[ViewController touchesCancelled:withEvent:]
    

    经测试可得知:

    • 当通过hitTestpointInside找到最优响应者后,会给gestureRecognizers和相应的view同时发送touchBegin消息,如果找到合适gestureRecognizer则会独有该touches,即调用view的touheCancel消息,接着有gestreRecognizer来响应事件。

    • cancelsTouchesInView:默认为YES。表示当手势识别成功后,取消最佳响应者对象对于事件的响应,并不再向最佳响应者发送事件。若设置为NO,则表示在手势识别器识别成功后仍然向最佳响应者发送事件,最佳响应者仍响应事件。

    • delaysTouchesBegan:默认为NO,即在手势识别器识别手势期间,触摸对象状态发生变化时,都会发送给最佳响应者,若设置成YES,则在识别手势期间,触摸状态发生变化时不会发送给最佳响应者。

    • delaysTouchesEnded:默认为NO。默认情况下当手势识别器未能识别手势时,若此时触摸已经结束,则会立即通知Application发送状态为end的touch事件给最佳响应者以调用touchesEnded:withEvent:结束事件响应;若设置为YES,则会在手势识别失败时,延迟一小段时间(0.15s)再调用响应者的 touchesEnded:withEvent:

    4.2 手势Gesture和UIControl的关系

    将示例中的grayView添加手势,redView替换成button

    LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
    [grayView addGestureRecognizer:grayGesture];
    
    LMButton *button = [[LMButton alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3)];
    button.backgroundColor = [UIColor redColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [grayView addSubview:button];
    

    点击button,打印日志:

    2019-12-30 16:22:43.255640+0800 Event-Demo[40421:1091112] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
    2019-12-30 16:22:43.256213+0800 Event-Demo[40421:1091112] method: -[LMButton touchesBegan:withEvent:]
    2019-12-30 16:22:43.261559+0800 Event-Demo[40421:1091112] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
    2019-12-30 16:22:43.261907+0800 Event-Demo[40421:1091112] method: -[LMButton touchesEnded:withEvent:]
    2019-12-30 16:22:43.262199+0800 Event-Demo[40421:1091112] method: -[ViewController buttonClick:]
    

    有手势的grayView上增加一个buttonbutton上增加一个blueView

    LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
    [grayView addGestureRecognizer:grayGesture];
    
    LMButton *button = [[LMButton alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3)];
    button.backgroundColor = [UIColor redColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [grayView addSubview:button];
    
    LMBlueView *blueView = [[LMBlueView alloc] initWithFrame:CGRectMake(0, 0, button.bounds.size.width/2, button.bounds.size.height/2)];
    blueView.backgroundColor = [UIColor blueColor];
    [button addSubview:blueView];
    

    点击blueView,打印日志:

    2019-12-30 16:28:05.892085+0800 Event-Demo[40457:1095228] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
    2019-12-30 16:28:05.892806+0800 Event-Demo[40457:1095228] method: -[LMBlueView touchesBegan:withEvent:]
    2019-12-30 16:28:05.899176+0800 Event-Demo[40457:1095228] method: -[LMButton touchesBegan:withEvent:]
    2019-12-30 16:28:05.899547+0800 Event-Demo[40457:1095228] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
    2019-12-30 16:28:05.899825+0800 Event-Demo[40457:1095228] method: -[ViewController grayViewClick:]
    2019-12-30 16:28:05.899964+0800 Event-Demo[40457:1095228] method: -[LMBlueView touchesCancelled:withEvent:]
    2019-12-30 16:28:05.900087+0800 Event-Demo[40457:1095228] method: -[LMButton touchesCancelled:withEvent:]
    

    经测试最终得知:

    • UIControl及其子类能够执行点击事件而不是走底层的手势的原因为:在识别到相应的gestureRecognizer后如果当前的最优响应者是UIControl及其子类并且当前的gestureRecognizer不是UIContol上的手势,则会响应UIControltarget:action:的方法。否则则会响应gestureRecognizertarget:action:的方法。

    4.3 手势Gesture和UIScrollview的关系

    将示例中的grayView添加手势,redView替换成tableView

    LMGrayGestureRecognizer *grayGesture = [[LMGrayGestureRecognizer alloc] initWithTarget:self action:@selector(grayViewClick:)];
    [grayView addGestureRecognizer:grayGesture];
    
    LMTableView *tableView = [[LMTableView alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3) style:UITableViewStylePlain];
    tableView.backgroundColor = [UIColor redColor];
    tableView.dataSource = self;
    tableView.delegate = self;
    [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:NSStringFromClass([UITableViewCell class])];
    [grayView addSubview:tableView];
    

    点击cell,打印日志:

    无手势:
    2019-12-30 16:40:55.246500+0800 Event-Demo[40548:1105091] method: -[LMTableView touchesBegan:withEvent:]
    2019-12-30 16:40:55.247270+0800 Event-Demo[40548:1105091] method: -[LMTableView touchesEnded:withEvent:]
    2019-12-30 16:40:55.248467+0800 Event-Demo[40548:1105091] method: -[ViewController tableView:didSelectRowAtIndexPath:]
    
    有手势:
    2019-12-30 16:38:34.628235+0800 Event-Demo[40526:1103012] method: -[LMGrayView pointInside:withEvent:]
    2019-12-30 16:38:34.629412+0800 Event-Demo[40526:1103012] method: -[LMGrayGestureRecognizer touchesBegan:withEvent:]
    2019-12-30 16:38:34.682632+0800 Event-Demo[40526:1103012] method: -[LMGrayGestureRecognizer touchesEnded:withEvent:]
    2019-12-30 16:38:34.683721+0800 Event-Demo[40526:1103012] method: -[ViewController grayViewClick:]
    

    经测试最终得知:

    • UIScrollView为最优响应者并且父控件没有手势时UIScrollView才可以自己处理点击事件。否则被父控件的gestureRecognizer占有。
    • 当父控件有手势时UIScrollViewtouches方法都不执行,类似于设置delaysTouchesBeganYES
    • 虽然UIScrollView及其子类和UIControl及其子类类似都可以阻断响应链,但是当UIScrollView及其子类为最优响应者时,如果父控件中有gestureRecognizer依然会被其占有。

    4.4 UIScrollView点击穿透解决方案

    UIScrollView为最优响应者父控件有手势时,UIScrollView及其子类的点击代理方法和touchesBegan方法不响应。

    解决的三种办法:

    • 可以通过给父控件手势设置cancelsTouchesInViewNO,则会同时响应gestureRecognizer的事件和UIScrollView及其子类的代理方法和touches事件。

    • 在父控件中的手势的代理方法里面做一下判断,当touch.view是我们需要触发的view的时候,return NO ,这样就不会走手势方法,而去触发这个touch.view这个对象的方法了。

      - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
          if ([NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"]){
              return NO;
          }
          return YES;
      }
      
    • 可以通过给UIScrollView及其子类添加gestureRecognizer,从而来调用需要处理的事情。


    本文首发于我的个人博客 https://limeng99.club/,转载请标明出处。

    相关文章

      网友评论

          本文标题:iOS事件传递及响应链

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