iOS知识点整理-事件传递&响应链相关

237 阅读3分钟

相信各位同仁在工作中或多或少的都遇到过关于按钮点击范围太小,希望能放大点击范围诸如此类的产品需求,最简单最低级的方法例如添加透明superView响应target事件等方法也可以解决此类问题,但这样是明显的“治标不治本”且随着业务迭代容易出现未知的错误,基于事件传递的机制,iOS提供了相应API解决此类问题; 该方法可以改变控件响应的点击范围:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect bounds = self.bounds;
    bounds = CGRectInset(bounds, -10, -10);
//    CGRectContainsPoint(<#CGRect rect#>, <#CGPoint point#>) 判断点是否在矩形内
    return CGRectContainsPoint(bounds, point);
}

系统发生触摸事件的时候会从window到父控件到子控件一个个检测触摸点是否在其中,如果在其中,则返回YES,最后返回YES的子控件作为响应事件的控件。 我们只要重写这个方法,在其中判断,是否点击了我们想要的区域,是的话就返回YES,否则返回NO,这样就实现了自定义点击的有效区域了。注意,这边并没有改变按钮的形状,按钮还是矩形的按钮,只是改变了按钮中响应区域而已。

由此引申出的事件传递链条就是我们需要重点理解的知识了:

事件传递链条

  • 产生触摸事件时,keyWindow会在它的内容视图上调用
//此方法返回的就是处理此触摸事件最合适的view
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

来完成这个寻找过程:

hitTest:withEvent方法底层实现

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//    NSLog(@"%@--hitTest",[self class]);
//    return [super hitTest:point withEvent:event];
    
    
    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    
    // 2. 判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    
    // 3.从后往前遍历自己的子控件
    NSInteger count = self.subviews.count;
    
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        
        // 把当前控件上的坐标系转换成子控件上的坐标系
     CGPoint childP = [self convertPoint:point toView:childView];
        
       UIView *fitView = [childView hitTest:childP withEvent:event];
        
        
        if (fitView) { // 寻找到最合适的view
            return fitView;
        }
        
        
    }
    
    // 循环结束,表示没有比自己更合适的view
    return self;
    
}

  • ① hitTest:withEvent在内部首先会判断该视图是否能响应事件,如果不能响应,返回nil,表示该视图不响应此触摸事件;
  • ② 调用pointInside:withEvent:判断点击事件是否发生在当前视图范围内,方法返回NO,则return nil;
  • ③ 如果pointInside返回YES,则向当前视图的所有子视图发送hitTest:withEvent消息,所有子视图的遍历顺序是从顶层视图一直到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或全部子视图遍历完毕。 若第一次有子视图返回非空对象,则hitTest方法返回此对象,处理结束;若所有子视图都返回非,则hitTest方法返回该视图本身

事件传递中所有pointInside返回YES的view加上控制器、UIWindow、UIApplication构成了响应者链。

  • 事件传递是自下而上的(hitTest);
  • 响应者链是自上而下的(touchBegin等)

(本文约定,window上最外层的view称为“上”)

关于事件传递的详细理解参照这里