歡迎光臨
每天分享高質量文章

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

作者:雪山飛狐_91ae
鏈接:https://www.jianshu.com/p/4aeaf3aa0c7e

系統響應階段

  • 1、手指觸碰屏幕,屏幕感受到觸摸後,將事件交由IOKit來處理。

  • 2、IOKIT將觸摸事件封裝成IOHIDEvent物件,並通過mach port傳遞給SpringBoard行程。

 

mach port是行程端口,各行程間通過它來通信。Springboard是一個系統行程,可以理解為桌面系統,可以統一管理和分發系統接收到的觸摸事件。

 

  • 3、SpringBoard由於接收到觸摸事件,因此觸發了系統行程的主執行緒的runloop的source回呼。

 

發生觸摸事件的時候,你有可能正在桌面上翻頁,也有可能正在頭條上看新聞,如果是前者,則觸發SpringBoard主執行緒的runloop的source0回呼,將桌面系統交由系統行程去消耗。而如果是後者,則將觸摸事件通過IPC傳遞給前臺APP行程,後面的事便是APP內部對於觸摸事件的響應了。

APP響應觸摸事件

  • 1、APP行程的mach port接收來自SpringBoard的觸摸事件,主執行緒的runloop被喚醒,觸發source1回呼。

  • 2、source1回呼又觸發了一個source0回呼,將接收到的IOHIDEvent物件封裝成UIEvent物件,此時APP將正式開始對於觸摸事件的響應。

  • 3、source0回呼將觸摸事件添加到UIApplication的事件佇列,當觸摸事件出隊後UIApplication為觸摸事件尋找最佳響應者。

  • 4、尋找到最佳響應者之後,接下來的事情便是事件在響應鏈中傳遞和響應。

觸摸 事件 響應者

觸摸

 

觸摸物件即UITouch物件。


一個手指觸摸屏幕,就會生成一個UITouch物件,如果多個手指同時觸摸,就會生成多個UITouch物件。


多個手指先後觸摸,如果系統判斷多個手指觸摸的是同一個地方,那麼不會生成多個UITouch物件,而是更新這個UITouch物件,改變其tap count。如果多個手指觸摸的不是同一個地方,那就會生成多個UITouch物件。

 

觸摸事件

 

觸摸事件即UIEvent。


UIEvent即對UITouch的一次封裝。由於一次觸摸事件並不止有一個觸摸物件,可能是多指同時觸摸。觸摸物件集合可以通過allTouches屬性來獲取。

 

響應者

 

響應者即UIResponser


下列實體都是UIResponser:

 

  • 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;
//觸摸結束前,某個系統事件中斷了觸摸,例如電話呼入
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

尋找最佳響應者(Hit-Testing)

當APP通過mach port得到這個觸摸事件時,APP中有那麼多UIView或者UIViewController,到底應該給誰去響應呢?尋找最佳響應者就是找出這個優先級最高的響應物件。

 

  • 尋找最佳響應者的具體流程如下:

  • 1、UIApplication首先將
    事件傳遞給視窗物件(UIWindow),如果有多個UIWindow物件,則先選擇最後加上的UIWindow物件。

  • 2、若UIWindow物件能響應這個觸摸事件,則繼續向其子視圖傳遞,向子視圖傳遞時也是先傳遞給最後加上的子視圖。

  • 3、若子視圖無法響應該事件,則傳回父視圖,再傳遞給倒數第二個加入該父視圖的子視圖。
    [圖片上傳失敗…(image-95c4b4-1523776714398)]
    例如上面這張圖,C在B的後面加入,E在F的後面加入。那麼尋找最佳響應者的順序就是:

  • 1、UIWindow物件將事件傳遞給視圖A,A判斷自己能否響應觸摸事件,如果能響應,則繼續傳遞給其子視圖。

  • 2、如果A能響應觸摸事件,由於A有兩個子視圖B,C,而C又在B的後面加入的,所以A視圖再把觸摸事件傳遞給C,C再判斷自己能否響應觸摸事件,若能則繼續傳遞給其子視圖,若不能,則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種狀態無法響應事件
     if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01return nil; 
    //觸摸點若不在當前視圖上則無法響應事件
    if ([self pointInside:point withEvent:event] == NOreturn nil; 
    //從後往前遍歷子視圖陣列 
    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:

 

自定義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:]

通過打印結果我們發現,觸摸事件壓根就沒有傳遞到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、攔截,不再往下分發事件,重寫touchesBegan:withEvent:方法,不呼叫父類的touchesBegan:withEvent:方法。

  • 3、不攔截,繼續往下分發事件,重新touchesBegan:withEvent:方法,並呼叫父類的touchesBegan:withEvent:方法。

 

我們一般在編寫代碼時,如果某個視圖響應事件,會在該視圖類中重寫touchesBegan:withEvent:方法,但是並不會呼叫父類的


touchesBegan:withEvent:方法,這樣我們就把這個事件攔截下來了,不再沿著響應鏈往下傳遞。那麼我們為什麼想要沿著響應鏈傳遞事件就要重寫父類的touchesBegan:withEvent:方法呢?因為父類的touchesBegan:withEvent:方法預設是向下傳遞的。我們重寫touchesBegan:withEvent:並呼叫父類的方法就是既對觸摸事件實現了響應,又將事件沿著響應鏈傳遞了。

