簡(jiǎn)介
本文的目的是讓讀者徹底了解,iOS系統(tǒng)是如何識(shí)別Touch事件,以及如何傳遞對(duì)應(yīng)的事件到指定的View
iOS事件主要分為四種
- touch事件
- 運(yùn)動(dòng)事件
- 遠(yuǎn)程事件
- 按壓事件
參見(jiàn)UIEvent的type屬性
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches,
UIEventTypeMotion,
UIEventTypeRemoteControl,
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
};

這里只介紹第一種Touch事件
為了便于區(qū)分把
TouchEvent的
touchesBegan:withEvent:叫做Touch,手勢(shì)類(lèi)型的叫Gesture.

事件處理流程
- App(Demo
TSAplication類(lèi),UIApplication子類(lèi),代表當(dāng)前應(yīng)用)先通過(guò)hit-testing找到touch View. - 而后
APP收到sendEvent:事件,傳遞給Window(TSWindow)的sendEvent:,由TSWindow分解成touchesBegan:withEvent:,touchesMoved:withEvent,touchesEnd:withEvent,touchesCancel:withEvent等事件。 - 具體的
手勢(shì)識(shí)別以及Target-Action在TSWindow的sendEvent:收到Touch Event狀態(tài)的UITouchPhaseBegan與UITouchPhaseEnded|UITouchPhaseCancelled之間進(jìn)行。
解析UITapGestureRecognizer
UIGestureRecognizer不入響應(yīng)鏈
UIGestureRecognizer objects are not in the responder chain, yet observe touches hit-tested to their view and their view's subviews.
原理
觸摸事件(Touch events)主要用兩步實(shí)現(xiàn)的
第一步:碰撞測(cè)試(hit-testing)
要響應(yīng)一個(gè)觸摸事件,先要找到觸摸的是哪個(gè)View,再根據(jù)
Next Response來(lái)找到合適的處理方法
碰撞測(cè)試原理
-
hit-testing通過(guò)UIView的hitTest:withEvent:找到對(duì)應(yīng)的觸摸的View。 -
hitTest:withEvent:內(nèi)部會(huì)調(diào)用本類(lèi)pointInside:withEvent:,pointInside:withEvent:返回bool值,來(lái)確定是否點(diǎn)擊范圍在本View范圍內(nèi)。 - 如果在,
倒序遞歸調(diào)用所有subviews的同一過(guò)程,只要找到最后一層調(diào)用pointInside:withEvent:返回YES,沒(méi)有subview或者subview的pointInside:withEvent:返回為nil,則返回self,并逐級(jí)返回到頂層。 找到之后并不繼續(xù)進(jìn)行遞歸。
至于為什么
hitTest:withEvent:在一個(gè)點(diǎn)擊事件里調(diào)用兩遍,還不知道為什么
舉個(gè)例子
Demo截屏

當(dāng)前controller叫
hit-testing,controller對(duì)應(yīng)的view叫AView,AView第一個(gè)子View叫BView,BView有一個(gè)唯一子ViewCView,AView第二個(gè)子View叫DView,DView沒(méi)有子View。以此界面,來(lái)分析hit-testing
點(diǎn)擊AView
時(shí)序圖

時(shí)序圖的步驟在一次touch調(diào)用了2遍,看日志,原因未知,沒(méi)有查到資料。
日志
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
第一遍調(diào)試堆棧

第二遍調(diào)試堆棧
解析:
- 第一個(gè)收到
hitTest:withEvent:肯定是TSWindow,TSWindow調(diào)用pointInside:withEvent:返回YES,代表點(diǎn)擊在TSWindows上,倒序遞歸遍歷TSWindowsubviews。 - 只有一個(gè)子View
AView,調(diào)用AView的pointInside:withEvent:返回YES,倒序遞歸遍歷AViewsubviews. - 先遍歷
DView,調(diào)用DView的pointInside:withEvent:返回NO,DView的hitTest:withEvent:返回nil。 - 再遍歷
BView,,調(diào)用BView的pointInside:withEvent:返回NO,BView的hitTest:withEvent:返回nil。 -
AView所有子View都返回nil了,就返回self.
點(diǎn)擊DView
時(shí)序圖

日志
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
點(diǎn)擊BView
時(shí)序圖

日志
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
解析
不再解析,總結(jié)成一句話(huà):
倒序遞歸subview,直到某個(gè)子ViewpointInside:withEvent:返回YES,沒(méi)有subview或者subviews的hitTest:withEvent:返回nil,則返回self.
點(diǎn)擊CView
用提供的Demo,自驗(yàn)看日志,即可。
模擬代碼
發(fā)現(xiàn)用文字流程圖對(duì)程序員來(lái)說(shuō),還沒(méi)代碼更直觀。故附上模擬代碼
偽代碼
- (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代碼
放在第二篇