iOS事件响应链传递的一些理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zyzxrj/article/details/53326482

最近公司分享会上有同事分享了事件响应链的一些细节和逻辑,借这个机会把我觉得要注意的点整理一下。

1、事件传递顺序

事件的传递顺序,我就不说什么从UIApplication开始下传了,这边只说说视图层的传递:


事件传递:父视图往子视图传递,这个图传递如下

点击B:A->B

点击D:A->C->D

怎么验证这个说法,最简单的,关闭父视图的userInteractionEnabled,这时候点击子视图无效,但是关闭子视图响应,父视图仍然可以做成响应

2、响应事件处理

在判断事件响应的过程中主要用到两个函数,决定哪个视图来响应事件,是通过不断递归调用View中的 - (UIView *)hitTest: withEvent: 方法和 -(BOOL)pointInside: withEvent: 方法来实现的

我们通过打印log来分析系统方法响应顺序,效果图代码如下:



RedView代码:
#import "RedView.h"

@implementation RedView

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self addLabel];
    }
    return self;
}
- (void)addLabel
{
    UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 20)];
    label.text = @"A View";
    [self addSubview:label];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"A_touchesBegan");
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    NSLog(@"A_touchesMoved");
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    NSLog(@"A_touchesEnded");
}


//返回最适合处理事件的视图,最好在父视图中指定子视图的响应
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"进入A_View---hitTest withEvent ---");
    UIView * view = [super hitTest:point withEvent:event];
    NSLog(@"离开A_View--- hitTest withEvent ---hitTestView:%@",view);
    return view;
}
//判断一个点是否落在自己的视图范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
    NSLog(@"A_view--- pointInside withEvent ---");
    BOOL isInside = [super pointInside:point withEvent:event];
    NSLog(@"A_view--- pointInside withEvent --- isInside:%d",isInside);
    return isInside;
}

@end
点击空白处,log如下:

点击A视图一次,log如下:


父视图通过调用自身pointInside方法判断点是否落在本视图上,落在本视图就调用其子类的hitTest方法来判断谁来响应该事件,假如我们在A视图上改一个跟B视图一样大小的C视图log如下:


由此我们可以大致得出hitTest代码如下:
//返回最适合处理事件的视图,最好在父视图中指定子视图的响应
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.userInteractionEnabled || !self.hidden || self.alpha <= 0.01) {
        return nil;
    }
    
    if ([self pointInside:point withEvent:event]) {
        
        for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
            CGPoint subPoint = [subView convertPoint:point fromView:self];
            
            UIView *bestView = [subView hitTest:subPoint withEvent:event];
            if (bestView) {
                return bestView;
            }
        }
        return self;
    }

    return nil;
}

3、重写这两个函数的作用

假如我们需要响应一个圆形区域的点击事件,我们可以通过重写该视图的pointInside方法如下:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    //首先调用父类的方法确定点击的区域确实在按钮的区域中
    BOOL res = [super pointInside:point withEvent:event];
    if (res) {
        //绘制一个圆形path
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:self.bounds];
        if ([path containsPoint:point]) {
            //如果在path区域内,返回YES
            return YES;
        }
        return NO;
    }
    return NO;
}
除此之外,我们还可以扩大、 屏蔽某视图的响应区域等等,大家自由发挥


4、视图不响应检查要点

Tips:有时候发现一个视图无法响应点击事件,可以检查下面几项

扫描二维码关注公众号,回复: 3024736 查看本文章

1、hidden = YES 视图被隐藏

2、userInteractionEnabled = NO 不接受响应事件

3、alpha <= 0.01,透明视图不接收响应事件

4、子视图超出父视图范围

5、需响应视图被其他视图盖住

6、是否重写了其父视图以及自身的hitTest方法

7、是否重写了其父视图以及自身的pointInside方法

猜你喜欢

转载自blog.csdn.net/zyzxrj/article/details/53326482