精簡(jiǎn)地說:iOS事件分為傳遞和響應(yīng)兩個(gè)部分。
事件傳遞(建立傳遞鏈):
iOS系統(tǒng)檢測(cè)到手指觸摸(Touch)操作時(shí)會(huì)將其打包成一個(gè)UIEvent對(duì)象,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列,單例的UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來處理,UIWindow對(duì)象首先會(huì)使用hitTest:withEvent:方法尋找此次Touch操作初始點(diǎn)所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖,這個(gè)過程稱之為hit-test view。
hittest的目的就是找到最終的傳遞鏈。
hitTest:withEvent:流程如下:
- 先判斷當(dāng)前視圖hidden=YES,userInteractionEnabled=NO,alpha<0.01等屬性,如果滿足其中之一,返回nil。
- 再看當(dāng)前視圖的pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi);
- 若返回NO,則hitTest:withEvent:返回nil;
- 若返回YES,則向當(dāng)前視圖的【一級(jí)子視圖(subviews)遞歸發(fā)送】hitTest:withEvent:消息,所有子視圖的遍歷順序是【從subviews數(shù)組的末尾向前遍歷】,直到有子視圖返回非空對(duì)象或者全部子視圖遍歷完畢;
- 若第一次有子視圖返回非空對(duì)象,則hitTest:withEvent:方法返回此對(duì)象,處理結(jié)束;如所有子視圖都返回非,則hitTest:withEvent:方法返回自身。
提醒:
hittest返回nil表示該條傳遞鏈已經(jīng)終止,不是正確的傳遞鏈。
hittest返回非nil對(duì)象表示已經(jīng)找到傳遞鏈的葉子節(jié)點(diǎn),即找到正確的傳遞鏈。
hitTest:withEvent:遇到以下會(huì)返回nil。
1.hidden=YES的視圖。
2.userInteractionEnabled=NO的視圖(注意userInteractionEnabled是影響子視圖事件傳遞,但不影響兄弟視圖)
3.alpha<0.01的視圖。
4.顯示區(qū)域超過父視圖bounds區(qū)域的視圖。那么超出區(qū)域不能識(shí)別。當(dāng)然,可以重寫pointInside:withEvent:方法來識(shí)別。
hitTest:底層實(shí)現(xiàn)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判斷自己能否接收觸摸事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2.判斷觸摸點(diǎn)在不在自己范圍內(nèi)
if (![self pointInside:point withEvent:event]) return nil;
// 3.從后往前遍歷自己的子控件,看是否有子控件更適合響應(yīng)此事件
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;
}
結(jié)合例子分析:

舉例分析:用戶點(diǎn)擊了View D,下面結(jié)合上圖介紹hit-test view的流程:
1、A是UIWindow的根視圖,因此,UIWindwo對(duì)象會(huì)首先對(duì)A進(jìn)行hit-test;
2、顯然用戶點(diǎn)擊的范圍是在A的范圍內(nèi),因此,pointInside:withEvent:返回了YES,這時(shí)會(huì)繼續(xù)檢查A的子視圖;
3、這時(shí)候會(huì)有兩個(gè)分支,B和C:
C在subviews的末尾,先遞歸遍歷C。點(diǎn)擊的范圍在C內(nèi),即C的pointInside:withEvent:返回YES;
4、這時(shí)候有D和E兩個(gè)分支:
點(diǎn)擊的范圍不再E內(nèi),因此E的pointInside:withEvent:返回NO,對(duì)應(yīng)的hitTest:withEvent:返回nil;
點(diǎn)擊的范圍在D內(nèi),即D的pointInside:withEvent:返回YES,由于D沒有子視圖,因此,D的hitTest:withEvent:會(huì)將D返回,再往回回溯,就是C的hitTest:withEvent:返回D--->>A的hitTest:withEvent:返回D。
即A->C->E(返回nil)->D(返回D)->C(返回D)->A(返回D)
至此,本次點(diǎn)擊事件的第一響應(yīng)者就通過響應(yīng)者鏈的事件分發(fā)邏輯成功的找到了。
不難看出,這個(gè)處理流程有點(diǎn)類似二分搜索的思想,這樣能以最快的速度,最精確地定位出能響應(yīng)觸摸事件的UIView。
另外hittest可能會(huì)被調(diào)用2-3次,這里請(qǐng)不要再hittest作業(yè)務(wù)相關(guān)操作,否則會(huì)導(dǎo)致執(zhí)行多次。
事件響應(yīng)(回溯響應(yīng)鏈):
經(jīng)過hittest后,我們已經(jīng)找到了鏈尾【第一響應(yīng)者】,這時(shí)候開始回溯響應(yīng)操作。響應(yīng)鏈的關(guān)系如圖:
請(qǐng)注意:響應(yīng)鏈在傳遞鏈的基礎(chǔ)上增加了UIViewController。

