简介
本文的目的是让读者彻底了解,iOS系统是如何识别Touch事件,以及如何传递对应的事件到指定的View
iOS事件主要分为四种
- touch事件
- 运动事件
- 远程事件
- 按压事件
参见UIEvent
的type
属性
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches,
UIEventTypeMotion,
UIEventTypeRemoteControl,
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
};
这里只介绍第一种Touch事件
为了便于区分把
TouchEvent
的
touchesBegan:withEvent:
叫做Touch
,手势类型的叫Gesture
.
事件处理流程
- App(Demo
TSAplication
类,UIApplication
子类,代表当前应用)先通过hit-testing
找到touch View
. - 而后
APP
收到sendEvent:
事件,传递给Window(TSWindow
)的sendEvent:
,由TSWindow
分解成touchesBegan:withEvent:
,touchesMoved:withEvent
,touchesEnd:withEvent
,touchesCancel:withEvent
等事件。 - 具体的
手势识别
以及Target-Action
在TSWindow
的sendEvent:
收到Touch Event
状态的UITouchPhaseBegan
与UITouchPhaseEnded
|UITouchPhaseCancelled
之间进行。
解析UITapGestureRecognizer
UIGestureRecognizer
不入响应链
UIGestureRecognizer objects are not in the responder chain, yet observe touches hit-tested to their view and their view's subviews.
原理
触摸事件(Touch events
)主要用两步实现的
第一步:碰撞测试(hit-testing
)
要响应一个触摸事件,先要找到触摸的是哪个View,再根据
Next Response
来找到合适的处理方法
碰撞测试原理
-
hit-testing
通过UIView的hitTest:withEvent:
找到对应的触摸的View。 -
hitTest:withEvent:
内部会调用本类pointInside:withEvent:
,pointInside:withEvent:
返回bool值,来确定是否点击范围在本View范围内。 - 如果在,
倒序递归
调用所有subviews的同一过程
,只要找到最后一层
调用pointInside:withEvent:
返回YES
,没有subview或者subview的pointInside:withEvent:
返回为nil,则返回self,并逐级返回到顶层。 找到之后并不继续进行递归。
至于为什么
hitTest:withEvent:
在一个点击事件里调用两遍,还不知道为什么
举个例子
Demo截屏
当前controller叫
hit-testing
,controller对应的view叫AView
,AView
第一个子View叫BView
,BView
有一个唯一子ViewCView
,AView
第二个子View叫DView
,DView
没有子View。以此界面,来分析hit-testing
点击AView
时序图
时序图的步骤在一次touch调用了2遍,看日志,原因未知,没有查到资料。
日志
2016-12-08 11:00:07.546 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] Before
2016-12-08 11:00:07.547 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] Before
2016-12-08 11:00:07.547 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] After
2016-12-08 11:00:07.547 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] Before
2016-12-08 11:00:07.548 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] Before
2016-12-08 11:00:07.548 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] After
2016-12-08 11:00:07.548 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] Before
2016-12-08 11:00:07.548 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] Before
2016-12-08 11:00:07.549 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] After
2016-12-08 11:00:07.549 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] After
2016-12-08 11:00:07.549 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] Before
2016-12-08 11:00:07.549 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] Before
2016-12-08 11:00:07.550 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] After
2016-12-08 11:00:07.550 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] After
2016-12-08 11:00:07.550 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] After
2016-12-08 11:00:07.551 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] After
2016-12-08 11:00:07.551 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] Before
2016-12-08 11:00:07.551 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] Before
2016-12-08 11:00:07.551 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] After
2016-12-08 11:00:07.552 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] Before
2016-12-08 11:00:07.552 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] Before
2016-12-08 11:00:07.552 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] After
2016-12-08 11:00:07.552 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] Before
2016-12-08 11:00:07.553 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] Before
2016-12-08 11:00:07.553 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] After
2016-12-08 11:00:07.553 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] After
2016-12-08 11:00:07.553 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] Before
2016-12-08 11:00:07.553 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] Before
2016-12-08 11:00:07.554 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] After
2016-12-08 11:00:07.554 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] After
2016-12-08 11:00:07.554 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] After
2016-12-08 11:00:07.554 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] After
第一遍调试堆栈
第二遍调试堆栈
解析:
- 第一个收到
hitTest:withEvent:
肯定是TSWindow
,TSWindow
调用pointInside:withEvent:
返回YES,代表点击在TSWindows
上,倒序递归
遍历TSWindow
subviews。 - 只有一个子View
AView
,调用AView
的pointInside:withEvent:
返回YES,倒序递归
遍历AView
subviews. - 先遍历
DView
,调用DView
的pointInside:withEvent:
返回NO,DView
的hitTest:withEvent:
返回nil。 - 再遍历
BView
,,调用BView
的pointInside:withEvent:
返回NO,BView
的hitTest:withEvent:
返回nil。 -
AView
所有子View都返回nil了,就返回self
.
点击DView
时序图
日志
2016-12-08 14:30:34.886 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] Before
2016-12-08 14:30:34.886 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] Before
2016-12-08 14:30:34.886 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] After
2016-12-08 14:30:34.887 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] Before
2016-12-08 14:30:34.887 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] Before
2016-12-08 14:30:34.888 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] After
2016-12-08 14:30:34.888 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] Before
2016-12-08 14:30:34.888 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] Before
2016-12-08 14:30:34.888 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] After
2016-12-08 14:30:34.889 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] After
2016-12-08 14:30:34.889 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] After
2016-12-08 14:30:34.889 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] After
2016-12-08 14:30:34.890 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] Before
2016-12-08 14:30:34.890 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] Before
2016-12-08 14:30:34.890 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] After
2016-12-08 14:30:34.890 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] Before
2016-12-08 14:30:34.891 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] Before
2016-12-08 14:30:34.891 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] After
2016-12-08 14:30:34.891 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] Before
2016-12-08 14:30:34.891 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] Before
2016-12-08 14:30:34.892 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] After
2016-12-08 14:30:34.892 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] After
2016-12-08 14:30:34.892 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] After
2016-12-08 14:30:34.892 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] After
点击BView
时序图
日志
2016-12-08 15:19:16.611 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] Before
2016-12-08 15:19:16.612 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] Before
2016-12-08 15:19:16.612 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] After
2016-12-08 15:19:16.613 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] Before
2016-12-08 15:19:16.614 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] Before
2016-12-08 15:19:16.614 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] After
2016-12-08 15:19:16.615 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] Before
2016-12-08 15:19:16.615 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] Before
2016-12-08 15:19:16.616 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] After
2016-12-08 15:19:16.616 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] After
2016-12-08 15:19:16.617 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] Before
2016-12-08 15:19:16.617 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] Before
2016-12-08 15:19:16.617 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] After
2016-12-08 15:19:16.618 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:CView] Before
2016-12-08 15:19:16.618 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:CView] Before
2016-12-08 15:19:16.619 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:CView] After
2016-12-08 15:19:16.619 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:CView] After
2016-12-08 15:19:16.620 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] After
2016-12-08 15:19:16.620 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] After
2016-12-08 15:19:16.621 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] After
2016-12-08 15:19:16.621 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] Before
2016-12-08 15:19:16.622 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] Before
2016-12-08 15:19:16.622 TSEventDemo[35550:1352018] -[TSWindow pointInside:withEvent:] After
2016-12-08 15:19:16.623 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] Before
2016-12-08 15:19:16.623 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] Before
2016-12-08 15:19:16.623 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:AView(MainView)] After
2016-12-08 15:19:16.624 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] Before
2016-12-08 15:19:16.624 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] Before
2016-12-08 15:19:16.625 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:DView] After
2016-12-08 15:19:16.625 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:DView] After
2016-12-08 15:19:16.625 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] Before
2016-12-08 15:19:16.625 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] Before
2016-12-08 15:19:16.626 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:BView] After
2016-12-08 15:19:16.626 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:CView] Before
2016-12-08 15:19:16.626 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:CView] Before
2016-12-08 15:19:16.627 TSEventDemo[35550:1352018] -[TSView pointInside:withEvent:] [name:CView] After
2016-12-08 15:19:16.627 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:CView] After
2016-12-08 15:19:16.628 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:BView] After
2016-12-08 15:19:16.628 TSEventDemo[35550:1352018] -[TSView hitTest:withEvent:] [name:AView(MainView)] After
2016-12-08 15:19:16.628 TSEventDemo[35550:1352018] -[TSWindow hitTest:withEvent:] After
解析
不再解析,总结成一句话:
倒序递归
subview,直到某个子ViewpointInside:withEvent:
返回YES,没有subview或者subviews的hitTest:withEvent:
返回nil,则返回self
.
点击CView
用提供的Demo,自验看日志,即可。
模拟代码
发现用文字流程图对程序员来说,还没代码更直观。故附上模拟代码
伪代码
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self _isAnimatedUserInteractionEnabled]) {
return nil;
} else {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
if (hitView) {
return hitView;
}
}
return self;
}
}
Demo代码
放在第二篇