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

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

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:有时候发现一个视图无法响应点击事件,可以检查下面几项

1、hidden = YES 视图被隐藏

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

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值