关键点: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进行事件队列处理。