iOS中觸摸事件傳遞和響應原理

系統(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:]

這和我們的分析是一致的。

自定義hitTest:withEvent:.png

大家看一下上面的圖铡原,其中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對象。
事件響應鏈.png

上圖是官網對于響應鏈的示例展示碧库,如果最佳響應者對象是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

我們首先來看一個場景


UIGestureRecognizer.png

我們給上圖中的黃色視圖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:方法終止了最佳響應者對于事件的響應渐排。
下面再看一個情景:

多個手勢識別器.png

在上圖中炬太,視圖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應有的身份禁添。
看下面一種情景

多個手勢識別器和button.png

圖中視圖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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市治专,隨后出現的幾起案子卖陵,更是在濱河造成了極大的恐慌,老刑警劉巖张峰,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泪蔫,死亡現場離奇詭異,居然都是意外死亡喘批,警方通過查閱死者的電腦和手機撩荣,發(fā)現死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饶深,“玉大人餐曹,你說我怎么就攤上這事〉欣澹” “怎么了台猴?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俱两。 經常有香客問我饱狂,道長,這世上最難降的妖魔是什么锋华? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任嗡官,我火速辦了婚禮,結果婚禮上毯焕,老公的妹妹穿的比我還像新娘衍腥。我一直安慰自己磺樱,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布婆咸。 她就那樣靜靜地躺著竹捉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尚骄。 梳的紋絲不亂的頭發(fā)上块差,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音倔丈,去河邊找鬼憨闰。 笑死,一個胖子當著我的面吹牛需五,可吹牛的內容都是我干的鹉动。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼宏邮,長吁一口氣:“原來是場噩夢啊……” “哼泽示!你這毒婦竟也來了?” 一聲冷哼從身側響起蜜氨,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤械筛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后飒炎,有當地人在樹林里發(fā)現了一具尸體埋哟,經...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年郎汪,在試婚紗的時候發(fā)現自己被綠了定欧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡怒竿,死狀恐怖砍鸠,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情耕驰,我是刑警寧澤爷辱,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站朦肘,受9級特大地震影響饭弓,放射性物質發(fā)生泄漏。R本人自食惡果不足惜媒抠,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一弟断、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趴生,春花似錦阀趴、人聲如沸昏翰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棚菊。三九已至,卻和暖如春叔汁,著一層夾襖步出監(jiān)牢的瞬間统求,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工据块, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留码邻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓另假,卻偏偏與公主長得像冒滩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浪谴,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容

  • 系統(tǒng)響應階段 1.手指觸碰屏幕,屏幕感受到觸摸后因苹,將事件交由IOKit來處理苟耻。 2.IOKIT將觸摸事件封裝成IO...
    雪山飛狐_91ae閱讀 7,379評論 4 37
  • 在iOS開發(fā)中經常會涉及到觸摸事件。本想自己總結一下扶檐,但是遇到了這篇文章凶杖,感覺總結的已經很到位,特此轉載款筑。作者:L...
    WQ_UESTC閱讀 6,013評論 4 26
  • 本文主要講解iOS觸摸事件的一系列機制智蝠,涉及的問題大致包括: 觸摸事件由觸屏生成后如何傳遞到當前應用? 應用接收觸...
    baihualinxin閱讀 1,210評論 0 9
  • 觸摸事件的生命周期 當我們手指觸碰屏幕的那一刻奈梳,一個觸摸事件便產生了杈湾。經過進程間通信,觸摸事件被傳遞到合適的應用之...
    Gintok閱讀 1,356評論 0 3
  • 轉載: https://blog.csdn.net/qq871531334/article/details/822...
    NicooYang閱讀 1,591評論 0 9