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

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)

分享創造快樂