屏幕旋转之后的触摸坐标_iOS 中触摸事件传递和响应原理
鏈接:https://www.jianshu.com/p/4aeaf3aa0c7e
系統(tǒng)響應(yīng)階段
1、手指觸碰屏幕,屏幕感受到觸摸后,將事件交由IOKit來處理。
2、IOKIT將觸摸事件封裝成IOHIDEvent對象,并通過mach port傳遞給SpringBoard進(jìn)程。
mach port是進(jìn)程端口,各進(jìn)程間通過它來通信。Springboard是一個系統(tǒng)進(jìn)程,可以理解為桌面系統(tǒng),可以統(tǒng)一管理和分發(fā)系統(tǒng)接收到的觸摸事件。
3、SpringBoard由于接收到觸摸事件,因此觸發(fā)了系統(tǒng)進(jìn)程的主線程的runloop的source回調(diào)。
發(fā)生觸摸事件的時候,你有可能正在桌面上翻頁,也有可能正在頭條上看新聞,如果是前者,則觸發(fā)SpringBoard主線程的runloop的source0回調(diào),將桌面系統(tǒng)交由系統(tǒng)進(jìn)程去消耗。而如果是后者,則將觸摸事件通過IPC傳遞給前臺APP進(jìn)程,后面的事便是APP內(nèi)部對于觸摸事件的響應(yīng)了。
APP響應(yīng)觸摸事件
1、APP進(jìn)程的mach port接收來自SpringBoard的觸摸事件,主線程的runloop被喚醒,觸發(fā)source1回調(diào)。
2、source1回調(diào)又觸發(fā)了一個source0回調(diào),將接收到的IOHIDEvent對象封裝成UIEvent對象,此時APP將正式開始對于觸摸事件的響應(yīng)。
3、source0回調(diào)將觸摸事件添加到UIApplication的事件隊列,當(dāng)觸摸事件出隊后UIApplication為觸摸事件尋找最佳響應(yīng)者。
4、尋找到最佳響應(yīng)者之后,接下來的事情便是事件在響應(yīng)鏈中傳遞和響應(yīng)。
觸摸 事件 響應(yīng)者
觸摸
觸摸對象即UITouch對象。
一個手指觸摸屏幕,就會生成一個UITouch對象,如果多個手指同時觸摸,就會生成多個UITouch對象。
多個手指先后觸摸,如果系統(tǒng)判斷多個手指觸摸的是同一個地方,那么不會生成多個UITouch對象,而是更新這個UITouch對象,改變其tap count。如果多個手指觸摸的不是同一個地方,那就會生成多個UITouch對象。
觸摸事件
觸摸事件即UIEvent。
UIEvent即對UITouch的一次封裝。由于一次觸摸事件并不止有一個觸摸對象,可能是多指同時觸摸。觸摸對象集合可以通過allTouches屬性來獲取。
響應(yīng)者
響應(yīng)者即UIResponser
下列實(shí)例都是UIResponser:
UIView
UIViewController
UIApplication
Appdelegate
響應(yīng)者響應(yīng)觸摸事件是通過下列四個方法來實(shí)現(xiàn)的:
-?(void)touchesBegan:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event;
//手指在屏幕上移動
-?(void)touchesMoved:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event;
//手指離開屏幕,觸摸結(jié)束
-?(void)touchesEnded:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event;
//觸摸結(jié)束前,某個系統(tǒng)事件中斷了觸摸,例如電話呼入
-?(void)touchesCancelled:(NSSet<UITouch?*>?*)touches?withEvent:(nullable?UIEvent?*)event;
尋找最佳響應(yīng)者(Hit-Testing)
當(dāng)APP通過mach port得到這個觸摸事件時,APP中有那么多UIView或者UIViewController,到底應(yīng)該給誰去響應(yīng)呢?尋找最佳響應(yīng)者就是找出這個優(yōu)先級最高的響應(yīng)對象。
尋找最佳響應(yīng)者的具體流程如下:
1、UIApplication首先將
事件傳遞給窗口對象(UIWindow),如果有多個UIWindow對象,則先選擇最后加上的UIWindow對象。2、若UIWindow對象能響應(yīng)這個觸摸事件,則繼續(xù)向其子視圖傳遞,向子視圖傳遞時也是先傳遞給最后加上的子視圖。
3、若子視圖無法響應(yīng)該事件,則返回父視圖,再傳遞給倒數(shù)第二個加入該父視圖的子視圖。
[圖片上傳失敗...(image-95c4b4-1523776714398)]
例如上面這張圖,C在B的后面加入,E在F的后面加入。那么尋找最佳響應(yīng)者的順序就是:1、UIWindow對象將事件傳遞給視圖A,A判斷自己能否響應(yīng)觸摸事件,如果能響應(yīng),則繼續(xù)傳遞給其子視圖。
2、如果A能響應(yīng)觸摸事件,由于A有兩個子視圖B,C,而C又在B的后面加入的,所以A視圖再把觸摸事件傳遞給C,C再判斷自己能否響應(yīng)觸摸事件,若能則繼續(xù)傳遞給其子視圖,若不能,則A視圖再將觸摸事件傳遞給B視圖。
3、如果C能響應(yīng)觸摸事件,C視圖也有兩個子視圖,分別是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時,不能響應(yīng)事件。
尋找最佳響應(yīng)者的原理
hitTest:withEvent:
每個UIView都有一個hitTest:withEvent:方法。這個方法是尋找最佳響應(yīng)者的核心方法,同時又是傳遞事件的橋梁。它的作用是詢問事件在當(dāng)前視圖中的響應(yīng)者。hitTest:withEvent:返回一個UIView對象,作為當(dāng)前視圖層次中的響應(yīng)者。其默認(rèn)實(shí)現(xiàn)是:
若當(dāng)前視圖無法響應(yīng)事件,則返回nil。
若當(dāng)前視圖能響應(yīng)事件,但無子視圖可響應(yīng)事件,則返回當(dāng)前視圖。
若當(dāng)前視圖能響應(yīng)事件,同時有子視圖能響應(yīng),則返回子視圖層次中的事件響應(yīng)者。
開始時UIApplication調(diào)用UIWindow的hitTest:withEvent:方法將觸摸事件傳遞給UIWindow,如果UIWindow能夠響應(yīng)觸摸事件,則調(diào)用hitTest:withEvent:將事件傳遞給其子視圖并詢問子視圖上的最佳響應(yīng)者,這樣一級一級傳遞下去,獲取最終的最佳響應(yīng)者。
hitTest:withEvent:的代碼實(shí)現(xiàn)大致如下:
????//3種狀態(tài)無法響應(yīng)事件
?????if?(self.userInteractionEnabled?==?NO?||?self.hidden?==?YES?||??self.alpha?<=?0.01)?return?nil;?
????//觸摸點(diǎn)若不在當(dāng)前視圖上則無法響應(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];?
????????//詢問子視圖層級中的最佳響應(yīng)視圖
????????UIView?*fitView?=?[childView?hitTest:childP?withEvent:event];?
????????if?(fitView)?
????????{
????????????//如果子視圖中有更合適的就返回
????????????return?fitView;?
????????}
????}?
????//沒有在子視圖中找到更合適的響應(yīng)視圖,那么自身就是最合適的
????return?self;
}
注意這里的方法pointInside:withEvent:,這個方法是判斷觸摸點(diǎn)是否在視圖范圍內(nèi)。默認(rèn)的實(shí)現(xiàn)是如果觸摸點(diǎn)在視圖范圍內(nèi)則返回YES,否則返回NO。
下面我們在上圖中的每個視圖層次中添加三個方法來驗(yàn)證之前的分析:
????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)擊視圖,打印出來的結(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的那部分的視圖的時候,我們打印看看:
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:]
通過打印結(jié)果我們發(fā)現(xiàn),觸摸事件壓根就沒有傳遞到C視圖這里,這是為什么呢?
原來,觸摸事件最早傳遞到B視圖,然后調(diào)用B視圖的hitTest:withEvent:方法,在這個方法中會調(diào)用pointInside:withEvent:來判斷觸摸點(diǎn)是否在視圖范圍內(nèi),這里由于觸摸的點(diǎn)是在A視圖的那部分,所以不在B視圖的那部分,因此返回NO。這樣觸摸事件就傳遞到了A視圖,由于A可以響應(yīng)觸摸事件,而A又沒有子視圖,所以最終的最佳響應(yīng)者就是A視圖。
那么這顯然不是我們希望看到的,我們希望的是當(dāng)觸摸C時,不管觸摸的是C的哪里,C都能成為最佳響應(yīng)者響應(yīng)觸摸事件。
要解決這個問題也很容易,我們只需要在B視圖中重寫pointInside:withEvent:方法。
????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)
通過hitTest:withEvent:我們已經(jīng)找到了最佳響應(yīng)者,下面要做的事就是讓這個最佳響應(yīng)者響應(yīng)觸摸事件。這個最佳響應(yīng)者對于觸摸事件擁有決定權(quán),它可以決定是自己一個響應(yīng)這個事件,也可以自己響應(yīng)之后還把它傳遞給其他響應(yīng)者。這個由響應(yīng)者構(gòu)成的就是響應(yīng)鏈。
響應(yīng)者對于事件的響應(yīng)和傳遞都是在touchesBegan:withEvent:這個方法中完成的。該方法默認(rèn)的實(shí)現(xiàn)是將該方法沿著響應(yīng)鏈往下傳遞
響應(yīng)者對于接收到的事件有三種操作:
1、默認(rèn)的操作。不攔截,事件會沿著默認(rèn)的響應(yīng)鏈自動往下傳遞。
2、攔截,不再往下分發(fā)事件,重寫touchesBegan:withEvent:方法,不調(diào)用父類的touchesBegan:withEvent:方法。
3、不攔截,繼續(xù)往下分發(fā)事件,重新touchesBegan:withEvent:方法,并調(diào)用父類的touchesBegan:withEvent:方法。
我們一般在編寫代碼時,如果某個視圖響應(yīng)事件,會在該視圖類中重寫touchesBegan:withEvent:方法,但是并不會調(diào)用父類的
touchesBegan:withEvent:方法,這樣我們就把這個事件攔截下來了,不再沿著響應(yīng)鏈往下傳遞。那么我們?yōu)槭裁聪胍刂憫?yīng)鏈傳遞事件就要重寫父類的touchesBegan:withEvent:方法呢?因?yàn)楦割惖膖ouchesBegan:withEvent:方法默認(rèn)是向下傳遞的。我們重寫touchesBegan:withEvent:并調(diào)用父類的方法就是既對觸摸事件實(shí)現(xiàn)了響應(yīng),又將事件沿著響應(yīng)鏈傳遞了。
響應(yīng)鏈中的事件傳遞規(guī)則
每一個響應(yīng)者對象都有一個nextResponder方法,用來獲取響應(yīng)鏈中當(dāng)前響應(yīng)者對象的下一個響應(yīng)者。因此,如果事件的最佳響應(yīng)者確定了,那么整個響應(yīng)鏈也就確定了。
對于響應(yīng)者對象,默認(rèn)的nextResponde對象如下:
UIView
若視圖是UIViewController的View。則其nextResponder是UIViewController,若其只是單獨(dú)的視圖,則其nextResponder是其父視圖。UIViewController
若該視圖是window的根視圖,則其nextResponder為窗口對象,若其是由其他視圖控制器present的,則其nextResponder是presenting View Controller。UIWindownextResponder為UIApplication對象。
事件響應(yīng)鏈.png
上圖是官網(wǎng)對于響應(yīng)鏈的示例展示,如果最佳響應(yīng)者對象是UITextField,則響應(yīng)鏈為:
UITextField->UIView->UIView->UIViewController->UIWindow->UIApplication->UIApplicationDelegate.
現(xiàn)在我們可以猜想,在父類的touchesBegan:withEvent:方法中,可能調(diào)用了[self.nextResponder touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event]這樣來將事件沿著響應(yīng)鏈傳遞。
UIResponder、UIGestureRecognizer、UIControl的優(yōu)先級
不光UIResponder能響應(yīng)觸摸事件,UIGestureRecognizer和UIControl也能處理觸摸事件。
UIGestureRecognizer
我們首先來看一個場景
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ù),最后touchesCancelled:withEvent:函數(shù)確被調(diào)用,正確的應(yīng)該是最后touchesEnded:withEvent:函數(shù)被調(diào)用,這是怎么回事呢?Apple的解釋是:
window在將事件傳遞給最佳響應(yīng)者之前會把事件先傳給手勢識別器,然后再傳給最佳響應(yīng)者,當(dāng)手勢識別器已經(jīng)識別了手勢時,最佳響應(yīng)者對象會調(diào)用touchesCancelled:withEvent:方法終止對事件的響應(yīng)。
如果按照這個理論,上面的結(jié)果也應(yīng)該是先打印taped后打印-[AView touchesBegan:withEvent:]呀,為什么不是這樣呢?問題出在,打印taped并不代表是這個時候事件傳遞到了手勢識別器這里,而是手勢識別器這個時候正式識別了手勢。正式識別了這個手勢和事件被傳遞到了手勢識別器這里的時間是不一樣的。
那么我們怎樣才能知道事件是先傳遞給了最佳響應(yīng)者還是壽司識別器呢?只需要找到手勢識別器的響應(yīng)函數(shù)然后打印它們即可。手勢識別器的響應(yīng)函數(shù)和UIResponder的響應(yīng)函數(shù)非常相似:
-?(void)touchesMoved:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event;
-?(void)touchesEnded:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event;
-?(void)touchesCancelled:(NSSet<UITouch?*>?*)touches?withEvent:(UIEvent?*)event;
我們重寫一個單擊手勢類,繼承自UITapGestureRecognizer即可。在這個類里導(dǎo)入頭文件:
-?(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];
}
這樣我們就可以打印手勢識別器接收事件的時間。我們打印結(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:]
通過打印結(jié)果我們能夠很清楚的看到,事件最先傳遞給了手勢識別器,然后傳遞給了最佳響應(yīng)者,在手勢識別器識別成功手勢后,調(diào)用最佳響應(yīng)者的touchesCancelled:方法終止最佳響應(yīng)者對于事件的響應(yīng)。
下面再看一個情景:
多個手勢識別器.png
在上圖中,視圖A,B,C上都添加了手勢識別器,那么當(dāng)我們單擊C視圖的時候,事件是一個怎么樣的響應(yīng)過程呢?我們打印結(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這幾個視圖上面的手勢識別器,然后才傳遞給了最佳響應(yīng)者C視圖,A,UIView,B,C這幾個視圖的手勢識別器都識別了手勢之后,調(diào)用最佳響應(yīng)者的touchesCancelled:withEvent:方法來取消最佳響應(yīng)者對于事件的響應(yīng)。
再來運(yùn)行一下程序,打印執(zhí)行結(jié)果:
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這四個視圖上的手勢識別器接收事件的順序發(fā)生了變化,但是最佳響應(yīng)者CView一定是最后接收事件的,并且最后響應(yīng)的函數(shù)一定是CView上綁定的手勢識別器的函數(shù)。由此我們得出結(jié)論:
當(dāng)響應(yīng)鏈上有手勢識別器時,事件在傳遞過程中一定會先傳遞給響應(yīng)鏈上的手勢識別器,然后才傳遞給最佳響應(yīng)者,當(dāng)響應(yīng)鏈上的手勢識別了手勢后就會取消最佳響應(yīng)者對于事件的響應(yīng)。事件傳遞給響應(yīng)鏈上的手勢識別器時是亂序的,并不是按照響應(yīng)鏈從頂至底傳遞,但是最后響應(yīng)的函數(shù)還是響應(yīng)鏈最頂端的手勢識別器函數(shù)。
手勢識別器的三個屬性
@property(nonatomic)?BOOL?cancelsTouchesInView;@property(nonatomic)?BOOL?delaysTouchesBegan;
@property(nonatomic)?BOOL?delaysTouchesEnded;
先總結(jié)一下手勢識別器和UIResponder對于事件響應(yīng)的聯(lián)系:
Window先將事件傳遞給響應(yīng)鏈上的手勢識別器,再傳遞給UIResponder。
手勢識別器識別手勢期間,若果觸摸對象的狀態(tài)發(fā)生變化,都是先發(fā)送給手勢識別器,再發(fā)送給UIResponder。
若手勢識別器已經(jīng)成功識別了手勢,則停止UIResponder對于事件的響應(yīng),并停止向UIResponder發(fā)送事件。
若手勢識別器未能識別手勢,而此時觸摸并未結(jié)束,則停止向手勢識別器發(fā)送手勢,僅向UIResponder發(fā)送事件。
若手勢識別器未能識別手勢,而此時觸摸已經(jīng)結(jié)束,則向UIResponder發(fā)送end狀態(tài)的touch事件以停止對事件的響應(yīng)。
1.cancelsTouchesInView
默認(rèn)為yes。表示當(dāng)手勢識別成功后,取消最佳響應(yīng)者對象對于事件的響應(yīng),并不再向最佳響應(yīng)者發(fā)送事件。若設(shè)置為No,則表示在手勢識別器識別成功后仍然向最佳響應(yīng)者發(fā)送事件,最佳響應(yīng)者仍響應(yīng)事件。2.delaysTouchesBegan
默認(rèn)為No,即在手勢識別器識別手勢期間,觸摸對象狀態(tài)發(fā)生變化時,都會發(fā)送給最佳響應(yīng)者,若設(shè)置成yes,則在識別手勢期間,觸摸狀態(tài)發(fā)生變化時不會發(fā)送給最佳響應(yīng)者。3.delaysTouchesEnded
默認(rèn)為NO。默認(rèn)情況下當(dāng)手勢識別器未能識別手勢時,若此時觸摸已經(jīng)結(jié)束,則會立即通知Application發(fā)送狀態(tài)為end的touch事件給最佳響應(yīng)者以調(diào)用 touchesEnded:withEvent: 結(jié)束事件響應(yīng);若設(shè)置為YES,則會在手勢識別失敗時,延遲一小段時間(0.15s)再調(diào)用響應(yīng)者的 touchesEnded:withEvent:。
UIControl
UIControl是系統(tǒng)提供的能夠以target-action模式處理觸摸事件的控件,iOS中UIButton、UISegmentedControl、UISwitch等控件都是UIControl的子類。當(dāng)UIControl跟蹤到觸摸事件時,會向其上添加的target發(fā)送事件以執(zhí)行action。值得注意的是,UIConotrol是UIView的子類,因此本身也具備UIResponder應(yīng)有的身份。
看下面一種情景
UIButton.png
圖中視圖A,B,C上都添加有單擊手勢,C上面的黑色按鈕添加有action。
當(dāng)我們點(diǎn)擊C上面的黑色按鈕時,看打印結(jié)果:
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ù)最后都沒有響應(yīng),而是響應(yīng)的黑色按鈕綁定的action。我們再在黑色按鈕上面加一個單擊手勢,然后單擊黑色按鈕,看打印結(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上面添加了手勢后,UIControl不會響應(yīng)自己的action。
因此得出結(jié)論:
UIControl會阻止父視圖上的手勢識別器的行為,也就是UIControl的執(zhí)行優(yōu)先級比父視圖上面的UIGestureRecognizer要高,但是比UIControl自身的UIGestureRecognizer優(yōu)先級要低。
●編號418,輸入編號直達(dá)本文
●輸入m獲取文章目錄
推薦↓↓↓程序員求職面試
更多推薦《25個技術(shù)類微信公眾號》
涵蓋:程序人生、算法與數(shù)據(jù)結(jié)構(gòu)、黑客技術(shù)與網(wǎng)絡(luò)安全、大數(shù)據(jù)技術(shù)、前端開發(fā)、Java、Python、Web開發(fā)、安卓開發(fā)、iOS開發(fā)、C/C++、.NET、Linux、數(shù)據(jù)庫、運(yùn)維等。
總結(jié)
以上是生活随笔為你收集整理的屏幕旋转之后的触摸坐标_iOS 中触摸事件传递和响应原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 维度和指标(metrics and di
- 下一篇: 虾皮市场中店铺定位是什么,如何做好产品线