iOS中触摸事件传递和响应原理
系統(tǒng)響應(yīng)階段
?
-
1.手指觸碰屏幕,屏幕感受到觸摸后,將事件交由IOKit來(lái)處理。
-
2.IOKIT將觸摸事件封裝成IOHIDEvent對(duì)象,并通過(guò)mach port傳遞給SpringBoard進(jìn)程。
?
?
mach port是進(jìn)程端口,各進(jìn)程間通過(guò)它來(lái)通信。Springboard是一個(gè)系統(tǒng)進(jìn)程,可以理解為桌面系統(tǒng),可以統(tǒng)一管理和分發(fā)系統(tǒng)接收到的觸摸事件。
?
-
3.SpringBoard由于接收到觸摸事件,因此觸發(fā)了系統(tǒng)進(jìn)程的主線程的runloop的source回調(diào)。
發(fā)生觸摸事件的時(shí)候,你有可能正在桌面上翻頁(yè),也有可能正在頭條上看新聞,如果是前者,則觸發(fā)SpringBoard主線程的runloop的source0回調(diào),將桌面系統(tǒng)交由系統(tǒng)進(jìn)程去消耗。而如果是后者,則將觸摸事件通過(guò)IPC傳遞給前臺(tái)APP進(jìn)程,后面的事便是APP內(nèi)部對(duì)于觸摸事件的響應(yīng)了。
?
APP響應(yīng)觸摸事件
?
-
1.APP進(jìn)程的mach port接收來(lái)自SpringBoard的觸摸事件,主線程的runloop被喚醒,觸發(fā)source1回調(diào)。
-
2.source1回調(diào)又觸發(fā)了一個(gè)source0回調(diào),將接收到的IOHIDEvent對(duì)象封裝成UIEvent對(duì)象,此時(shí)APP將正式開(kāi)始對(duì)于觸摸事件的響應(yīng)。
-
3.source0回調(diào)將觸摸事件添加到UIApplication的事件隊(duì)列,當(dāng)觸摸事件出隊(duì)后UIApplication為觸摸事件尋找最佳響應(yīng)者。
-
4.尋找到最佳響應(yīng)者之后,接下來(lái)的事情便是事件在響應(yīng)鏈中傳遞和響應(yīng)。
?
觸摸 事件 響應(yīng)者
觸摸
?
觸摸對(duì)象即UITouch對(duì)象。
一個(gè)手指觸摸屏幕,就會(huì)生成一個(gè)UITouch對(duì)象,如果多個(gè)手指同時(shí)觸摸,就會(huì)生成多個(gè)UITouch對(duì)象。
多個(gè)手指先后觸摸,如果系統(tǒng)判斷多個(gè)手指觸摸的是同一個(gè)地方,那么不會(huì)生成多個(gè)UITouch對(duì)象,而是更新這個(gè)UITouch對(duì)象,改變其tap count。如果多個(gè)手指觸摸的不是同一個(gè)地方,那就會(huì)生成多個(gè)UITouch對(duì)象。
?
觸摸事件
?
觸摸事件即UIEvent。
UIEvent即對(duì)UITouch的一次封裝。由于一次觸摸事件并不止有一個(gè)觸摸對(duì)象,可能是多指同時(shí)觸摸。觸摸對(duì)象集合可以通過(guò)allTouches屬性來(lái)獲取。
?
響應(yīng)者
?
響應(yīng)者即UIResponser
下列實(shí)例都是UIResponser:
?
-
UIView
-
UIViewController
-
UIApplication
-
Appdelegate
響應(yīng)者響應(yīng)觸摸事件是通過(guò)下列四個(gè)方法來(lái)實(shí)現(xiàn)的:
?
//手指觸碰屏幕,觸摸開(kāi)始-?(void)touchesBegan:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event; //手指在屏幕上移動(dòng) -?(void)touchesMoved:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event; //手指離開(kāi)屏幕,觸摸結(jié)束 -?(void)touchesEnded:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event; //觸摸結(jié)束前,某個(gè)系統(tǒng)事件中斷了觸摸,例如電話呼入 -?(void)touchesCancelled:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event;?
尋找最佳響應(yīng)者(Hit-Testing)
?
當(dāng)APP通過(guò)mach port得到這個(gè)觸摸事件時(shí),APP中有那么多UIView或者UIViewController,到底應(yīng)該給誰(shuí)去響應(yīng)呢?尋找最佳響應(yīng)者就是找出這個(gè)優(yōu)先級(jí)最高的響應(yīng)對(duì)象。
?
-
尋找最佳響應(yīng)者的具體流程如下:
-
1.UIApplication首先將
事件傳遞給窗口對(duì)象(UIWindow),如果有多個(gè)UIWindow對(duì)象,則先選擇最后加上的UIWindow對(duì)象。 -
2.若UIWindow對(duì)象能響應(yīng)這個(gè)觸摸事件,則繼續(xù)向其子視圖傳遞,向子視圖傳遞時(shí)也是先傳遞給最后加上的子視圖。
-
3.若子視圖無(wú)法響應(yīng)該事件,則返回父視圖,再傳遞給倒數(shù)第二個(gè)加入該父視圖的子視圖。
[圖片上傳失敗...(image-95c4b4-1523776714398)]
例如上面這張圖,C在B的后面加入,E在F的后面加入。那么尋找最佳響應(yīng)者的順序就是: -
1.UIWindow對(duì)象將事件傳遞給視圖A,A判斷自己能否響應(yīng)觸摸事件,如果能響應(yīng),則繼續(xù)傳遞給其子視圖。
-
2.如果A能響應(yīng)觸摸事件,由于A有兩個(gè)子視圖B,C,而C又在B的后面加入的,所以A視圖再把觸摸事件傳遞給C,C再判斷自己能否響應(yīng)觸摸事件,若能則繼續(xù)傳遞給其子視圖,若不能,則A視圖再將觸摸事件傳遞給B視圖。
-
3.如果C能響應(yīng)觸摸事件,C視圖也有兩個(gè)子視圖,分別是E和F,但是由于E是在F之后加到C上面的,所以先傳遞到,由于E可以響應(yīng)觸摸事件,所以最終的最佳響應(yīng)者就是E。
?
視圖如何判斷自己能否響應(yīng)觸摸事件?
?
下列情況下,視圖不能響應(yīng)觸摸事件:
?
-
1.觸摸點(diǎn)不在試圖范圍內(nèi)。
-
2.不允許交互:視圖的userInteractionEnabled = NO。
-
3.隱藏:hidden = YES,如果視圖隱藏了,則不能響應(yīng)事件。
-
4.透明度:當(dāng)視圖的透明度小于等于0.01時(shí),不能響應(yīng)事件。
?
尋找最佳響應(yīng)者的原理
?
hitTest:withEvent:
?
每個(gè)UIView都有一個(gè)hitTest:withEvent:方法。這個(gè)方法是尋找最佳響應(yīng)者的核心方法,同時(shí)又是傳遞事件的橋梁。它的作用是詢問(wèn)事件在當(dāng)前視圖中的響應(yīng)者。hitTest:withEvent:返回一個(gè)UIView對(duì)象,作為當(dāng)前視圖層次中的響應(yīng)者。其默認(rèn)實(shí)現(xiàn)是:
?
-
若當(dāng)前視圖無(wú)法響應(yīng)事件,則返回nil。
-
若當(dāng)前視圖能響應(yīng)事件,但無(wú)子視圖可響應(yīng)事件,則返回當(dāng)前視圖。
-
若當(dāng)前視圖能響應(yīng)事件,同時(shí)有子視圖能響應(yīng),則返回子視圖層次中的事件響應(yīng)者。
開(kāi)始時(shí)UIApplication調(diào)用UIWindow的hitTest:withEvent:方法將觸摸事件傳遞給UIWindow,如果UIWindow能夠響應(yīng)觸摸事件,則調(diào)用hitTest:withEvent:將事件傳遞給其子視圖并詢問(wèn)子視圖上的最佳響應(yīng)者,這樣一級(jí)一級(jí)傳遞下去,獲取最終的最佳響應(yīng)者。
hitTest:withEvent:的代碼實(shí)現(xiàn)大致如下:
?
-?(UIView?*)hitTest:(CGPoint)point?withEvent:(UIEvent?*)event{//3種狀態(tài)無(wú)法響應(yīng)事件if?(self.userInteractionEnabled?==?NO?||?self.hidden?==?YES?||??self.alpha?<=?0.01)?return?nil;?//觸摸點(diǎn)若不在當(dāng)前視圖上則無(wú)法響應(yīng)事件if?([self?pointInside:point?withEvent:event]?==?NO)?return?nil;?//從后往前遍歷子視圖數(shù)組?int?count?=?(int)self.subviews.count;?for?(int?i?=?count?-?1;?i?>=?0;?i--)?{?//?獲取子視圖UIView?*childView?=?self.subviews[i];?//?坐標(biāo)系的轉(zhuǎn)換,把觸摸點(diǎn)在當(dāng)前視圖上坐標(biāo)轉(zhuǎn)換為在子視圖上的坐標(biāo)CGPoint?childP?=?[self?convertPoint:point?toView:childView];?//詢問(wèn)子視圖層級(jí)中的最佳響應(yīng)視圖UIView?*fitView?=?[childView?hitTest:childP?withEvent:event];?if?(fitView)?{//如果子視圖中有更合適的就返回return?fitView;?}}?//沒(méi)有在子視圖中找到更合適的響應(yīng)視圖,那么自身就是最合適的return?self; }注意這里的方法pointInside:withEvent:,這個(gè)方法是判斷觸摸點(diǎn)是否在視圖范圍內(nèi)。默認(rèn)的實(shí)現(xiàn)是如果觸摸點(diǎn)在視圖范圍內(nèi)則返回YES,否則返回NO。
下面我們?cè)谏蠄D中的每個(gè)視圖層次中添加三個(gè)方法來(lái)驗(yàn)證之前的分析:
?
-?(UIView?*)hitTest:(CGPoint)point?withEvent:(UIEvent?*)event{NSLog(@"%s",__func__);return?[super?hitTest:point?withEvent:event]; } -?(BOOL)pointInside:(CGPoint)point?withEvent:(UIEvent?*)event{NSLog(@"%s",__func__);return?[super?pointInside:point?withEvent:event]; } -?(void)touchesBegan:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event{NSLog(@"%s",__func__); }點(diǎn)擊視圖,打印出來(lái)的結(jié)果是:
?
-[AView?hitTest:withEvent:] -[AView?pointInside:withEvent:] -[CView?hitTest:withEvent:] -[CView?pointInside:withEvent:] -[EView?hitTest:withEvent:] -[EView?pointInside:withEvent:] -[EView?touchesBegan:withEvent:]這和我們的分析是一致的。
?
自定義hitTest:withEvent:
?
自定義hitTest:withEvent:.png
?
大家看一下上面的圖,其中A和B都是根視圖控制器的View的子視圖,C是加在B上的子視圖。當(dāng)我們觸摸C中在A的那部分的視圖的時(shí)候,我們打印看看:
?
2018-04-13?19:37:19.985968+0800?UITouchDemo[9174:387327]?-[BView?hitTest:withEvent:] 2018-04-13?19:37:19.987782+0800?UITouchDemo[9174:387327]?-[BView?pointInside:withEvent:] 2018-04-13?19:37:19.988017+0800?UITouchDemo[9174:387327]?-[AView?hitTest:withEvent:] 2018-04-13?19:37:19.988294+0800?UITouchDemo[9174:387327]?-[AView?pointInside:withEvent:] 2018-04-13?19:37:19.990704+0800?UITouchDemo[9174:387327]?-[AView?touchesBegan:withEvent:]通過(guò)打印結(jié)果我們發(fā)現(xiàn),觸摸事件壓根就沒(méi)有傳遞到C視圖這里,這是為什么呢?
原來(lái),觸摸事件最早傳遞到B視圖,然后調(diào)用B視圖的hitTest:withEvent:方法,在這個(gè)方法中會(huì)調(diào)用pointInside:withEvent:來(lái)判斷觸摸點(diǎn)是否在視圖范圍內(nèi),這里由于觸摸的點(diǎn)是在A視圖的那部分,所以不在B視圖的那部分,因此返回NO。這樣觸摸事件就傳遞到了A視圖,由于A可以響應(yīng)觸摸事件,而A又沒(méi)有子視圖,所以最終的最佳響應(yīng)者就是A視圖。
那么這顯然不是我們希望看到的,我們希望的是當(dāng)觸摸C時(shí),不管觸摸的是C的哪里,C都能成為最佳響應(yīng)者響應(yīng)觸摸事件。
要解決這個(gè)問(wèn)題也很容易,我們只需要在B視圖中重寫(xiě)pointInside:withEvent:方法。
?
-?(BOOL)pointInside:(CGPoint)point?withEvent:(UIEvent?*)event{NSLog(@"%s",?__func__);CGPoint?tmpPoint?=?[self?convertPoint:point?toView:_cView];if([_cView?pointInside:tmpPoint?withEvent:event]){return?YES;}return?[super?pointInside:point?withEvent:event]; }我們判斷觸摸點(diǎn)位置是否在視圖C范圍內(nèi),如果在視圖C的范圍內(nèi),則直接返回YES。
?
觸摸事件的響應(yīng)
?
通過(guò)hitTest:withEvent:我們已經(jīng)找到了最佳響應(yīng)者,下面要做的事就是讓這個(gè)最佳響應(yīng)者響應(yīng)觸摸事件。這個(gè)最佳響應(yīng)者對(duì)于觸摸事件擁有決定權(quán),它可以決定是自己一個(gè)響應(yīng)這個(gè)事件,也可以自己響應(yīng)之后還把它傳遞給其他響應(yīng)者。這個(gè)由響應(yīng)者構(gòu)成的就是響應(yīng)鏈。
響應(yīng)者對(duì)于事件的響應(yīng)和傳遞都是在touchesBegan:withEvent:這個(gè)方法中完成的。該方法默認(rèn)的實(shí)現(xiàn)是將該方法沿著響應(yīng)鏈往下傳遞
響應(yīng)者對(duì)于接收到的事件有三種操作:
?
-
1.默認(rèn)的操作。不攔截,事件會(huì)沿著默認(rèn)的響應(yīng)鏈自動(dòng)往下傳遞。
-
2.攔截,不再往下分發(fā)事件,重寫(xiě)touchesBegan:withEvent:方法,不調(diào)用父類的touchesBegan:withEvent:方法。
-
3.不攔截,繼續(xù)往下分發(fā)事件,重新touchesBegan:withEvent:方法,并調(diào)用父類的touchesBegan:withEvent:方法。
?
我們一般在編寫(xiě)代碼時(shí),如果某個(gè)視圖響應(yīng)事件,會(huì)在該視圖類中重寫(xiě)touchesBegan:withEvent:方法,但是并不會(huì)調(diào)用父類的
touchesBegan:withEvent:方法,這樣我們就把這個(gè)事件攔截下來(lái)了,不再沿著響應(yīng)鏈往下傳遞。那么我們?yōu)槭裁聪胍刂憫?yīng)鏈傳遞事件就要重寫(xiě)父類的touchesBegan:withEvent:方法呢?因?yàn)楦割惖膖ouchesBegan:withEvent:方法默認(rèn)是向下傳遞的。我們重寫(xiě)touchesBegan:withEvent:并調(diào)用父類的方法就是既對(duì)觸摸事件實(shí)現(xiàn)了響應(yīng),又將事件沿著響應(yīng)鏈傳遞了。
?
響應(yīng)鏈中的事件傳遞規(guī)則
?
每一個(gè)響應(yīng)者對(duì)象都有一個(gè)nextResponder方法,用來(lái)獲取響應(yīng)鏈中當(dāng)前響應(yīng)者對(duì)象的下一個(gè)響應(yīng)者。因此,如果事件的最佳響應(yīng)者確定了,那么整個(gè)響應(yīng)鏈也就確定了。
對(duì)于響應(yīng)者對(duì)象,默認(rèn)的nextResponde對(duì)象如下:
?
-
UIView
若視圖是UIViewController的View。則其nextResponder是UIViewController,若其只是單獨(dú)的視圖,則其nextResponder是其父視圖。 -
UIViewController
若該視圖是window的根視圖,則其nextResponder為窗口對(duì)象,若其是由其他視圖控制器present的,則其nextResponder是presenting View Controller。 -
UIWindow
nextResponder為UIApplication對(duì)象。?
事件響應(yīng)鏈.png
上圖是官網(wǎng)對(duì)于響應(yīng)鏈的示例展示,如果最佳響應(yīng)者對(duì)象是UITextField,則響應(yīng)鏈為: -
UITextField->UIView->UIView->UIViewController->UIWindow->UIApplication->UIApplicationDelegate.
現(xiàn)在我們可以猜想,在父類的touchesBegan:withEvent:方法中,可能調(diào)用了[self.nextResponder touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event]這樣來(lái)將事件沿著響應(yīng)鏈傳遞。
?
UIResponder、UIGestureRecognizer、UIControl的優(yōu)先級(jí)
?
不光UIResponder能響應(yīng)觸摸事件,UIGestureRecognizer和UIControl也能處理觸摸事件。
?
UIGestureRecognizer
?
我們首先來(lái)看一個(gè)場(chǎng)景
?
?
UIGestureRecognizer.png
?
我們給上圖中的黃色視圖A添加tap事件:
?
UITapGestureRecognizer?*tap?=?[[UITapGestureRecognizer?alloc]?init];[tap?addTarget:self?action:@selector(tapGesture)];[self?addGestureRecognizer:tap]; 添加點(diǎn)擊事件:-?(void)tapGesture{NSLog(@"taped"); }?
運(yùn)行程序,點(diǎn)擊黃色視圖A,看打印結(jié)果:
?
2018-04-15?16:36:25.378952+0800?UITouchDemo[14824:351042]?-[AView?touchesBegan:withEvent:] 2018-04-15?16:36:25.388247+0800?UITouchDemo[14824:351042]?taped 2018-04-15?16:36:25.391769+0800?UITouchDemo[14824:351042]?-[AView?touchesCancelled:withEvent:]首先響應(yīng)者A響應(yīng)了tap。然后執(zhí)行了手勢(shì)識(shí)別器的函數(shù),最后touchesCancelled:withEvent:函數(shù)確被調(diào)用,正確的應(yīng)該是最后touchesEnded:withEvent:函數(shù)被調(diào)用,這是怎么回事呢?Apple的解釋是:
?
window在將事件傳遞給最佳響應(yīng)者之前會(huì)把事件先傳給手勢(shì)識(shí)別器,然后再傳給最佳響應(yīng)者,當(dāng)手勢(shì)識(shí)別器已經(jīng)識(shí)別了手勢(shì)時(shí),最佳響應(yīng)者對(duì)象會(huì)調(diào)用touchesCancelled:withEvent:方法終止對(duì)事件的響應(yīng)。
?
如果按照這個(gè)理論,上面的結(jié)果也應(yīng)該是先打印taped后打印-[AView touchesBegan:withEvent:]呀,為什么不是這樣呢?問(wèn)題出在,打印taped并不代表是這個(gè)時(shí)候事件傳遞到了手勢(shì)識(shí)別器這里,而是手勢(shì)識(shí)別器這個(gè)時(shí)候正式識(shí)別了手勢(shì)。正式識(shí)別了這個(gè)手勢(shì)和事件被傳遞到了手勢(shì)識(shí)別器這里的時(shí)間是不一樣的。
那么我們?cè)鯓硬拍苤朗录窍葌鬟f給了最佳響應(yīng)者還是壽司識(shí)別器呢?只需要找到手勢(shì)識(shí)別器的響應(yīng)函數(shù)然后打印它們即可。手勢(shì)識(shí)別器的響應(yīng)函數(shù)和UIResponder的響應(yīng)函數(shù)非常相似:
?
-?(void)touchesBegan:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event; -?(void)touchesMoved:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event; -?(void)touchesEnded:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event; -?(void)touchesCancelled:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event;我們重寫(xiě)一個(gè)單擊手勢(shì)類,繼承自UITapGestureRecognizer即可。在這個(gè)類里導(dǎo)入頭文件<UIKit/UIGestureRecognizerSubclass.h>:
?
-?(void)touchesBegan:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event{NSLog(@"%s,%s",object_getClassName(self.view),?__func__);[super?touchesBegan:touches?withEvent:event]; } -?(void)touchesMoved:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event{NSLog(@"%s,%s",object_getClassName(self.view),?__func__);[super?touchesMoved:touches?withEvent:event]; } -?(void)touchesEnded:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event{NSLog(@"%s,%s",object_getClassName(self.view),?__func__);[super?touchesEnded:touches?withEvent:event]; } -?(void)touchesCancelled:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event{NSLog(@"%s,%s",object_getClassName(self.view),?__func__);[super?touchesCancelled:touches?withEvent:event]; }這樣我們就可以打印手勢(shì)識(shí)別器接收事件的時(shí)間。我們打印結(jié)果:
?
2018-04-16?14:53:20.444618+0800?UITouchDemo[24410:731610]?AView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?14:53:20.451872+0800?UITouchDemo[24410:731610]?-[AView?touchesBegan:withEvent:] 2018-04-16?14:53:20.452245+0800?UITouchDemo[24410:731610]?AView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?14:53:20.455192+0800?UITouchDemo[24410:731610]?AView?taped 2018-04-16?14:53:20.455448+0800?UITouchDemo[24410:731610]?-[AView?touchesCancelled:withEvent:]通過(guò)打印結(jié)果我們能夠很清楚的看到,事件最先傳遞給了手勢(shì)識(shí)別器,然后傳遞給了最佳響應(yīng)者,在手勢(shì)識(shí)別器識(shí)別成功手勢(shì)后,調(diào)用最佳響應(yīng)者的touchesCancelled:方法終止最佳響應(yīng)者對(duì)于事件的響應(yīng)。
下面再看一個(gè)情景:
?
多個(gè)手勢(shì)識(shí)別器.png
?
在上圖中,視圖A,B,C上都添加了手勢(shì)識(shí)別器,那么當(dāng)我們單擊C視圖的時(shí)候,事件是一個(gè)怎么樣的響應(yīng)過(guò)程呢?我們打印結(jié)果看一下:
?
2018-04-16?15:03:21.809456+0800?UITouchDemo[24654:740042]?AView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:03:21.811451+0800?UITouchDemo[24654:740042]?UIView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:03:21.813232+0800?UITouchDemo[24654:740042]?CView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:03:21.815768+0800?UITouchDemo[24654:740042]?BView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:03:21.818022+0800?UITouchDemo[24654:740042]?-[CView?touchesBegan:withEvent:] 2018-04-16?15:03:21.818708+0800?UITouchDemo[24654:740042]?AView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:03:21.818899+0800?UITouchDemo[24654:740042]?UIView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:03:21.819147+0800?UITouchDemo[24654:740042]?CView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:03:21.819552+0800?UITouchDemo[24654:740042]?BView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:03:21.820637+0800?UITouchDemo[24654:740042]?CView?taped 2018-04-16?15:03:21.820967+0800?UITouchDemo[24654:740042]?-[CView?touchesCancelled:withEvent:]我們可以看到,事件首先傳遞給了A,UIView,B,C這幾個(gè)視圖上面的手勢(shì)識(shí)別器,然后才傳遞給了最佳響應(yīng)者C視圖,A,UIView,B,C這幾個(gè)視圖的手勢(shì)識(shí)別器都識(shí)別了手勢(shì)之后,調(diào)用最佳響應(yīng)者的touchesCancelled:withEvent:方法來(lái)取消最佳響應(yīng)者對(duì)于事件的響應(yīng)。
再來(lái)運(yùn)行一下程序,打印執(zhí)行結(jié)果:
?
2018-04-16?15:09:53.877158+0800?UITouchDemo[24765:744167]?UIView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:09:53.877720+0800?UITouchDemo[24765:744167]?AView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:09:53.878351+0800?UITouchDemo[24765:744167]?CView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:09:53.878720+0800?UITouchDemo[24765:744167]?BView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:09:53.880317+0800?UITouchDemo[24765:744167]?-[CView?touchesBegan:withEvent:] 2018-04-16?15:09:53.886045+0800?UITouchDemo[24765:744167]?UIView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:09:53.887088+0800?UITouchDemo[24765:744167]?AView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:09:53.887661+0800?UITouchDemo[24765:744167]?CView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:09:53.888026+0800?UITouchDemo[24765:744167]?BView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:09:53.888661+0800?UITouchDemo[24765:744167]?CView?taped 2018-04-16?15:09:53.889124+0800?UITouchDemo[24765:744167]?-[CView?touchesCancelled:withEvent:]我們看到,UIView,A.B,C這四個(gè)視圖上的手勢(shì)識(shí)別器接收事件的順序發(fā)生了變化,但是最佳響應(yīng)者CView一定是最后接收事件的,并且最后響應(yīng)的函數(shù)一定是CView上綁定的手勢(shì)識(shí)別器的函數(shù)。由此我們得出結(jié)論:
當(dāng)響應(yīng)鏈上有手勢(shì)識(shí)別器時(shí),事件在傳遞過(guò)程中一定會(huì)先傳遞給響應(yīng)鏈上的手勢(shì)識(shí)別器,然后才傳遞給最佳響應(yīng)者,當(dāng)響應(yīng)鏈上的手勢(shì)識(shí)別了手勢(shì)后就會(huì)取消最佳響應(yīng)者對(duì)于事件的響應(yīng)。事件傳遞給響應(yīng)鏈上的手勢(shì)識(shí)別器時(shí)是亂序的,并不是按照響應(yīng)鏈從頂至底傳遞,但是最后響應(yīng)的函數(shù)還是響應(yīng)鏈最頂端的手勢(shì)識(shí)別器函數(shù)。
?
手勢(shì)識(shí)別器的三個(gè)屬性
?
@property(nonatomic)?BOOL?cancelsTouchesInView; @property(nonatomic)?BOOL?delaysTouchesBegan; @property(nonatomic)?BOOL?delaysTouchesEnded;先總結(jié)一下手勢(shì)識(shí)別器和UIResponder對(duì)于事件響應(yīng)的聯(lián)系:
?
-
Window先將事件傳遞給響應(yīng)鏈上的手勢(shì)識(shí)別器,再傳遞給UIResponder。
-
手勢(shì)識(shí)別器識(shí)別手勢(shì)期間,若果觸摸對(duì)象的狀態(tài)發(fā)生變化,都是先發(fā)送給手勢(shì)識(shí)別器,再發(fā)送給UIResponder。
-
若手勢(shì)識(shí)別器已經(jīng)成功識(shí)別了手勢(shì),則停止UIResponder對(duì)于事件的響應(yīng),并停止向UIResponder發(fā)送事件。
-
若手勢(shì)識(shí)別器未能識(shí)別手勢(shì),而此時(shí)觸摸并未結(jié)束,則停止向手勢(shì)識(shí)別器發(fā)送手勢(shì),僅向UIResponder發(fā)送事件。
-
若手勢(shì)識(shí)別器未能識(shí)別手勢(shì),而此時(shí)觸摸已經(jīng)結(jié)束,則向UIResponder發(fā)送end狀態(tài)的touch事件以停止對(duì)事件的響應(yīng)。
-
1.cancelsTouchesInView
默認(rèn)為yes。表示當(dāng)手勢(shì)識(shí)別成功后,取消最佳響應(yīng)者對(duì)象對(duì)于事件的響應(yīng),并不再向最佳響應(yīng)者發(fā)送事件。若設(shè)置為No,則表示在手勢(shì)識(shí)別器識(shí)別成功后仍然向最佳響應(yīng)者發(fā)送事件,最佳響應(yīng)者仍響應(yīng)事件。 -
2.delaysTouchesBegan
默認(rèn)為No,即在手勢(shì)識(shí)別器識(shí)別手勢(shì)期間,觸摸對(duì)象狀態(tài)發(fā)生變化時(shí),都會(huì)發(fā)送給最佳響應(yīng)者,若設(shè)置成yes,則在識(shí)別手勢(shì)期間,觸摸狀態(tài)發(fā)生變化時(shí)不會(huì)發(fā)送給最佳響應(yīng)者。 -
3.delaysTouchesEnded
默認(rèn)為NO。默認(rèn)情況下當(dāng)手勢(shì)識(shí)別器未能識(shí)別手勢(shì)時(shí),若此時(shí)觸摸已經(jīng)結(jié)束,則會(huì)立即通知Application發(fā)送狀態(tài)為end的touch事件給最佳響應(yīng)者以調(diào)用 touchesEnded:withEvent: 結(jié)束事件響應(yīng);若設(shè)置為YES,則會(huì)在手勢(shì)識(shí)別失敗時(shí),延遲一小段時(shí)間(0.15s)再調(diào)用響應(yīng)者的 touchesEnded:withEvent:。
?
UIControl
?
UIControl是系統(tǒng)提供的能夠以target-action模式處理觸摸事件的控件,iOS中UIButton、UISegmentedControl、UISwitch等控件都是UIControl的子類。當(dāng)UIControl跟蹤到觸摸事件時(shí),會(huì)向其上添加的target發(fā)送事件以執(zhí)行action。值得注意的是,UIConotrol是UIView的子類,因此本身也具備UIResponder應(yīng)有的身份。
看下面一種情景
?
UIButton.png
?
圖中視圖A,B,C上都添加有單擊手勢(shì),C上面的黑色按鈕添加有action。
當(dāng)我們點(diǎn)擊C上面的黑色按鈕時(shí),看打印結(jié)果:
?
2018-04-16?15:57:10.552464+0800?UITouchDemo[25592:774264]?BView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:57:10.552719+0800?UITouchDemo[25592:774264]?AView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:57:10.553084+0800?UITouchDemo[25592:774264]?CView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?15:57:10.556521+0800?UITouchDemo[25592:774264]?BView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:57:10.557096+0800?UITouchDemo[25592:774264]?AView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:57:10.557447+0800?UITouchDemo[25592:774264]?CView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?15:57:10.558708+0800?UITouchDemo[25592:774264]?button?Clicked我們看到,雖然事件都傳遞給了響應(yīng)鏈上的手勢(shì)識(shí)別器,但是這些手勢(shì)識(shí)別器綁定的函數(shù)最后都沒(méi)有響應(yīng),而是響應(yīng)的黑色按鈕綁定的action。我們?cè)僭诤谏粹o上面加一個(gè)單擊手勢(shì),然后單擊黑色按鈕,看打印結(jié)果:
?
2018-04-16?16:05:35.555304+0800?UITouchDemo[25754:780177]?CView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?16:05:35.555745+0800?UITouchDemo[25754:780177]?BView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?16:05:35.556011+0800?UITouchDemo[25754:780177]?AView,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?16:05:35.556573+0800?UITouchDemo[25754:780177]?UIButton,-[PDTapGestureRecognizer?touchesBegan:withEvent:] 2018-04-16?16:05:35.559354+0800?UITouchDemo[25754:780177]?CView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?16:05:35.559600+0800?UITouchDemo[25754:780177]?BView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?16:05:35.560494+0800?UITouchDemo[25754:780177]?AView,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?16:05:35.561018+0800?UITouchDemo[25754:780177]?UIButton,-[PDTapGestureRecognizer?touchesEnded:withEvent:] 2018-04-16?16:05:35.562089+0800?UITouchDemo[25754:780177]?Button?taped可以看到,當(dāng)UIControl上面添加了手勢(shì)后,UIControl不會(huì)響應(yīng)自己的action。
因此得出結(jié)論:
UIControl會(huì)阻止父視圖上的手勢(shì)識(shí)別器的行為,也就是UIControl的執(zhí)行優(yōu)先級(jí)比父視圖上面的UIGestureRecognizer要高,但是比UIControl自身的UIGestureRecognizer優(yōu)先級(jí)要低。
閱讀原文
總結(jié)
以上是生活随笔為你收集整理的iOS中触摸事件传递和响应原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Heritrix在Windows下的运行
- 下一篇: 把日期转换成时间戳!!很简单