NextResponder:
1.當(dāng)一個(gè)view被添加到superView上的時(shí)候,它的nextResponder就會(huì)被指向它的superView;
2.當(dāng)vc被初始化的時(shí)候,self.view(topmost view)的nextResponder會(huì)被指向所在的controller;
(概括前兩者就是:如果當(dāng)前這個(gè)view是控制器的self.view,那么控制器就是上一個(gè)響應(yīng)者 如果當(dāng)前這個(gè)view不是控制器的view,那么父控件就是上一個(gè)響應(yīng)者)
3.vc的nextResponder會(huì)被指向self.view的superView。
4.最頂級(jí)的vc的nextResponder指向UIWindow。
5.UIWindow的nextResponder指向UIApplication
在事件響應(yīng)對(duì)象UIResponder(UIView和UIViewController都是繼承UIResponder)中有對(duì)應(yīng)的方法來分別處理這幾個(gè)階段的事件:
touchesBegan:NSArray<UITouch *>>withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
Touch默認(rèn)流程(以單擊為例)是:從響應(yīng)鏈葉子節(jié)點(diǎn)開始回溯,先是TouchBegan回溯,然后是TouchEnded回溯。
舉例:對(duì)于觸摸事件來說,UIApplication會(huì)首先把事件交給keyWindow,Window會(huì)將事件交給UIGestureRecognizer處理,如果UIGestureRecognizer識(shí)別了傳遞過來的事件,則交給相對(duì)應(yīng)的target去處理,回溯終止,事件不會(huì)再傳遞!當(dāng)然這里也可以重寫touchBegan等方法手動(dòng)讓手勢(shì)繼續(xù)往superview傳從而實(shí)現(xiàn)多級(jí)響應(yīng)。
如果UIGestureRecognizer并沒有識(shí)別傳遞過來的事件(可能是沒有視圖添加手勢(shì),也可能手勢(shì)識(shí)別不成功),事件會(huì)傳遞到視圖樹形結(jié)構(gòu)
touches方法實(shí)際上什么事都沒做,UIView繼承了它進(jìn)行重寫,就是把事件傳遞給nextResponder,相當(dāng)于[self.nextResponder touchesBegan:touches withEvent:event]。所以當(dāng)一個(gè)view沒有重寫touch事件,那么這個(gè)事件就會(huì)一直傳遞下去,直到UIApplication。如果重寫了touch方法,這個(gè)view響應(yīng)了事件之后,事件就被攔截了,它的nextResponder不會(huì)收到這個(gè)事件。這個(gè)時(shí)候如果想事件繼續(xù)傳遞下去,可以調(diào)用[self.nextResponder touchesBegan:touches withEvent:event]
手勢(shì):
通過touches方法監(jiān)聽view觸摸事件,有很明顯的幾個(gè)缺點(diǎn):必須得自定義view、由于是在view內(nèi)部的touches方法中監(jiān)聽觸摸事件,因此默認(rèn)情況下,無法讓其他外界對(duì)象監(jiān)聽view的觸摸事件、不容易區(qū)分用戶的具體手勢(shì)行為。
所以iOS把觸摸事件做了封裝, 對(duì)常用的手勢(shì)進(jìn)行了處理, 封裝了6種常見的手勢(shì)
UITapGestureRecognizer(敲擊)
UILongPressGestureRecognizer(長(zhǎng)按)
UISwipeGestureRecognizer(輕掃)
UIRotationGestureRecognizer(旋轉(zhuǎn))
UIPinchGestureRecognizer(捏合,用于縮放)
UIPanGestureRecognizer(拖拽)
UIControlEvent:
其實(shí)要了解UIControlEvent,必須簡(jiǎn)單說一下UITouch和UIEvent事件,都是和觸摸相關(guān),UIEvent是一系列UITouch的集合,在IOS中負(fù)責(zé)響應(yīng)觸摸事件。
UIControl是UIView的子類,當(dāng)然也是UIResponder的子類。UIControl是諸如UIButton、UISwitch、UITextField等控件的父類,它本身也包含了一些屬性和方法。


