事件传递

2020-08-12  本文已影响0人  KB_MORE
图片.png

比如我们在self.view 上依次添加view1、view2、view3(3个view是同级关系),那么系统用hitTest以及pointInside时会先从view3开始便利,如果pointInside返回YES就继续遍历view3的subviews(如果view3没有子视图,那么会返回view3),如果pointInside返回NO就开始遍历view2。反序遍历,最后一个添加的subview开始。也算是一种算法优化。后面会具体介绍hitTest的内部实现和具体使用场景。

hitTest:withEnvent-->内部调用pointInside:withEvent查找最顶层view--->view调用UITouch一系列方法--->gestureRecognizers找到合适Recognizer----->调用其他Recognizer和veiew的touchCancelled:withEvent方法 同时触发合适Recognizer的selector

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

普通button点击时间响应堆栈调用

 frame #0: 0x000000010db26367 demo-事件传递响应链`-[ViewController recClick](self=0x00007fbe3e406310, _cmd="recClick") at ViewController.m:57:5
    frame #1: 0x00007fff47c3a347 UIKitCore`-[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 44
    frame #2: 0x00007fff47c4333d UIKitCore`_UIGestureRecognizerSendTargetActions + 109
    frame #3: 0x00007fff47c409ea UIKitCore`_UIGestureRecognizerSendActions + 298
    frame #4: 0x00007fff47c3fd17 UIKitCore`-[UIGestureRecognizer _updateGestureForActiveEvents] + 757
    frame #5: 0x00007fff47c31eda UIKitCore`_UIGestureEnvironmentUpdate + 2706
    frame #6: 0x00007fff47c3140a UIKitCore`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 467
    frame #7: 0x00007fff47c3117f UIKitCore`-[UIGestureEnvironment _updateForEvent:window:] + 200
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
---------------------------------------------------------------------------------------------------
    frame #8: 0x00007fff480d04b0 UIKitCore`-[UIWindow sendEvent:] + 4574
    frame #9: 0x00007fff480ab53b UIKitCore`-[UIApplication sendEvent:] + 356
    frame #10: 0x00007fff4812c71a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 6847
    frame #11: 0x00007fff4812f1e0 UIKitCore`__handleEventQueueInternal + 5980
    frame #12: 0x00007fff23bd4471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #13: 0x00007fff23bd439c CoreFoundation`__CFRunLoopDoSource0 + 76
    frame #14: 0x00007fff23bd3b74 CoreFoundation`__CFRunLoopDoSources0 + 180
    frame #15: 0x00007fff23bce87f CoreFoundation`__CFRunLoopRun + 1263
    frame #16: 0x00007fff23bce066 CoreFoundation`CFRunLoopRunSpecific + 438
    frame #17: 0x00007fff384c0bb0 GraphicsServices`GSEventRunModal + 65
    frame #18: 0x00007fff48092d4d UIKitCore`UIApplicationMain + 1621
    frame #19: 0x000000010db2740d demo-事件传递响应链`main(argc=1, argv=0x00007ffee20d8c90) at main.m:18:12

添加手势点击事件响应堆栈调用


* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x00000001079563b7 demo-事件传递响应链`-[ViewController click:](self=0x00007fedf3d09a40, _cmd="click:", ben=0x00007fedf3c08b20) at ViewController.m:68:18
    frame #1: 0x00007fff48093fff UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 83
    frame #2: 0x00007fff47a6c00e UIKitCore`-[UIControl sendAction:to:forEvent:] + 223
    frame #3: 0x00007fff47a6c358 UIKitCore`-[UIControl _sendActionsForEvents:withEvent:] + 398
    frame #4: 0x00007fff47a6b2b7 UIKitCore`-[UIControl touchesEnded:withEvent:] + 481
    frame #5: 0x00007fff480cebbf UIKitCore`-[UIWindow _sendTouchesForEvent:] + 2604
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
---------------------------------------------------------------------------------------------------
    frame #6: 0x00007fff480d04c6 UIKitCore`-[UIWindow sendEvent:] + 4596
    frame #7: 0x00007fff480ab53b UIKitCore`-[UIApplication sendEvent:] + 356
    frame #8: 0x00007fff4812c71a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 6847
    frame #9: 0x00007fff4812f1e0 UIKitCore`__handleEventQueueInternal + 5980
    frame #10: 0x00007fff23bd4471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #11: 0x00007fff23bd439c CoreFoundation`__CFRunLoopDoSource0 + 76
    frame #12: 0x00007fff23bd3b74 CoreFoundation`__CFRunLoopDoSources0 + 180
    frame #13: 0x00007fff23bce87f CoreFoundation`__CFRunLoopRun + 1263
    frame #14: 0x00007fff23bce066 CoreFoundation`CFRunLoopRunSpecific + 438
    frame #15: 0x00007fff384c0bb0 GraphicsServices`GSEventRunModal + 65
    frame #16: 0x00007fff48092d4d UIKitCore`UIApplicationMain + 1621
    frame #17: 0x000000010795741d demo-事件传递响应链`main(argc=1, argv=0x00007ffee82a8c90) at main.m:18:12
    frame #18: 0x00007fff5227ec25 libdyld.dylib`start + 1

1.hitTest:withEvent:和pointInside

1.1 hitTest:withEvent:和pointInside 演练

1.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;
}

1.3 pointInside运用:增大热区范围

在开发过程中难免会遇到需要增大UIButton等的热区范围,假如UIButton的布局不允许修改,那么就需要用到pointInside来增大UIButton的点击热区范围。具体实现代码如下:

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

也就是说如果button的size小于200*200,则点击button相对中心位置上下左右各100的范围内即使超出button,也可以响应点击事件

2.响应链

2.1 响应链的组成

respondChain

还用上面那个栗子:
点击redView:
redview -> grayView -> viewController -> ...


image

因为只实现到controller的touches事件方法因此只打印到Controller。

2.2 UIControl阻断响应链

把上面栗子中的grayView替换成一个Button:

    GSExpandButton *expandButton = [[GSExpandButton alloc] initWithFrame:CGRectMake(0, kTopHeight, kScreenWidth/2, 400)];
    expandButton.backgroundColor = [UIColor lightGrayColor];
    [expandButton setTitle:@"点我啊" forState:UIControlStateNormal];
    [expandButton addTarget:self action:@selector(expandButtonClick:) forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:expandButton];

    self.redView = [[GSRedView alloc] initWithFrame:CGRectMake(0, 0, expandButton.bounds.size.width / 2, expandButton.bounds.size.height / 3)];
    [expandButton addSubview:self.redView];

    self.blueView = [[GSBlueView alloc] initWithFrame:CGRectMake(expandButton.bounds.size.width/2, expandButton.bounds.size.height * 2/3, expandButton.bounds.size.width/2, expandButton.bounds.size.height/3)];

    //    blueView.userInteractionEnabled = NO;
    //    blueView.hidden = YES;
    //    blueView.alpha = 0.1;//0.0;
    [expandButton addSubview:self.blueView];

    self.yellowView = [[GSYellowView alloc] initWithFrame:CGRectMake(CGRectGetMinX(expandButton.frame), CGRectGetMaxY(expandButton.frame) + 20, expandButton.bounds.size.width, 100)];
    [self.view addSubview:self.yellowView];

点击redView:
redview -> expandButton


image
上一篇 下一篇

猜你喜欢

热点阅读