響應鏈中的事件傳遞規則

每一個響應者物件都有一個nextResponder方法,用來獲取響應鏈中當前響應者物件的下一個響應者。因此,如果事件的最佳響應者確定了,那麼整個響應鏈也就確定了。


對於響應者物件,預設的
nextResponde物件如下:

 

  • UIView
    若視圖是UIViewController的View。則其
    nextResponder是UIViewController,若其只是單獨的視圖,則其nextResponder是其父視圖。

  • UIViewController
    若該視圖是window的根視圖,則其
    nextResponder為視窗物件,若其是由其他視圖控制器present的,則其nextResponder是presenting View Controller。

  • UIWindow
    nextResponder為UIApplication物件。

     

    事件響應鏈.png

    上圖是官網對於響應鏈的示例展示,如果最佳響應者物件是UITextField,則響應鏈為:

  • UITextField->UIView->UIView->UIViewController->UIWindow->UIApplication->UIApplicationDelegate.
    現在我們可以猜想,在父類的
    touchesBegan:withEvent:方法中,可能呼叫了[self.nextResponder touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event]這樣來將事件沿著響應鏈傳遞。

UIResponder、UIGestureRecognizer、UIControl的優先級

不光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響應了tap。然後執行了手勢識別器的函式,最後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即可。在這個類里匯入頭檔案:

 

- (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:方法來取消最佳響應者對於事件的響應。


再來運行一下程式,打印執行結果:

 

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這四個視圖上的手勢識別器接收事件的順序發生了變化,但是最佳響應者CView一定是最後接收事件的,並且最後響應的函式一定是CView上系結的手勢識別器的函式。由此我們得出結論:


當響應鏈上有手勢識別器時,事件在傳遞過程中一定會先傳遞給響應鏈上的手勢識別器,然後才傳遞給最佳響應者,當響應鏈上的手勢識別了手勢後就會取消最佳響應者對於事件的響應。事件傳遞給響應鏈上的手勢識別器時是亂序的,並不是按照響應鏈從頂至底傳遞,但是最後響應的函式還是響應鏈最頂端的手勢識別器函式。

手勢識別器的三個屬性

@property(nonatomic) BOOL cancelsTouchesInView;
@property(nonatomic) BOOL delaysTouchesBegan;
@property(nonatomic) BOOL delaysTouchesEnded;

先總結一下手勢識別器和UIResponder對於事件響應的聯繫:

 

  • Window先將事件傳遞給響應鏈上的手勢識別器,再傳遞給UIResponder。

  • 手勢識別器識別手勢期間,若果觸摸物件的狀態發生變化,都是先發送給手勢識別器,再發送給UIResponder。

  • 若手勢識別器已經成功識別了手勢,則停止UIResponder對於事件的響應,並停止向UIResponder發送事件。

  • 若手勢識別器未能識別手勢,而此時觸摸並未結束,則停止向手勢識別器發送手勢,僅向UIResponder發送事件。

  • 若手勢識別器未能識別手勢,而此時觸摸已經結束,則向UIResponder發送end狀態的touch事件以停止對事件的響應。

  • 1.cancelsTouchesInView
    預設為yes。表示當手勢識別成功後,取消最佳響應者物件對於事件的響應,並不再向最佳響應者發送事件。若設置為No,則表示在手勢識別器識別成功後仍然向最佳響應者發送事件,最佳響應者仍響應事件。

  • 2.delaysTouchesBegan
    預設為No,即在手勢識別器識別手勢期間,觸摸物件狀態發生變化時,都會發送給最佳響應者,若設置成yes,則在識別手勢期間,觸摸狀態發生變化時不會發送給最佳響應者。

  • 3.delaysTouchesEnded
    預設為NO。預設情況下當手勢識別器未能識別手勢時,若此時觸摸已經結束,則會立即通知Application發送狀態為end的touch事件給最佳響應者以呼叫 touchesEnded:withEvent: 結束事件響應;若設置為YES,則會在手勢識別失敗時,延遲一小段時間(0.15s)再呼叫響應者的 touchesEnded:withEvent:。

UIControl

UIControl是系統提供的能夠以target-action樣式處理觸摸事件的控制元件,iOS中UIButton、UISegmentedControl、UISwitch等控制元件都是UIControl的子類。當UIControl跟蹤到觸摸事件時,會向其上添加的target發送事件以執行action。值得註意的是,UIConotrol是UIView的子類,因此本身也具備UIResponder應有的身份。


看下麵一種情景

 

UIButton.png

 

圖中視圖A,B,C上都添加有單擊手勢,C上面的黑色按鈕添加有action。


當我們點擊C上面的黑色按鈕時,看打印結果:

 

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

我們看到,雖然事件都傳遞給了響應鏈上的手勢識別器,但是這些手勢識別器系結的函式最後都沒有響應,而是響應的黑色按鈕系結的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的執行優先級比父視圖上面的UIGestureRecognizer要高,但是比UIControl自身的UIGestureRecognizer優先級要低。

赞(0)

分享創造快樂