UIControl對(duì)象采用了一種新的事件處理機(jī)制,將觸摸事件轉(zhuǎn)換成簡(jiǎn)單操作,其實(shí)就是重寫了UIResponder的方法中(如touchBegan:withEvent)中,即事件不再往上回溯響應(yīng)。這樣方便了事件處理,而不用每次都重寫TouchBegan方法。
UIResponder可以參考這篇文章UIKit: UIResponder。
比如我點(diǎn)擊一個(gè)UIButton,即使你未添加UIControlEventTouchUpInside,它的父類touchBegan也不會(huì)被調(diào)用。因?yàn)閁IControl重寫的方法touchBegan:withEvent并未調(diào)用[super touchBegan:withEvent]
UITapGestureRecognzier:
UITapGestureRecognzier其實(shí)就是對(duì)各類復(fù)雜觸摸操作響應(yīng)過程的一個(gè)封裝。
在六種手勢(shì)識(shí)別中,只有一種手勢(shì)是離散手勢(shì),它就是UITapGestureRecognzier。離散手勢(shì)的特點(diǎn)就是一旦識(shí)別就無法取消,而且只會(huì)調(diào)用一次手勢(shì)操作事件(初始化手勢(shì)時(shí)指定的觸發(fā)方法)。換句話說其他五種手勢(shì)是連續(xù)手勢(shì),連續(xù)手勢(shì)的特點(diǎn)就是會(huì)多次調(diào)用手勢(shì)操作事件,而且在連續(xù)手勢(shì)識(shí)別后可以取消手勢(shì)?!鞠聢D是手勢(shì)狀態(tài)圖】

分別以UITap和UIPan兩種手勢(shì)說明流程:
UITap:TouchBegan回溯->Tap->TouchCancelled回溯。
UIPan:TouchBegan回溯->TouchMoved多次回溯->UIPanBegan-TouchCancelled回溯->UIPanChanged多次->UIPanEnded。
事件總結(jié):
1.父視圖不能接收事件,則子視圖無法接受事件
2.子視圖超出父視圖的部分,不能接收事件
3.同一個(gè)父視圖下,最上面的視圖,首先遭遇事件,如果能夠響應(yīng),就不向下傳遞事件。如果不能接收,事件向下傳遞
總結(jié):
- iOS事件流程分為尋找響應(yīng)鏈和響應(yīng)鏈回溯,其中響應(yīng)鏈回溯部分和android類似。
- 為了事件響應(yīng)處理的方便,蘋果又推出了UIControlEvent和UITapGestureRecognzier。它們都是針對(duì)響應(yīng)鏈回溯過程。
- UITapGestureRecognzier和UIControlEvent不同,UIControlEvent不會(huì)響應(yīng)父類的TouchBegan等操作,而UITapGestureRecognzier會(huì)響應(yīng)。