系統(tǒng)響應階段
- 1.手指觸碰屏幕枪萄,屏幕感受到觸摸后隐岛,將事件交由IOKit來處理。
- 2.IOKIT將觸摸事件封裝成IOHIDEvent對象,并通過mach port傳遞給SpringBoard進程。
mach 是一個卡內基梅隆大學開發(fā)的用于支持操作系統(tǒng)研究的操作系統(tǒng)內核葵硕。mach是一個真正的內核赊颠,是UNIX中BSD的替代內核。
mach port是進程端口戚篙,各進程間通過它來通信。Springboard是一個系統(tǒng)進程,可以理解為桌面系統(tǒng)湘今,可以統(tǒng)一管理和分發(fā)系統(tǒng)接收到的觸摸事件。
- 3.SpringBoard由于接收到觸摸事件剪菱,因此觸發(fā)了系統(tǒng)進程的主線程的runloop的source回調
? ???發(fā)生觸摸事件的時候摩瞎,你有可能正在桌面上翻頁,也有可能正在頭條上看新聞孝常,如果是前者旗们,則觸發(fā)SpringBoard主線程的runloop的source0回調,將桌面系統(tǒng)交由系統(tǒng)進程去消耗构灸。而如果是后者上渴,則將觸摸事件通過IPC(Inter-Process Communication,進程間通信)傳遞給前臺App進程喜颁,后面的事便是App內部對于觸摸事件的響應了稠氮。
APP響應觸摸事件
- 1.App進程的mach port接收來自SpringBoard的觸摸事件,主線程的runloop被喚醒洛巢,觸發(fā)source1回調
- 2.source1回調又觸發(fā)了一個source0回調括袒,將接收到的IOHIDEvent對象封裝成UIEvent對象,此時App將正式對于觸摸事件的響應稿茉。
- 3.source0回調將觸摸事件添加到UIApplication的事件隊列锹锰,當觸摸事件出隊后UIApplication為觸摸事件尋找最佳響應者芥炭。
- 4.尋找到最佳響應者之后,接下來的事情便是事件在響應鏈中傳遞和響應恃慧。
觸摸 事件 響應者
觸摸
觸摸對象即UITouch對象园蝠。
一個手指觸摸屏幕,就會生成一個UITouch對象痢士,如果多個手指同時觸摸彪薛,就會生成多個UITouch對象。
多個手指先后觸摸怠蹂,如果系統(tǒng)判斷多個手指觸摸的是同一個地方善延,那么不會生成多個UITouch對象,而是更新這個UITouch對象城侧,改變其tap count易遣。如果多個手指觸摸的不是同一個地方,那就會生成多個UITouch對象嫌佑。
觸摸事件
觸摸事件即UIEvent豆茫。
UIEvent即對UITouch的一次封裝。由于一次觸摸事件并不止有一個觸摸對象屋摇,可能是多指同時觸摸揩魂。觸摸對象集合可以通過allTouches屬性來獲取。
響應者
響應者即UIResponder
下列實例都是UIResponder:
- UIView
- UIViewController
- UIApplication
- Appdelegate
? ???響應者響應觸摸事件是通過下列四個方法來實現的:
//手指觸碰屏幕炮温,觸摸開始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指在屏幕上移動
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指離開屏幕火脉,觸摸結束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//觸摸結束前,某個系統(tǒng)事件中斷了觸摸柒啤,例如電話呼入
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
尋找最佳響應者(Hit-Testing)
當App通過mach port得到這個觸摸事件時忘分,App中有那么多UIView或者UIViewController,到底應該給誰去響應呢白修?尋找最佳響應者就是找出這個優(yōu)先級最高的響應對象。
尋找最佳響應者的具體流程如下:
1.UIApplication首先將事件傳遞給UIWindow重斑,如果有多個UIWindow對象兵睛,則先選擇最后加上的UIWindow對象。
2.若UIWindow對象能響應這個觸摸事件窥浪,則繼續(xù)向其子視圖傳遞祖很,向子視圖傳遞時也是先傳遞給最后加上的子視圖。
-
3.若子視圖無法響應該事件漾脂,則返回父視圖假颇,再傳遞給倒數第二個加入該父視圖的子視圖。
尋找最佳響應者.png
例如上面這張圖骨稿,C在B的后面加入笨鸡,E在F的后面加入姜钳。那么尋找最佳響應者的順序就是:
1.UIWindow對象將事件傳遞給視圖A,A判斷自己能否響應觸摸事件,如果能響應形耗,則繼續(xù)傳遞給其子視圖哥桥。
2.如果A能響應觸摸事件,由于A有兩個子視圖B,C激涤,而C又在B的后面加入的拟糕,所以A視圖再把觸摸事件傳遞給C,C再判斷自己能否響應觸摸事件倦踢,若能則繼續(xù)傳遞給其子視圖送滞,若不能,則A視圖再將觸摸事件傳遞給B視圖辱挥。
3.如果C能響應觸摸事件犁嗅,C視圖也有兩個子視圖,分別是E和F般贼,但是由于E是在F之后加到C上面的愧哟,所以先傳遞到,由于E可以響應觸摸事件哼蛆,所以最終的最佳響應者就是E蕊梧。
視圖如何判斷自己能否響應觸摸事件?
下列情況下腮介,視圖不能響應觸摸事件:
- 1.觸摸點不在視圖范圍內肥矢。
- 2.不允許交互:視圖的userInteractionEnabled = NO。
- 3.隱藏:hidden = YES叠洗,如果視圖隱藏了甘改,則不能響應事件。
- 4.透明度:當視圖的透明度小于等于0.01時灭抑,不能響應事件十艾。
尋找最佳響應者的原理
hitTest:withEvent:
每個UIView都有一個hitTest:withEvent:方法。這個方法是尋找最佳響應者的核心方法腾节,同時又是傳遞事件的橋梁忘嫉。它的作用是詢問事件在當前視圖中的響應者。
hitTest:withEvent:返回一個UIView對象案腺,作為當前視圖層次中的響應者庆冕。其默認實現是:
- 若當前視圖無法響應事件,則返回nil劈榨。
- 若當前視圖能響應事件访递,但無子視圖可響應事件,則返回當前視圖同辣。
- 若當前視圖能響應事件拷姿,同時有子視圖能響應惭载,則返回子視圖層次中的事件響應者。
開始時UIApplication調用UIWindow的hitTest:withEvent:方法將觸摸事件傳遞給UIWindow跌前,如果UIWindow能夠響應觸摸事件棕兼,則調用hitTest:withEvent:將事件傳遞給其子視圖并詢問子視圖上的最佳響應者,這樣一級一級傳遞下去抵乓,獲取最終的最佳響應者伴挚。 hitTest:withEvent:的代碼實現大致如下:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//3種狀態(tài)無法響應事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
//觸摸點若不在當前視圖上則無法響應事件
if ([self pointInside:point withEvent:event] == NO) return nil;
//從后往前遍歷子視圖數組 同層次的后加入優(yōu)先
int count = (int)self.subviews.count;
for (int i = count - 1; i >= 0; i--)
{
// 獲取子視圖
UIView *childView = self.subviews[i];
// 坐標系的轉換,把觸摸點在當前視圖上坐標轉換為在子視圖上的坐標
CGPoint childP = [self convertPoint:point toView:childView];
//詢問子視圖層級中的最佳響應視圖
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView)
{
//如果子視圖中有更合適的就返回
return fitView;
}
}
//沒有在子視圖中找到更合適的響應視圖,那么自身就是最合適的
return self;
}
注意這里的方法pointInside:withEvent:灾炭,這個方法是判斷觸摸點是否在視圖范圍內茎芋。默認的實現是如果觸摸點在視圖范圍內則返回YES,否則返回NO蜈出。
下面我們在上圖中的每個視圖層次中添加三個方法來驗證之前的分析:
- (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__);
}
點擊視圖田弥,打印出來的結果是:
-[AView hitTest:withEvent:]
-[AView pointInside:withEvent:]
-[CView hitTest:withEvent:]
-[CView pointInside:withEvent:]
-[EView hitTest:withEvent:]
-[EView pointInside:withEvent:]
-[EView touchesBegan:withEvent:]
這和我們的分析是一致的。
大家看一下上面的圖铡原,其中A和B都是根視圖控制器的View的子視圖偷厦,C是加在B上的子視圖。當我們觸摸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:]
通過打印結果我們發(fā)現只泼,觸摸事件壓根就沒有傳遞到C視圖這里,這是為什么呢卵洗?
原來请唱,觸摸事件最早傳遞到B視圖,然后調用B視圖的hitTest:withEvent:方法过蹂,在這個方法中會調用pointInside:withEvent:
來斷觸摸點是否在視圖范圍內十绑,這里由于觸摸的點是在A視圖的那部分,所以不在B視圖的那部分酷勺,因此返回NO本橙。這樣觸摸事件就傳遞到了A視圖,由于A可以響應觸摸事件脆诉,而A又沒有子視圖勋功,所以最終的最佳響應者就是A視圖。
那么這顯然不是我們希望看到的库说,我們希望的是當觸摸C時,不管觸摸的是C的哪里片择,C都能成為最佳響應者響應觸摸事件潜的。
要解決這個問題也很容易,我們只需要在B視圖中重寫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];
}
我們判斷觸摸點位置是否在視圖C范圍內啰挪,如果在視圖C的范圍內信不,則直接返回YES。
觸摸事件的響應
通過hitTest:withEvent:
我們已經找到了最佳響應者亡呵,下面要做的事情就是讓這個最佳響應者響應觸摸事件抽活。這個最佳響應者對于觸摸事件擁有決定權,它可以決定是自己一個響應這個事件锰什,也可以自己響應之后還把它傳遞給其他響應者下硕。這個由響應者構成的就是響應鏈。
響應者對于事件的響應和傳遞都是在touchesBegan:withEvent:
這個方法中完成的汁胆。該方法默認的實現是將該方法沿著響應鏈往下傳遞
響應者對于接收到的事件有三種操作:
- 1.默認的操作梭姓。不攔截,事件會沿著默認的響應鏈自動往下傳遞嫩码。
- 2.攔截誉尖,不再往下分發(fā)事件,重寫
touchesBegan:withEvent:
方法铸题,不調用父類的touchesBegan:withEvent:
方法铡恕。 - 3.不攔截,繼續(xù)往下分發(fā)事件丢间,重新
touchesBegan:withEvent:
方法探熔,并調用父類的touchesBegan:withEvent:
方法。
我們一般在編寫代碼時千劈,如果某個視圖響應事件祭刚,會在該視圖類中重寫touchesBegan:withEvent:
方法,但是并不會調用父類的touchesBegan:withEvent:
方法墙牌,這樣我們就把這個事件攔截下來了涡驮,不再沿著響應鏈往下傳遞。那么我們?yōu)槭裁聪胍刂憫渹鬟f事件就要重寫父類的touchesBegan:withEvent:
方法呢喜滨?因為父類的touchesBegan:withEvent:
方法默認是向下傳遞的捉捅。我們重寫touchesBegan:withEvent:
并調用父類的方法就是既對觸摸事件實現了響應,又將事件沿著響應鏈傳遞了虽风。
響應鏈中的事件傳遞規(guī)則
每一個響應者對象都有一個nextResponder
方法棒口,用來獲取響應鏈中當前響應者對象的下一個響應者。因此辜膝,如果事件的最佳響應者確定了无牵,那么整個響應鏈也就確定了。
對于響應者對象厂抖,默認的nextResponder
對象如下:
-
UIView
若視圖是UIViewController的view茎毁。則其nextResponder
是UIViewController,若其只是單獨的視圖,則其nextResponder
是其父視圖七蜘。 -
UIViewController
若該viewController是window的rootViewController谭溉,則其nextResponder
為窗口對象,若其是由其他視圖控制器present的橡卤,則其nextResponder
是presenting View Controller扮念。 - UIWindow
nextResponder
為UIApplication對象。
上圖是官網對于響應鏈的示例展示碧库,如果最佳響應者對象是UITextField柜与,則響應鏈為:
- UITextField->UIView->UIView->UIViewController->UIWindow->UIApplication->UIApplicationDelegate.
現在我們可以猜想,在父類的touchesBegan:withEvent:方法中谈为,可能調用了[self.nextResponder touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event]這樣來將事件沿著響應鏈傳遞旅挤。
UIResponder、UIGestureRecognizer伞鲫、UIControl的優(yōu)先級
不光UIResponder能響應觸摸事件粘茄,UIGestureRecognizer和UIControl也能處理觸摸事件。
UIGestureRecognizer
我們首先來看一個場景
我們給上圖中的黃色視圖A添加tap事件:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
[tap addTarget:self action:@selector(tapGesture)];
[self addGestureRecognizer:tap];
添加點擊事件:
- (void)tapGesture{
NSLog(@"taped");
}
運行程序秕脓,點擊黃色視圖A柒瓣,看打印結果:
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:]
首先響應者A調用了touchesBegan:withEvent:
響應了tap。然后執(zhí)行了手勢識別器的函數吠架,最后touchesCancelled:withEvent:
函數確被調用芙贫,正確的應該是最后touchesEnded:withEvent:
函數被調用,這是怎么回事呢傍药?Apple的解釋是:
window在將事件傳遞給最佳響應者之前會把事件先傳給手勢識別器磺平,然后再傳給最佳響應者,當手勢識別器已經識別了手勢拐辽,最佳響應者對象會調用
touchesCancelled:withEvent:
方法終止對事件的響應拣挪。
如果按照這個理論,上面的結果也應該是先打印taped
后打印-[AView touchesBegan:withEvent:]
呀俱诸,為什么不是這樣呢菠劝?問題出在,打印taped
并不代表是這個時候事件傳遞到了手勢識別器這里睁搭,而是手勢識別器這個時候正式識別了手勢赶诊。
正式識別了這個手勢和事件被傳遞到了手勢識別器這里的時間是不一樣的。
那么我們怎樣才能知道事件是先傳遞給了最佳響應者還是壽司識別器呢园骆?只需要找到手勢識別器的響應函數然后打印它們即可舔痪。手勢識別器的響應函數和UIResponder的響應函數非常相似:
- (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;
我們重寫了一個單擊手勢類,繼承自UITapGestureRecognizer锌唾。在這個類里導入頭文件<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];
}
這樣我們就可以打印手勢識別器接收事件的時間锄码。我們打印結果:
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:]
通過打印結果我們能夠很清楚的看到,事件最先傳給了手勢識別器,然后傳遞給了最佳響應者巍耗,在手勢識別器識別成功手勢后,調用最佳響應者的touchesCancelled:
方法終止了最佳響應者對于事件的響應渐排。
下面再看一個情景:
在上圖中炬太,視圖A,B,C上都添加了手勢識別器,那么當我們單擊C視圖的時候驯耻,事件是一個怎么樣的響應過程呢亲族?我們打印結果看一下:
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這幾個視圖上面的手勢識別器可缚,然后才傳遞給了最佳響應者C視圖霎迫,A,UIView,B,C這幾個視圖的手勢識別器都識別了手勢之后,調用最佳響應者的touchesCancelled:withEvent:
方法來取消最佳響應者對于事件的響應帘靡。
再來運行一下程序知给,打印執(zhí)行結果:
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這四個視圖上的手勢識別器接收事件的順序發(fā)生了變化描姚,但是最佳響應者CView一定是最后接收事件的涩赢,并且最后響應的函數一定是CView上綁定的手勢識別器的函數。由此我們得出結論:
當響應鏈上有手勢識別器時轩勘,事件在傳遞過程中一定會先傳遞給響應鏈上的手勢識別器筒扒,然后才傳遞給最佳響應者,當響應鏈上的手勢識別了手勢后就會取消最佳響應者對于事件的響應绊寻。事件傳遞給響應鏈上的手勢識別器時是亂序的花墩,并不是按照響應鏈從頂至底傳遞,但是最后響應的函數還是響應鏈最頂端的手勢識別器函數澄步。
手勢識別器的三個屬性
@property(nonatomic) BOOL cancelsTouchesInView;
@property(nonatomic) BOOL delaysTouchesBegan;
@property(nonatomic) BOOL delaysTouchesEnded;
先總結一下手勢識別器和UIResponder對于事件響應的聯系:
- Window先將事件傳遞給響應鏈上的手勢識別器冰蘑,再傳遞給UIResponder。
- 手勢識別器識別手勢期間驮俗,如果觸摸對象的狀態(tài)發(fā)生變化懂缕,都是先發(fā)送給手勢識別器,再發(fā)送給UIResponder王凑。
- 若手勢識別器已經成功識別了手勢搪柑,則停止UIResponder對于事件的響應,并停止向UIResponder發(fā)送事件索烹。
- 若手勢識別器未能識別手勢工碾,而此時觸摸未結束,則停止向手勢識別器發(fā)送手勢百姓,僅向UIResponder發(fā)送事件渊额。
- 若手勢識別器未能識別手勢,而此時觸摸已經結束,則向UIResponder發(fā)送end狀態(tài)的touch事件以停止對事件的響應旬迹。
- 1火惊、cancelsTouchesInView
默認為yes。表示當手勢識別成功后奔垦,取消最佳響應者對象對于事件的響應屹耐,并不再向最佳響應者發(fā)送事件。若設置為No椿猎,則表示在手勢識別器識別成功后仍然向最佳響應者發(fā)送事件惶岭,最佳響應者仍響應事件,會調用touchesEnded:withEvent:
- 2犯眠、delaysTouchesBegan
默認為No按灶,即在手勢識別器識別手勢期間,觸摸對象狀態(tài)發(fā)生變化時筐咧,都會發(fā)送給最佳響應者鸯旁,調用touchesBegan:withEvent:
,若設置成yes嗜浮,則在識別手勢期間羡亩,觸摸狀態(tài)發(fā)生變化時不會發(fā)送給最佳響應者,不會調用touchesBegan:withEvent:
- 3.delaysTouchesEnded
默認為NO危融。默認情況下當手勢識別器未能識別手勢時畏铆,若此時觸摸已經結束,則會立即通知Application發(fā)送狀態(tài)為end的touch事件給最佳響應者以調用 touchesEnded:withEvent: 結束事件響應吉殃;若設置為YES辞居,則會在手勢識別失敗時,延遲一小段時間(0.15s)再調用響應者的 touchesEnded:withEvent:蛋勺。
UIControl
UIControl是系統(tǒng)提供的能夠以target-action模式處理觸摸事件的控件瓦灶,iOS中UIButton、UISegmentedControl抱完、UISwitch等控件都是UIControl的子類贼陶。當UIControl跟蹤到觸摸事件時,會向其上添加的target發(fā)送事件以執(zhí)行action巧娱。值得注意的是碉怔,UIConotrol是UIView的子類,因此本身也具備UIResponder應有的身份禁添。
看下面一種情景
圖中視圖A,B,C上都添加有單擊手勢撮胧,C上面的黑色按鈕添加有action。當我們點擊C上面的黑色按鈕時老翘,看打印結果:
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
我們看到芹啥,雖然事件都傳遞給了響應鏈上的手勢識別器锻离,但是這些手勢識別器綁定的函數最后都沒有響應,而是響應的黑色按鈕綁定的action墓怀。我們再在黑色按鈕上面加一個單擊手勢汽纠,然后單擊黑色按鈕,看打印結果:
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
可以看到傀履,當UIControl上面添加了手勢后疏虫,UIControl不會響應自己的action。
因此得出結論:
UIControl會阻止父視圖上的手勢識別器的行為啤呼,也就是UIControl的執(zhí)行優(yōu)先級比父視圖上面的UIGestureRecognizer要高,但是比UIControl自身的UIGestureRecognizer優(yōu)先級要低呢袱。
總結
系統(tǒng)觸摸事件響應鏈如下:
- 事件觸發(fā):主屏幕觸摸事件 --> 系統(tǒng)進程 --> App進程接收系統(tǒng)觸摸事件官扣,觸發(fā)source1回調 --> App進程觸發(fā)source0回調 --> source0回調將觸摸事件添加到Application事件隊列
-
尋找最佳響應者:首先通過
hitTest:withEvent:
尋找最佳響應者,順序為:UIApplication --> UIWindow --> UIViewController --> UIViewController.view --> view -
響應事件:再者通過
touchesBegan:withEvent:
來響應和傳遞這個觸摸事件羞福。默認順序為:最佳響應者 --> 最佳響應者的父View --> UIViewController.view --> UIViewController --> UIWindow --> UIApplication --> UIApplicationDelegate - 觸摸事件優(yōu)先級:UIGestureRecognizer > UIControl > 父view的UIGestureRecognizer > UIResponder
轉載參考自:iOS中觸摸事件傳遞和響應原理
Reference