iOS 事件响应机制

之前去腾讯面试被问到事件响应机制相关的问题,那个时候还是什么都不懂得,去这种大公司面试一般都是抱着学习的态度取得,当然侥幸的心里还是有的,总会想着万一能行呢。结果吗。。。最起码学习到了很多东西,这波不亏。

iOS的事件响应必须是UIResponder对象及其子类,我们Command查看层级关系不难发现,UITextView,UILabel,UIButton等控件他们都是UIResponder的子类。这也是他们能够响应事件的基础。

当触摸事件发生时,首先系统通过 hitTest方法找到能最合适的view,所谓最合适的view 其实就是 触摸事件 他的触摸点是否在这个View上,一个点可能在多个叠加的视图上,所以系统会找到所有的view(点所在的view),方向是从底下往上,所以判断点的顺序就是父view->子view->子view的子view,这样遍历下去,配合pointconvert方法和pointinside方法去判断这个点是否在这个view上,在就遍历他的子view,直到不满足条件。

  • 方法:hitTest 和 pointInside 方法

  • 逻辑: 递归判断

    • 手势触摸,系统会判断触摸点是否在当前视图上
    • 如果在,执行hitTest方法。同级子视图遍历规则遵循filo(先进后出)原则
    • 找到了这个view 然后执行event,从上往下一次执行,看是否能够执行,不能够执行,找他的同级视图或父视图
  • 三种情况下是不会响应事件

    • userInteractionEnabled = NO
    • hidden = YES
    • alpha < 0.01
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //首先判断是否可以接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01){ 
        return nil
    };
    //然后判断点是否在当前视图上
    if ([self pointInside:point withEvent:event] == NO) {
        return nil;
    }
    //循环遍历所有子视图,查找是否有最合适的视图
    for (NSInteger i = self.subviews.count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        //转换点到子视图坐标系上
        CGPoint childPoint = [self convertPoint:point toView:childView];
        //递归查找是否存在最合适的view
        UIView *fitView = [childView hitTest:childPoint withEvent:event];
        //如果返回非空,说明子视图中找到了最合适的view,那么返回它
        if (fitView) {
            return fitView;
        }
    }
    //循环结束,仍旧没有合适的子视图可以处理事件,那么就认为自己是最合适的view
    return self;
}

在找到最合适的view之后,会调用view的touches方法对事件进行响应,如果没有重写view的touches方法,touches默认的做法是将事件沿着响应者链往上抛,交给下一个响应者对象。也就是说,touches方法默认不处理事件,只是将事件沿着响应者链往上传递

响应者链在递归查找最合适的view的时候形成,所找到的view将成为第一响应者,会调用它的touches方法来响应事件,touches方法默认的处理是将事件往上抛给下一个响应者,而如果下一个响应者的touches方法没有重写,事件会继续沿着响应者链往上走,一直到UIApplication,如果依旧不能处理事件那么事件就被丢弃。

  • UIView
    如果view是viewcontroller的根view,那么下一个响应者是viewcontroller,否则是super view

  • UIViewcontroller
    如果viewcontroller的view是window的根view,那么下一个响应者是window;如果viewcontroller是另一个viewcontroller模态推出的,那么下一个响应者是另一个viewcontroller;如果viewcontroller的view被add到另一个viewcontroller的根view上,那么下一个响应者是另一个viewcontroller的根view

  • UIWindow
    UIWindow的下一个响应者是UIApplication

  • UIApplication
    通常UIApplication是响应者链的顶端(如果app delegate也继承了UIResponder,事件还会继续传给app delegate)

实用案例,利用hitTest方法修改button按钮点击范围,点击范围扩大2倍

//重写button的hitTest方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    NSLog(@"====%@",NSStringFromCGPoint(point));
    CGRect rect = self.bounds;
    CGRect newRect = CGRectMake(-rect.size.width/2, -rect.size.height/2, rect.size.width*2, rect.size.height*2);
    if (CGRectContainsPoint(newRect, point)) {
        return self;
    }
    return nil;
}

参考文章

你可能感兴趣的:(iOS 事件响应机制)