前言
看关于这方面的文章基本没有能涉及到UIGestureRecognizers
相关的文章,因此决定写这样一篇文章。也是我的第一篇文章,如有什么不对请及时指正。
本文主要通过一些实际测试来便于大家理解。
正文
- IOKit.framework 为系统内核的库
- SpringBoard.app 相当于手机的桌面
- Source1 主要接收系统的消息
- Source0 - UIApplication - UIWindow
- 从window开始系统会调用
hitTest:withEvent:
和pointInside
来找到最优响应者,具体过程可参考下图
- 比如我们在self.view 上依次添加view1、view2、view3(3个view是同级关系),那么系统用
hitTest
以及pointInside
时会先从view3开始便利,如果pointInside
返回YES就继续遍历view3的subviews(如果view3没有子视图,那么会返回view3),如果pointInside
返回NO就开始遍历view2。反序遍历,最后一个添加的subview开始。也算是一种算法优化。后面会具体介绍hitTest
的内部实现和具体使用场景。
- 比如我们在self.view 上依次添加view1、view2、view3(3个view是同级关系),那么系统用
- UITouch会给gestureRecognizers和最优响应者也就是hitTestView发送消息
- 默认view会走其
touchBegan:withEvent:
等方法,当gestureRecognizers找到识别的gestureRecognizer后,将会独自占有该touch,即会调用其他gestureRecognizer和hitTest view的touchCancelled:withEvent:
方法,并且它们不再收到该touche事件,也就不会走响应链流程。下面会具体阐述UIContol和UIScrollView和其子类与手势之间的冲突和关系。
- 默认view会走其
- 当该事件响应完毕,主线程的Runloop开始睡眠,等待下一个事件。
1.hitTest:withEvent:和pointInside
1.1 hitTest:withEvent:和pointInside 演练
测试hitTest和pointInside执行过程
点击redView:GSGrayView *grayView = [[GSGrayView alloc] initWithFrame:CGRectMake(0, kTopHeight, kScreenWidth/2, 400)]; [self.view addSubview:grayView]; GSRedView *redView = [[GSRedView alloc] initWithFrame:CGRectMake(0, 0, grayView.bounds.size.width / 2, grayView.bounds.size.height / 3)]; [grayView addSubview:redView]; GSBlueView *blueView = [[GSBlueView alloc] initWithFrame:CGRectMake(grayView.bounds.size.width/2, grayView.bounds.size.height * 2/3, grayView.bounds.size.width/2, grayView.bounds.size.height/3)]; // blueView.userInteractionEnabled = NO; // blueView.hidden = YES; // blueView.alpha = 0.1;//0.0; [grayView addSubview:blueView]; GSYellowView *yellowView = [[GSYellowView alloc] initWithFrame:CGRectMake(CGRectGetMinX(grayView.frame), CGRectGetMaxY(grayView.frame) + 20, grayView.bounds.size.width, 100)]; [self.view addSubview:yellowView];
yellowView -> grayView -> blueView -> redView
- 当点击redView时,因为yellowView和grayView同级,yellowView比grayView后添加,所以先打印yellowView,由于触摸点不在yellowView中因此打印grayView,然后遍历grayView的subViews分别打印blueView和redView。
- 当hitTest返回nil时,也不会打印pointInside。因此可以得出pointInside是在hitTest后面执行的。
- 当view的userInteractionEnabled为NO、hidden为YES或alpha<=0.1时,也不会打印pointInside方法。因此可以推断出在hitTest方法内部会判断如果这些条件一个成立则会返回nil,也不会调用pointInside方法。
- 如果在grayView的hitTest返回[super hitTest:point event:event],则会执行gery.subviews的遍历(subviews 的 hitTest 与 pointInside),grayView的pointInside是判断触摸点是否在grayView的bounds内,grayView的hitTest是判断是否需要遍历他的subviews.
- pointInside只是在执行hitTest时,会在hitTest内部调用的一个方法。也就是说pointInside是hitTest的辅助方法。
hitTest是一个递归函数
1.2 hitTest:withEvent:内部实现代码还原
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"-----%@",self.nextResponder.class);
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) return nil;
//判断点在不在这个视图里
if ([self pointInside:point withEvent:event]) {
//在这个视图 遍历该视图的子视图
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
//转换坐标到子视图
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
//递归调用hitTest:withEvent继续判断
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
//在这里打印self.class可以看到递归返回的顺序。
return hitTestView;
}
}