iOS 事件传递和事件响应

关键点:hitTest:withEvent方法的底层实现

//point是该试图的坐标系上的点
-(UIView *)hitTest:(CGpoint)point withEvent:(UIEvent *)event {
  //判断自己是否接受触摸事件
  if(self.userInteractionEnable = NO || self.hidden = YES || self.alpha <= 0.01) return nil;
  //判断触摸点在不在自己的范围内
  if(![self pointInside:point withEvent:event]) return nil;
  int count = self.subviews.count;
  //从后往前便利自己的控件,看是否有子空间更适合响应此事件
  for(int i = count - 1;i >=0;i--){
        UIView *childView  = self.subviews[i];
        CGPoint childPoint  = [self convertPoint:point toView:childView];
        UIView *fitView        = [childView hitTest:childPoint   withEvent:event];

          if(fitView) {
              return fitView;
          }
  }
   //没有找到比自己更合适的view
   return self;
}
  • 总结几个点

1、在hitTest:消息分发过程中,并不是所有包含了触摸点的view都会经历事件传递,只有pointInside返回YES的view才属于响应者链条。
2、响应者是什么?继承了UIResponder的对象称为响应者对象,能够处理touchesBegan等事件。很多响应者链接在一起的组合的一个链条称为响应者链条。
3、重写hitTest和pointInside可以做到好多功能。

使用案例

1、例如不想让某个视图响应事件,只需要重写pointInside:withEvent:方法,让此方法返回NO就行了。
2、隔层透传
3、扩大点击区域

2、响应者链条使用案例

  • 使用响应者链条找到当前view所属的控制器
      - (UIViewController *)parentController {
          UIResponder *responder = [self nextResponder];
           while(responder){
                  if([responder isKindofClass:[UIViewController class]]) {
                        return (UIViewController *)responder;
                   }
                   responder = [responder nextResponder];
            }
            return nil;
       }

3、nextResponder

链条:
subview->view->viewController->window->application->AppDelegate->nil

4、系统事件source0?那它是如何唤醒mainThread的runloop的呢?

  • 1、用户触发事件, IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收,SpringBoard会利用mach port,产生source1,来唤醒目标APP的com.apple.uikit.eventfetch-thread的RunLoop。
  • 2、 Eventfetch thread会将main runloop 中__handleEventQueue所对应的source0设置为signalled == Yes状态,同时唤醒main RunLoop。mainRunLoop则调用__handleEventQueue进行事件队列处理。

你可能感兴趣的:(iOS 事件传递和事件响应)