iOS 事件(UITouch魔招、UIControl、UIGestureRecognizer)傳遞機制

gitHub地址 :**** 響應(yīng)鏈Demo

文章有點長五辽,如果只是想了解大概過程的办斑,可以直接看后面的總結(jié)

一.觸摸、事件杆逗、響應(yīng)者

1. UITouch

源起觸摸

  • 一個手指一次觸摸屏幕乡翅,就對應(yīng)生成一個UITouch對象。多個手指同時觸摸屏幕罪郊,生成多個UITouch對象蠕蚜。

  • 多個手指先后觸摸,系統(tǒng)會根據(jù)觸摸的位置判斷是否更新同一個UITouch對象悔橄。若兩個手指一前一后觸摸同一個位置(即雙擊)靶累,那么第一次觸摸時生成一個UITouch對象,第二次觸摸會更新這個UITouch對象癣疟,這是該UITouch對象的Tap Count屬性值從1變成2挣柬,若兩個手指一前一后觸摸的位置不同,將會生成兩個UITouch對象睛挚,兩者之間沒有聯(lián)系邪蛔。

  • 每個UITouch對象記錄了觸摸的一些信息,包括觸摸時間扎狱、位置侧到、階段、所處的視圖委乌、窗口等信息床牧。

// 觸摸的各個階段狀態(tài) // 例如當手指移動時,會更新phase屬性到UITouchPhaseMoved遭贸;// 手指離屏后,更新到UITouchPhaseEndedtypedef NS_ENUM(NSInteger, UITouchPhase) {    UITouchPhaseBegan,             // whenever a finger touches the surface.    UITouchPhaseMoved,             // whenever a finger moves on the surface.    UITouchPhaseStationary,        // whenever a finger is touching the surface but hasn't moved since the previous event.    UITouchPhaseEnded,             // whenever a finger leaves the surface.    UITouchPhaseCancelled,         // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face)};
  • 手指離開屏幕一段時間后心软,確定該UITouch對象不會再被更新壕吹,就釋放著蛙。

2.UIEvent

事件的真身

觸摸的目的是生成觸摸事件供響應(yīng)者響應(yīng),一個觸摸事件對應(yīng)一個UIEvent對象耳贬,其中的type屬性標識了事件的類型踏堡,事件有如下幾種類型:

typedef NS_ENUM(NSInteger, UIEventType) {    UIEventTypeTouches,    UIEventTypeMotion,    UIEventTypeRemoteControl,    UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),};

這里我們所說的事件具體指的是觸摸事件。

  • UIEvent對象中包含了觸發(fā)該對象的觸摸對象集合咒劲,因為一個觸摸事件可能是由多個手指同時觸摸產(chǎn)生的顷蟆。觸摸對象集合通過allTouches屬性獲取。

3.UIResponder

UIResponder是iOS中用于處理用戶事件的API腐魂,可以處理觸摸事件帐偎、按壓事件(3D touch)、遠程控制事件蛔屹、硬件運動事件削樊。可以通過touchesBegan兔毒、pressesBegan漫贞、motionBegan、remoteControlReceivedWithEvent等方法育叁,獲取到對應(yīng)的回調(diào)消息迅脐。UIResponder不只用來接收事件,還可以處理和傳遞對應(yīng)的事件豪嗽,如果當前響應(yīng)者不能處理仪际,則轉(zhuǎn)發(fā)給其他合適的響應(yīng)者處理。

應(yīng)用程序通過響應(yīng)者來接收和處理事件昵骤,響應(yīng)者可以是繼承自UIResponder的任何子類树碱,例如UIView、UIViewController变秦、UIApplication等成榜。當事件來到時,系統(tǒng)會將事件傳遞給合適的響應(yīng)者蹦玫,并且將其成為第一響應(yīng)者赎婚。

第一響應(yīng)者未處理的事件,將會在響應(yīng)者鏈中進行傳遞樱溉,傳遞規(guī)則由UIResponder的nextResponder決定挣输,可以通過重寫該屬性來決定傳遞規(guī)則。當一個事件到來時福贞,第一響應(yīng)者沒有接收消息撩嚼,則順著響應(yīng)者鏈向后傳遞。

二.尋找事件的第一響應(yīng)者

App接收到觸摸事件后,會被放入當前應(yīng)用程序的UIApplication維護的事件隊列中完丽。

由于事件一次只有一個恋技,但是能夠響應(yīng)的事件的響應(yīng)者眾多,所以這就存在一個尋找第一響應(yīng)者的過程逻族。

1. 事件自下而上傳遞

查找第一響應(yīng)者時蜻底,有兩個非常關(guān)鍵的API,查找第一響應(yīng)者就是通過不斷調(diào)用子視圖的這兩個API完成的聘鳞。

調(diào)用方法薄辅,獲取到被點擊的視圖,也就是第一響應(yīng)者抠璃。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

hitTest:withEvent:方法內(nèi)部會通過調(diào)用pointInside:這個方法站楚,來判斷點擊區(qū)域是否在視圖上,是則返回YES鸡典,不是則返回NO源请。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

具體流程:

  • 應(yīng)用程序接收到觸摸事件后,將事件放入UIApplication的事件隊列彻况,等到處理該事件時谁尸,將該事件出隊列,UIApplication將事件傳遞給窗口對象(UIWindow)纽甘,如果存在多個窗口良蛮,則優(yōu)先詢問后顯示的窗口

  • 如果窗口UIWindow不能響應(yīng)事件,則將事件傳遞給其他窗口悍赢;若窗口能響應(yīng)事件决瞳,則從后往前詢問窗口的子視圖。

  • 以此類推左权,如果視圖不能響應(yīng)事件皮胡,則將事件傳遞給同級的上一個子視圖;如果能響應(yīng)赏迟,就從后往前遍歷當前視圖的子視圖屡贺。

  • 如果當前視圖的子視圖都不能響應(yīng)事件,則當前視圖就是最合適的響應(yīng)者锌杀。

舉個例子:

如圖所示:

image

視圖層級如下(同一層級的視圖越在下面甩栈,表示越后添加):

image

現(xiàn)在假設(shè)在E視圖所處的屏幕位置觸發(fā)一個觸摸,應(yīng)用接收到這個觸摸事件事件后糕再,先將事件傳遞給UIWindow量没,然后自下而上開始在子視圖中尋找第一響應(yīng)者。事件傳遞的順序如下所示:

  • UIWindow將事件傳遞給UIViewController的視圖UIView,UIView判斷自身能響應(yīng)事件突想,將事件傳遞給子視圖A

  • A判斷自身能響應(yīng)該事件殴蹄,繼續(xù)將事件傳遞給C(因為視圖C比視圖B后添加究抓,因此優(yōu)先傳給C)。

  • C判斷自身能響應(yīng)事件饶套,繼續(xù)將事件傳遞給F(同理F比E后添加)漩蟆。

  • F判斷自身不能響應(yīng)事件垒探,C又將事件傳遞給E妓蛮。

  • E判斷自身能響應(yīng)事件,同時E已經(jīng)沒有子視圖圾叼,因此最終E就是第一響應(yīng)者蛤克。

2. hitTest函數(shù)本質(zhì)

上面講到了事件在響應(yīng)者之間傳遞的規(guī)則,視圖通過判斷自身能否響應(yīng)事件來決定是否繼續(xù)想子視圖傳遞夷蚊。

這里涉及到兩個問題:

  • 視圖判斷自身能否響應(yīng)事件的判斷依據(jù)是什么构挤?

  • 如果能響應(yīng),視圖是如何將事件傳遞給子視圖的惕鼓?

針對第一個問題:

首先我們要知道筋现,以下幾種狀態(tài)的視圖是無法響應(yīng)事件的:

  • 不允許交互:userInteractionEnabled = NO

  • 隱藏:hidden = YES 如果父視圖隱藏,那么子視圖也會隱藏箱歧,隱藏的視圖無法接收事件

  • 透明度:alpha < 0.01 如果設(shè)置一個視圖的透明度<0.01矾飞,會直接影響子視圖的透明度。alpha:0.0~0.01為透明呀邢。

其次洒沦,如果當前視圖可以響應(yīng)事件,還必須通過pointInside函數(shù)判斷价淌,觸摸點是否在當前視圖的坐標范圍內(nèi)申眼,如果不在當前視圖的坐標范圍內(nèi),則無法響應(yīng)蝉衣,如果在坐標范圍內(nèi)括尸,并且該視圖可以響應(yīng)事件,就進入下一步事件的傳遞病毡。

針對第二個問題:

hitTest:withEvent: 方法返回一個UIView對象濒翻,作為當前視圖層次中的響應(yīng)者。默認實現(xiàn)是:

  • 若當前視圖無法響應(yīng)事件剪验,則返回nil

  • 若當前視圖可以響應(yīng)事件肴焊,但無子視圖可以響應(yīng)事件,則返回自身作為當前視圖層次中的事件響應(yīng)者

  • 若當前視圖可以響應(yīng)事件功戚,同時有子視圖可以響應(yīng)娶眷,則從后往前遍歷子視圖,返回子視圖層次中的事件響應(yīng)者

  • 以此類推啸臀,直到找到的當前視圖可以響應(yīng)事件届宠,并且當前視圖沒有子視圖烁落,那么當前視圖就是第一響應(yīng)者。

依據(jù)以上的描述我們可以推測出hitTest:WithEvent:的默認實現(xiàn)大致如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{    //3種狀態(tài)無法響應(yīng)事件     if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil;     //觸摸點若不在當前視圖上則無法響應(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];         // 坐標系的轉(zhuǎn)換,把觸摸點在當前視圖上坐標轉(zhuǎn)換為在子視圖上的坐標        CGPoint childP = [self convertPoint:point toView:childView];         //詢問子視圖層級中的最佳響應(yīng)視圖        UIView *fitView = [childView hitTest:childP withEvent:event];         if (fitView)         {            //如果子視圖中有更合適的就返回            return fitView;         }    }     //沒有在子視圖中找到更合適的響應(yīng)視圖豌注,那么自身就是最合適的    return self;}

我們分別在上述示例的視圖層次中的每個視圖實現(xiàn)文件添加如下方法:

#pragma mark -------------------------- Override Methods- (void)touchesBegan:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"%s",__func__);    [super touchesBegan:touches withEvent:event];}- (void)touchesMoved:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"%s",__func__);    [super touchesMoved:touches withEvent:event];}- (void)touchesEnded:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"%s",__func__);    [super touchesEnded:touches withEvent:event];}- (void)touchesCancelled:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"%s",__func__);    [super touchesCancelled:touches withEvent:event];}- (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];}</uitouch *></uitouch *></uitouch *></uitouch *>

然后單點E視圖伤塌,打印如下:

 -[AView hitTest:withEvent:] -[AView pointInside:withEvent:] -[CView hitTest:withEvent:] -[CView pointInside:withEvent:] -[FView hitTest:withEvent:] -[FView pointInside:withEvent:] -[EView hitTest:withEvent:] -[EView pointInside:withEvent:] -[AView hitTest:withEvent:] -[AView pointInside:withEvent:] -[CView hitTest:withEvent:] -[CView pointInside:withEvent:] -[FView hitTest:withEvent:] -[FView pointInside:withEvent:] -[EView hitTest:withEvent:] -[EView pointInside:withEvent:] -[EView touchesBegan:withEvent:] -[CView touchesBegan:withEvent:] -[AView touchesBegan:withEvent:] -[EView touchesEnded:withEvent:] -[CView touchesEnded:withEvent:] -[AView touchesEnded:withEvent:]

從打印結(jié)果我們可以看到最終EView視圖先對事件進行了響應(yīng),同時將事件沿著響應(yīng)鏈進行傳遞轧铁。

以上打印結(jié)果我們會發(fā)現(xiàn)單機E視圖后每聪,從[AView hitTest:withEvent:]到 [EView pointInside:withEvent:] 的過程會執(zhí)行兩遍,這個問題我查找了一些資料齿风,但都沒有好的答案药薯,蘋果那邊的回復(fù)是這樣的:

Yes, it’s normal. The system may tweak the point being hit tested between the calls. Since hitTest should be a pure function with no side-effects, this should be fine.

具體詳見:https://lists.apple.com/archives/cocoa-dev/2014/Feb/msg00118.html

意思就是說hitTest是一個沒有副作用的純函數(shù),進行多次調(diào)用也不會對外產(chǎn)生影響救斑,因此系統(tǒng)可以多次調(diào)整調(diào)用之間被測試的點童本。

這里并沒有給出具體的調(diào)用兩次的原因,你也可以理解為系統(tǒng)為了精確觸摸的點脸候,而進行了多次調(diào)用穷娱,但為什么是兩次,我也沒找到相關(guān)答案运沦。

3.事件攔截

實際開發(fā)中我們經(jīng)常會遇到如下需求

image

事件攔截.gif

在Tabbar的Item上面添加提示視圖tipView泵额,當點擊提示視圖tipview,對應(yīng)的Item也進行響應(yīng)茶袒,并且提示視圖tipView消失梯刚。

很明顯,這里的提示視圖tipView是添加在Tabbar上面的薪寓,但是提示視圖tipView的位置又超出了Tabbar的區(qū)域亡资,這時我們點擊提示視圖tipView,會發(fā)現(xiàn)提示視圖tipView得不到響應(yīng)向叉。

我們看一下調(diào)用的堆棧:

image
image
image

從堆棧中我們得出如下分析:

  • 生成的觸摸事件首先傳到了UIWindow锥腻,然后UIWindow將事件傳遞給控制器的根視圖UILayoutContainerView,

  • UILayoutContainerView判斷自己可以響應(yīng)觸摸事件,然后將事件傳遞給子視圖Tabbar

  • 子視圖Tabbar判斷觸摸點并不在自己的坐標范圍內(nèi)母谎,因此返回nil瘦黑,

  • 這時UILayoutContainerView將事件傳遞其他子視圖UINavigationTransitionView,UINavigationTransitionView判斷自己可以響應(yīng)事件,就將事件時間傳遞給其子視圖UIViewControllerWrapperView

  • UIViewControllerWrapperView判斷自己可以響應(yīng)事件奇唤,就將事件傳遞給子視圖FJFFirstViewController控制器的View

  • FJFFirstViewController控制器的View判斷自己可以響應(yīng)事件幸斥,然后就將事件傳遞給子視圖AView,AView判斷點擊位置不在自己的坐標范圍咬扇,返回nil甲葬,所以FJFFirstViewController控制器的View就是第一響應(yīng)者。

從這邊的分析我們可以看出事件沒有傳遞到提示視圖tipView懈贺,在Tabbar這里就直接返回了经窖,因為Tabbar判斷點擊位置不在自己的坐標范圍內(nèi)坡垫。

因此我們需要做的就是修改Tabbar的hitTest:withEvent:函數(shù)里面判斷點擊位置是否在Tabbar坐標范圍的的判斷條件,也就是需要重寫TabBard的 pointInside:withEvent:方法画侣,判斷如果當前觸摸坐標在子視圖tipView上面冰悠,就返回YES,否則返回NO配乱;這樣一來時間就會最終傳遞到tipView上面溉卓,最終事件就會由tipView來響應(yīng)。

代碼如下:

#import "FJFTabbar.h"@implementation FJFTabbar//TabBar- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {    //將觸摸點坐標轉(zhuǎn)換到在CircleButton上的坐標    CGPoint pointTemp = [self convertPoint:point toView:self.indicateView];    //若觸摸點在CricleButton上則返回YES    if ([self.indicateView pointInside:pointTemp withEvent:event]) {        return YES;    }    //否則返回默認的操作    return [super pointInside:point withEvent:event];}@end

三.事件的響應(yīng)及傳遞

經(jīng)過Hit-Testing的過程后宪卿,UIApplication已經(jīng)知道了第一響應(yīng)者是誰的诵,接下來要做的事情就是:

  • 將事件傳遞給第一響應(yīng)者

  • 將事件沿著響應(yīng)鏈傳遞

A. 將事件傳遞給第一響應(yīng)者:

由于第一響應(yīng)者具有處理事件的最高優(yōu)先級万栅,因此UIApplication會先將事件傳遞給它供其處理佑钾。首先,UIApplication將事件通過 sendEvent: 傳遞給事件所屬的window烦粒,window同樣通過 sendEvent: 再將事件傳遞給hit-tested view休溶,即第一響應(yīng)者。過程如下:

UIApplication ——> UIWindow ——> hit-tested view

以點擊EView視圖為例扰她,在EView的 touchesBegan:withEvent:上斷點查看調(diào)用棧就能看清這一過程:

image

從這調(diào)用堆棧我們可以看出兽掰,UIApplication對于將事件傳遞給那個UIWindow是很明確的,UIWindow對于將事件傳遞給哪個視圖也是很明確的徒役。因為這些信息都放在了UIEvent的Touch事件里面孽尽。

但是這些信息又是什么時候放入到UIEvent內(nèi)部的呢?

可想而知因為Hit-Testing和SendEvent兩者中的UIEvent是同一個UIEvent,所以這應(yīng)該是在Hit-Testing尋找第一響應(yīng)者的過程中忧勿,填入UIEvent內(nèi)部的杉女。

B.將事件沿著響應(yīng)鏈傳遞:

因為每個響應(yīng)者必定都是UIResponder對象,通過4個響應(yīng)觸摸事件的方法來響應(yīng)事件鸳吸。每個UIResponder對象默認都已經(jīng)實現(xiàn)了這4個方法熏挎,但是默認不對觸摸事件做任何處理耿焊,單純只是將事件沿著響應(yīng)鏈傳遞软驰。若要截獲事件進行自定義的響應(yīng)操作冈钦,就要重寫相關(guān)的方法辕万。

第一響應(yīng)者接收到觸摸事件后措左,就具有對觸摸事件的處理權(quán)厢绝,它可以選擇自己處理這個事件炸庞,也可以將這個事件沿著響應(yīng)鏈傳遞給下一個響應(yīng)者最欠,這個由響應(yīng)者之間構(gòu)成的視圖鏈就稱之為響應(yīng)鏈呕乎。

需要注意的是积担,上一節(jié)所說的事件傳遞的目的是為尋找事件的最佳響應(yīng)者,是自下而上的傳遞楣嘁;這里的事件傳遞目的是響應(yīng)者做出對事件的響應(yīng)磅轻,這個過程是自上而下的珍逸。前者為“尋找”,后者為“響應(yīng)”聋溜。

響應(yīng)者對于事件的操作方式:

響應(yīng)者對于事件的攔截以及傳遞都是通過 touchesBegan:withEvent:方法控制的谆膳,該方法的默認實現(xiàn)是將事件沿著默認的響應(yīng)鏈往下傳遞。

響應(yīng)者對于接收到的事件有3種操作:

  • 不攔截撮躁,默認操作

    事件會自動沿著默認的響應(yīng)鏈往下傳遞

  • 攔截漱病,不再往下分發(fā)事件

    重寫 touchesBegan:withEvent:進行事件處理,不調(diào)用父類的 touchesBegan:withEvent:

  • 攔截把曼,繼續(xù)往下分發(fā)事件

    重寫 touchesBegan:withEvent:進行事件處理杨帽,同時調(diào)用父類的 touchesBegan:withEvent:將事件往下傳遞

響應(yīng)鏈中的事件傳遞規(guī)則:

每一個響應(yīng)者對象(UIResponder對象)都有一個nextResponder方法,用于獲取響應(yīng)鏈中當前對象的下一個響應(yīng)者嗤军。因此注盈,一旦事件的第一響應(yīng)者確定了,這個事件所處的響應(yīng)鏈就確定了叙赚。

對于響應(yīng)者對象老客,默認的 nextResponder 實現(xiàn)如下:

  • UIView

    若視圖是控制器的根視圖,則其nextResponder為控制器對象震叮;否則胧砰,其nextResponder為父視圖。

  • UIViewController

    若控制器的視圖是window的根視圖苇瓣,則其nextResponder為窗口對象尉间;若控制器是從別的控制器present出來的,則其nextResponder為presenting view controller击罪。

  • UIWindow

    nextResponder為UIApplication對象哲嘲。

  • UIApplication

    若當前應(yīng)用的app delegate是一個UIResponder對象,且不是UIView外邓、UIViewController或app本身撤蚊,則UIApplication的nextResponder為app delegate。

舉個例子:

image

事件響應(yīng)示例.png

如上圖所示损话,響應(yīng)者鏈如下:

  • 如果點擊UITextField后其會成為第一響應(yīng)者侦啸。

  • 如果textField未處理事件,則會將事件傳遞給下一級響應(yīng)者鏈丧枪,也就是其父視圖光涂。

  • 父視圖未處理事件則繼續(xù)向下傳遞,也就是UIViewController的View拧烦。

  • 如果控制器的View未處理事件忘闻,則會交給控制器處理。

  • 控制器未處理則會交給UIWindow恋博。

  • 然后會交給UIApplication齐佳。

  • 最后交給UIApplicationDelegate私恬,如果其未處理則丟棄事件。

UITextField ——> UIView ——> UIView ——> UIViewController ——> UIWindow ——> UIApplication ——> UIApplicationDelegation

圖中虛線箭頭是指若該UIView是作為UIViewController根視圖存在的炼吴,則其nextResponder為UIViewController對象本鸣;若是直接add在UIWindow上的,則其nextResponder為UIWindow對象硅蹦。

可以用以下方式打印一個響應(yīng)鏈中的每一個響應(yīng)對象荣德,在第一響應(yīng)者的 touchBegin:withEvent: 方法中調(diào)用即可(別忘了調(diào)用父類的方法)

- (void)printResponderChain {    UIResponder *responder = self;    printf("%s",[NSStringFromClass([responder class]) UTF8String]);    while (responder.nextResponder) {        responder = responder.nextResponder;        printf(" --> %s",[NSStringFromClass([responder class]) UTF8String]);    }}

以點擊EView為例,重寫EView的touch Begin:WithEvent:

- (void)touchesBegan:(NSSet<uitouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"%s",__func__);    [self printResponderChain];    [super touchesBegan:touches withEvent:event];}</uitouch *>

響應(yīng)鏈如下:

EView --> CView --> AView --> UIView --> FJFFirstViewController --> UIViewControllerWrapperView --> UINavigationTransitionView --> UILayoutContainerView --> UINavigationController --> UIViewControllerWrapperView --> UITransitionView --> UILayoutContainerView --> FJFTabBarViewController --> FJFWindow --> FJFApplication --> AppDelegate

另外如果有需要童芹,完全可以重寫響應(yīng)者的 nextResponder 方法來自定義響應(yīng)鏈涮瞻。

四.UIGestureRecognizer、UIControl

上面我們講述了UIResponder響應(yīng)觸摸事件的過程假褪,但除了UIResponder之外署咽,UIGestureRecognizer、UIControl同樣具備對事件的處理能力嗜价。

以下將通過結(jié)合具體的示例來講解UIGestureRecognizer和UIControl是如何處理觸摸事件的艇抠。

舉個例子:

image

代碼:

#pragma mark -------------------------- Life Circle- (void)viewDidLoad {    [super viewDidLoad];    self.title = @"分類";    // view tap    FJFTapView *tmpContainerView = [[FJFTapView alloc] initWithFrame:CGRectMake(50, 80, 260, 300)];    tmpContainerView.backgroundColor = [UIColor redColor];    FJFTapGestureRecognizer *tapGesture = [[FJFTapGestureRecognizer alloc] initWithTarget:self action:@selector(viewTap:)];    [tmpContainerView addGestureRecognizer:tapGesture];    [self.view addSubview:tmpContainerView];    // view longPress    FJFLongPressView *tmpLongPressView = [[FJFLongPressView alloc] initWithFrame:CGRectMake(50, 400, 260, 200)];    tmpLongPressView.backgroundColor = [UIColor grayColor];    FJFLongPressGestureRecognizer *longPressGesture = [[FJFLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(viewlongPress:)];    [tmpLongPressView addGestureRecognizer:longPressGesture];    [self.view addSubview:tmpLongPressView];    // button    FJFButton *tmpButton = [[FJFButton alloc] initWithFrame:CGRectMake(100, 50, 120, 80)];    tmpButton.backgroundColor = [UIColor greenColor];    [tmpButton setTitle:@"UIButton" forState:UIControlStateNormal];    [tmpButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];    [tmpButton addTarget:self action:@selector(tmpButtonClicked:) forControlEvents:UIControlEventTouchUpInside];    [tmpContainerView addSubview:tmpButton];    // imageControl    FJFImageControl *imageControl = [[FJFImageControl alloc] initWithFrame:CGRectMake(100, 150, 120, 80) title:@"imageControl" iconImageName:@"ic_red_box.png"];    imageControl.backgroundColor = [UIColor blueColor];    [imageControl addTarget:self action:@selector(imageControlTouch:) forControlEvents:UIControlEventTouchUpInside];    [tmpContainerView addSubview:imageControl];}#pragma mark -------------------------- Response Event// tap- (void)viewTap:(UITapGestureRecognizer *)tap {    NSLog(@"%s", __FUNCTION__);}// longPress- (void)viewlongPress:(UILongPressGestureRecognizer *)longPress {    NSLog(@"%s", __FUNCTION__);}// buttonClicked- (void)tmpButtonClicked:(UIButton *)sender {    NSLog(@"%s", __FUNCTION__);}// controlTouch- (void)imageControlTouch:(FJFImageControl *)imageControl {     NSLog(@"%s", __FUNCTION__);}

如代碼所示:

  • FJFTapView 添加了繼承自UITapGestureRecognizer的FJFTapGestureRecognizer 單擊手勢

  • FJFLongPressView 添加了繼承自UILongPressGestureRecognizer的FJFLongPressGestureRecognizer 長按手勢

  • UIButton 添加 點擊事件

  • FJFImageControl 繼承自UIControl,也添加了點擊事件久锥,且UIButton和FJFImageControl都是FJFTapView的子視圖。

觀察各種情況的日志:

1.點擊FJFTapView:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFTapView touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewTap:][FJFTapView touchesCancelled:withEvent:]

2.長按FJFLongPressView:

[FJFLongPressGestureRecognizer touchesBegan:withEvent:][FJFLongPressView touchesBegan:withEvent:][FJFThreeViewController viewlongPress:][FJFLongPressView touchesCancelled:withEvent:][FJFLongPressGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewlongPress:]

3.點擊UIButton:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFButton touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFButton touchesEnded:withEvent:][FJFThreeViewController tmpButtonClicked:]

4.點擊FJFImageControl:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFImageControl touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewTap:][FJFImageControl touchesCancelled:withEvent:]

接下來我們一一解釋這些現(xiàn)象:

1. UIGestureRecognizer:

手勢分為離散型手勢(discrete gestures)和持續(xù)型手勢(continuous gesture)异剥。系統(tǒng)提供的離散型手勢包括點按手勢(UITapGestureRecognizer)和輕掃手勢(UISwipeGestureRecognizer)瑟由,其余均為持續(xù)型手勢。

兩者主要區(qū)別在于狀態(tài)變化過程:

離散型:

    識別成功:Possible —> Recognized    識別失斣┦佟:Possible —> Failed

持續(xù)型:

    完整識別:Possible —> Began —> [Changed] —> Ended    不完整識別:Possible —> Began —> [Changed] —> Cancel

A. 離散型手勢

從點擊FJFTapView的日志可以分析:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFTapView touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewTap:][FJFTapView touchesCancelled:withEvent:]
  • UIWindow在將事件傳遞給第一響應(yīng)者FJFTapView之前歹苦,先將事件傳遞給相關(guān)的手勢識別器FJFTapGestureRecognizer,

  • 若手勢成功識別事件督怜,就會取消第一響應(yīng)者FJFTapView對事件的響

  • 應(yīng)殴瘦;

  • 若手勢沒能識別事件,第一響應(yīng)者FJFTapView就會接手事件的處理号杠。

這里我們可以得出:UIGestureRecognizer比UIResponder具有更高的事件響應(yīng)的優(yōu)先級

這個結(jié)論我們也可以從官方文檔中得出:

A window delivers touch events to a gesture recognizer before it delivers them to the hit-tested view attached to the gesture recognizer. Generally, if a gesture recognizer analyzes the stream of touches in a multi-touch sequence and doesn’t recognize its gesture, the view receives the full complement of touches. If a gesture recognizer recognizes its gesture, the remaining touches for the view are cancelled.The usual sequence of actions in gesture recognition follows a path determined by default values of the cancelsTouchesInView, delaysTouchesBegan, delaysTouchesEndedproperties.

還有一點需要注意的是:

UIGestureRecognizer對事件的響應(yīng)也是通過touch相關(guān)的4個方法來實現(xiàn)的蚪腋,而這4個方法聲明在UIGestureRecognizerSubclass.h中。

image

而這里UIWindow之所以知道要把事件傳遞給哪些手勢識別器姨蟋,主要還是通過UIEvent里面的gestureRecognizers數(shù)組來獲取的屉凯,而數(shù)組里面的手勢識別器是在Hit-Test View尋找第一響應(yīng)者過程中填充的。

image

這里UIWindow會取出UIEvent里面的gestureRecognizers數(shù)組的手勢識別器眼溶,將事件傳遞給各個手勢識別器悠砚,如果有一個手勢識別器識別了事件,其他的手勢識別器就不會響應(yīng)該事件堂飞。

注意:這里取出gestureRecognizers數(shù)組的手勢識別器灌旧,沒有按照特定的順序绑咱,比如說從前往后或是從后往前,可以通過hook掉UIGestureRecognizer的touch相關(guān)方法,去追蹤得出枢泰。

因此我們可以分析日志:

  • UIWindow 先將事件傳遞給gestureRecognizers數(shù)組里的手勢識別器羡玛,然后再傳遞給第一響應(yīng)者FJFTapView.

  • 因為手勢識別器識別事件,需要一定時間宗苍,因此FJFTapView先調(diào)用了touchesBegan稼稿,這是因為FJFTapGestureRecognizer成功識別了事件,UIApplication就會取消FJFTapView對事件的響應(yīng)讳窟。

B. 持續(xù)型手勢

從點擊FJFLongPressView日志分析:

[FJFLongPressGestureRecognizer touchesBegan:withEvent:][FJFLongPressView touchesBegan:withEvent:][FJFThreeViewController viewlongPress:][FJFLongPressView touchesCancelled:withEvent:][FJFLongPressGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewlongPress:]

從日志我們可以看出長按手勢回調(diào)了兩次,我們通過分析兩次調(diào)用的堆棧:

第一次調(diào)用堆棧:

image

第二次調(diào)用堆棧

image

我們可以看出第一次調(diào)用是在runloop中通知監(jiān)聽的手勢識別器的觀察者让歼,來通知長按手勢識別器對長按事件進行響應(yīng),此時手勢識別器的state為UIGestureRecognizerStateBegan丽啡。

第二次調(diào)用是UIWindow 先將事件傳遞給UIEvent的gestureRecognizers數(shù)組里的手勢識別器谋右,然后長按手勢識別器FJFLongPressGestureRecognizer識別成功進行回調(diào),此時手勢識別器的state為UIGestureRecognizerStateEnded。

這里的調(diào)用邏輯其實跟單擊手勢識別器FJFTapGestureRecognizer相似补箍,主要區(qū)別在于長按手勢識別器FJFLongPressGestureRecognizer調(diào)用了兩次改执。

C. 總結(jié)

當觸摸發(fā)生或者觸摸的狀態(tài)發(fā)生變化時,UIWindow都會傳遞事件尋求響應(yīng)坑雅。

-UIWindow先將觸摸事件傳遞給響應(yīng)鏈上綁定的手勢識別器辈挂,再發(fā)送給觸摸對象對應(yīng)的第一響應(yīng)者。

  • 手勢識別器識別手勢期間裹粤,若觸摸對象的觸摸狀態(tài)發(fā)生變化终蒂,事件都是先發(fā)送給手勢識別器,再發(fā)送給第一響應(yīng)者遥诉。

  • 手勢識別器如果成功識別手勢拇泣,則通知UIApplication取消第一響應(yīng)者對于事件的響應(yīng),并停止向第一響應(yīng)者發(fā)送事件矮锈。

  • 如果手勢識別器未能識別手勢霉翔,而此時觸摸并未結(jié)束,則停止向手勢識別器發(fā)送事件苞笨,僅向第一響應(yīng)者發(fā)送事件债朵。

  • 如果手勢識別器未能識別手勢,且此時觸摸已經(jīng)結(jié)束猫缭,則向第一響應(yīng)者發(fā)送end狀態(tài)的touch事件葱弟,以停止對事件的響應(yīng)。

D. 拓展

手勢識別器的3個屬性:

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

a. cancelsTouchesInView:

默認為YES猜丹。表示當手勢識別器成功識別了手勢之后芝加,會通知Application取消響應(yīng)鏈對事件的響應(yīng),并不再傳遞事件給第一響應(yīng)者。若設(shè)置成NO藏杖,表示手勢識別成功后不取消響應(yīng)鏈對事件的響應(yīng)将塑,事件依舊會傳遞給第一響應(yīng)者。

以點擊FJFTapView為例蝌麸,將tapGesture.cancelsTouchesInView = NO;輸出日志如下:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFTapView touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewTap:][FJFTapView touchesEnded:withEvent:]

從日志我們可以看出点寥,即便FJFTapGestureRecognizer識別了點擊手勢,UIApplication也依舊將事件發(fā)送給FJFTapView.

b. delaysTouchesBegan:

默認為NO来吩。默認情況下手勢識別器在識別手勢期間敢辩,當觸摸狀態(tài)發(fā)生改變時,Application都會將事件傳遞給手勢識別器和第一響應(yīng)者弟疆;若設(shè)置成YES戚长,則表示手勢識別器在識別手勢期間,截斷事件怠苔,即不會將事件發(fā)送給第一響應(yīng)者同廉。

以點擊FJFTapView為例,將tapGesture.delaysTouchesBegan = YES;輸出日志如下:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewTap:]

從日志可以看出柑司,手勢識別器識別手勢期間迫肖,事件不會傳遞給FJFTapView,因此FJFTapView的touchesBegan:withEvent:不會被調(diào)用攒驰;而手勢識別器成功識別手勢后,獨吞了事件讼育,不會再傳遞給FJFTapView,因此只打印手勢識別器識別成功后手勢的綁定函數(shù)。

c. delaysTouchesEnded:

默認為YES。當手勢識別失敗時,若此時觸摸已經(jīng)結(jié)束,會延遲一小段時間(0.15s)再調(diào)用響應(yīng)者的touchesEnded:withEvent:确买;若設(shè)置成NO斤讥,則在手勢識別失敗時會立即通知Application發(fā)送狀態(tài)為end的touch事件給第一響應(yīng)者以調(diào)用 touchesEnded:withEvent:結(jié)束事件響應(yīng)。

2.UIControl

UIControl是系統(tǒng)提供的能夠以target-action模式處理觸摸事件的控件湾趾,iOS中UIButton芭商、UISegmentedControl派草、UISwitch等控件都是UIControl的子類。

值得注意的是铛楣,UIConotrol是UIView的子類近迁,因此本身也具備UIResponder應(yīng)有的身份。

UIControl作為控件類的基類簸州,它是一個抽象基類鉴竭,我們不能直接使用UIControl類來實例化控件,它只是為控件子類定義一些通用的接口岸浑,并提供一些基礎(chǔ)實現(xiàn)搏存,以在事件發(fā)生時,預(yù)處理這些消息并將它們發(fā)送到指定目標對象上助琐。

關(guān)于UIControl祭埂,此處介紹兩點:

  • target-action機制

  • 觸摸事件優(yōu)先級

Target-Action機制

Target-action是一種設(shè)計模式,直譯過來就是”目標-行為”兵钮。當我們通過代碼為一個按鈕添加一個點擊事件時蛆橡,通常是如下處理:

[button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];
image

注:圖片來源于官方文檔****Cocoa Application Competencies for iOS – Target Action

即當事件發(fā)生時,事件會被發(fā)送到控件對象中掘譬,然后再由這個控件對象去觸發(fā)target對象上的action行為泰演,來最終處理事件。因此葱轩,Target-Action機制由兩部分組成:即目標對象Target和行為Selector睦焕。目標對象指定最終處理事件的對象,而行為Selector則是處理事件的方法靴拱。

UIControl作為能夠響應(yīng)事件的控件垃喊,必然也需要待事件交互符合條件時才去響應(yīng),因此也會跟蹤事件發(fā)生的過程袜炕。不同于UIResponder以及UIGestureRecognizer通過touches系列方法跟蹤本谜,UIControl有其獨特的跟蹤方式:

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event {    NSLog(@"%s",__func__);    return YES;}- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event {    NSLog(@"%s",__func__);    return YES;}- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event {    NSLog(@"%s",__func__);}- (void)cancelTrackingWithEvent:(nullable UIEvent *)event {    NSLog(@"%s",__func__);}

這4個方法和UIResponder的那4個方法幾乎吻合,只不過UIControl只能接收單點觸控偎窘,因此接收的參數(shù)是單個UITouch對象乌助。這幾個方法的職能也和UIResponder一致,用來跟蹤觸摸的開始陌知、滑動他托、結(jié)束、取消仆葡。不過赏参,UIControl本身也是UIResponder,因此同樣有touches系列的4個方法。事實上登刺,UIControl的 Tracking 系列方法是在touch 系列方法內(nèi)部調(diào)用的籽腕。比如 beginTrackingWithTouch 是在 touchesBegan 方法內(nèi)部調(diào)用的, 因此它雖然也是UIResponder纸俭,但touches 系列方法的默認實現(xiàn)和UIResponder本類還是有區(qū)別的皇耗。

我們來分析下FJFButton的日志輸出以及調(diào)用堆棧:

日志輸出:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFButton touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFButton touchesEnded:withEvent:][FJFThreeViewController tmpButtonClicked:]

調(diào)用堆棧:

image

FJFButton調(diào)用堆棧.png

從以上信息,我們可以分析:

  • UIWindow 首先將事件傳遞給響應(yīng)鏈上綁定的手勢識別器FJFTapGestureRecognizer揍很,再傳遞給第一響應(yīng)者FJFButton

  • 手勢識別器FJFTapGestureRecognizer和第一響應(yīng)者FJFButton分別調(diào)用touch相關(guān)方法對事件進行識別郎楼,

  • 最終第一響應(yīng)者FJFButton對事件進行響應(yīng)調(diào)用 sendAction:to:forEvent:將target、action以及event對象發(fā)送給UIApplication窒悔,UIApplication對象再通過 sendAction:to:from:forEvent:向target發(fā)送action呜袁。

通過這個結(jié)果,我們會疑問:UIControl比其父視圖上的手勢識別器具有更高的事件響應(yīng)優(yōu)先級?

接下來我們看下繼承自UIControl的FJFImageControl的日志和調(diào)用堆棧:

日志輸出:

[FJFTapGestureRecognizer touchesBegan:withEvent:][FJFImageControl touchesBegan:withEvent:][FJFTapGestureRecognizer touchesEnded:withEvent:][FJFThreeViewController viewTap:][FJFImageControl touchesCancelled:withEvent:]

調(diào)用堆棧:

image

從以上信息简珠,我們又可以得出::UIControl比其父視圖上的手勢識別器的優(yōu)先級來的低阶界?

經(jīng)驗證系統(tǒng)提供的有默認action操作的UIControl,例如UIbutton聋庵、UISwitch等的單擊膘融,UIControl的響應(yīng)優(yōu)先級比手勢識別器高,而對于自定義的UIControl,響應(yīng)的優(yōu)先級比手勢低祭玉。

至于為什么會這樣氧映,沒找到具體原因,但測試的結(jié)果脱货,推測系統(tǒng)應(yīng)該是依據(jù)UITouch的touchIdentifier來進行區(qū)別處理岛都。

Target-Action的管理:

UIControl通過addTarget方法和removeTarget方法來添加和刪除Target-Action的操作。


如果想獲取控件對象所有相關(guān)的target對象振峻,則可以調(diào)用allTargets方法臼疫,該方法返回一個集合。集合中可能包含NSNull對象扣孟,表示至少有一個nil目標對象多矮。

而如果想獲取某個target對象及事件相關(guān)的所有action,則可以調(diào)用actionsForTarget:forControlEvent:方法哈打。

不過,這些都是UIControl開放出來的接口讯壶。我們還是想要探究一下料仗,UIControl是如何去管理Target-Action的呢?

實際上伏蚊,我們在程序某個合適的位置打個斷點來觀察UIControl的內(nèi)部結(jié)構(gòu)立轧,可以看到這樣的結(jié)果:

image

從圖中我們可以看出,UIControl內(nèi)部實際上是有一個可變數(shù)組(_targetActions)來保存Target-Action,數(shù)組中的每個元素是一個UIControlTargetAction對象氛改。UIControlTargetAction類是一個私有類帐萎,內(nèi)部維護

@interface UIControlTargetAction : NSObject {    SEL _action;    BOOL _cancelled;    unsigned int _eventMask;// 事件類型,比如:UIControlEventTouchUpInside    id _target;}

這四個變量,UIControl正是依據(jù)UIControlTargetAction來對事件進行處理胜卤。

五.事件完整響應(yīng)鏈

  • 系統(tǒng)通過 IOKit.framework來處理硬件操作疆导,其中屏幕處理也通過IOKit完成(IOKit可能是注冊監(jiān)聽了屏幕輸出的端口)

  • 當用戶操作屏幕,IOKit收到屏幕操作葛躏,會將這次操作封裝為IOHIDEvent對象澈段。通過mach port(IPC進程間通信)將事件轉(zhuǎn)發(fā)給SpringBoard來處理。

  • SpringBoard是iOS系統(tǒng)的桌面程序舰攒。SpringBoard收到mach port發(fā)過來的事件败富,喚醒main runloop來處理。

  • main runloop將事件交給source1處理摩窃,source1會調(diào)用__IOHIDEventSystemClientQueueCallback()函數(shù)兽叮。

    函數(shù)內(nèi)部會判斷,是否有程序在前臺顯示猾愿,如果有則通過mach port將IOHIDEvent事件轉(zhuǎn)發(fā)給這個程序鹦聪。

    如果前臺沒有程序在顯示,則表明SpringBoard的桌面程序在前臺顯示匪蟀,也就是用戶在桌面進行了操作椎麦。

    __IOHIDEventSystemClientQueueCallback()函數(shù)會將事件交給source0處理,source0會調(diào)用__UIApplicationHandleEventQueue()函數(shù)材彪,函數(shù)內(nèi)部會做具體的處理操作观挎。

  • 例如用戶點擊了某個應(yīng)用程序的icon,會將這個程序啟動段化。

    應(yīng)用程序接收到SpringBoard傳來的消息嘁捷,會喚醒main runloop并將這個消息交給source1處理,source1調(diào)用__IOHIDEventSystemClientQueueCallback()函數(shù)显熏,在函數(shù)內(nèi)部會將事件交給source0處理雄嚣,并調(diào)用source0的__UIApplicationHandleEventQueue()函數(shù)。

    在__UIApplicationHandleEventQueue()函數(shù)中喘蟆,會將傳遞過來的IOHIDEvent轉(zhuǎn)換為UIEvent對象缓升。

  • 在函數(shù)內(nèi)部,將事件放入UIApplication的事件隊列蕴轨,等到處理該事件時港谊,將該事件出隊列,UIApplication將事件傳遞給窗口對象(UIWindow)橙弱,如果存在多個窗口歧寺,則從后往前詢問最上層顯示的窗口

  • 窗口UIWindow通過hitTest和pointInside操作燥狰,判斷是否可以響應(yīng)事件,如果窗口UIWindow不能響應(yīng)事件斜筐,則將事件傳遞給其他窗口龙致;若窗口能響應(yīng)事件,則從后往前詢問窗口的子視圖顷链。

  • 以此類推目代,如果當前視圖不能響應(yīng)事件,則將事件傳遞給同級的上一個子視圖蕴潦;如果能響應(yīng)像啼,就從后往前遍歷當前視圖的子視圖。

  • 如果當前視圖的子視圖都不能響應(yīng)事件潭苞,則當前視圖就是第一響應(yīng)者忽冻。

  • 找到第一響應(yīng)者,事件的傳遞的響應(yīng)鏈也就確定的此疹。

  • 如果第一響應(yīng)者非UIControl子類且響應(yīng)鏈上也沒有綁定手勢識別器UIGestureRecognizer;

  • 那么由于第一響應(yīng)者具有處理事件的最高優(yōu)先級僧诚,因此UIApplication會先將事件傳遞給它供其處理。首先蝗碎,UIApplication將事件通過 sendEvent: 傳遞給事件所屬的window湖笨,window同樣通過 sendEvent: 再將事件傳遞給hit-tested view,即第一響應(yīng)者,第一響應(yīng)者具有對事件的完全處理權(quán)蹦骑,默認對事件不進行處理慈省,傳遞給下一個響應(yīng)者(nextResponder);如果響應(yīng)鏈上的對象一直沒有處理該事件眠菇,則最后會交給UIApplication边败,如果UIApplication實現(xiàn)代理,會交給UIApplicationDelegate捎废,如果UIApplicationDelegate沒處理笑窜,則該事件會被丟棄。

  • 如果第一響應(yīng)者非UIControl子類但響應(yīng)鏈上也綁定了手勢識別器UIGestureRecognizer;

  • UIWindow會將事件先發(fā)送給響應(yīng)鏈上綁定的手勢識別器UIGestureRecognizer登疗,再發(fā)送給第一響應(yīng)者排截,如果手勢識別器能成功識別事件,UIApplication默認會向第一響應(yīng)者發(fā)送cancel響應(yīng)事件的命令;如果手勢識別器未能識別手勢辐益,而此時觸摸并未結(jié)束断傲,則停止向手勢識別器發(fā)送事件,僅向第一響應(yīng)者發(fā)送事件智政。如果手勢識別器未能識別手勢艳悔,且此時觸摸已經(jīng)結(jié)束,則向第一響應(yīng)者發(fā)送end狀態(tài)的touch事件女仰,以停止對事件的響應(yīng)猜年。

  • 如果第一響應(yīng)者是自定義的UIControl的子類同時響應(yīng)鏈上也綁定了手勢識別器UIGestureRecognizer;這種情況跟第一響應(yīng)者非UIControl子類但響應(yīng)鏈上也綁定了手勢識別器UIGestureRecognizer`處理邏輯一樣;

  • 如果第一響應(yīng)者是UIControl的子類且是系統(tǒng)類(UIButton、UISwitch)同時響應(yīng)鏈上也綁定了手勢識別器UIGestureRecognizer;

  • UIWindow會將事件先發(fā)送給響應(yīng)鏈上綁定的手勢識別器UIGestureRecognizer疾忍,再發(fā)送給第一響應(yīng)者乔外,如果第一響應(yīng)者能響應(yīng)事件,UIControl調(diào)用調(diào)用sendAction:to:forEvent:將target一罩、action以及event對象發(fā)送給UIApplication杨幼,UIApplication對象再通過 sendAction:to:from:forEvent:向target發(fā)送action。

六. 延伸閱讀

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末差购,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汉嗽,更是在濱河造成了極大的恐慌欲逃,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饼暑,死亡現(xiàn)場離奇詭異稳析,居然都是意外死亡,警方通過查閱死者的電腦和手機弓叛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門彰居,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撰筷,你說我怎么就攤上這事陈惰。” “怎么了毕籽?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵抬闯,是天一觀的道長。 經(jīng)常有香客問我影钉,道長画髓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任平委,我火速辦了婚禮奈虾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘廉赔。我一直安慰自己肉微,他們只是感情好,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布蜡塌。 她就那樣靜靜地躺著碉纳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪馏艾。 梳的紋絲不亂的頭發(fā)上劳曹,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天奴愉,我揣著相機與錄音,去河邊找鬼铁孵。 笑死锭硼,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蜕劝。 我是一名探鬼主播檀头,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岖沛!你這毒婦竟也來了暑始?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤婴削,失蹤者是張志新(化名)和其女友劉穎廊镜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馆蠕,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡期升,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了互躬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片播赁。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吼渡,靈堂內(nèi)的尸體忽然破棺而出容为,到底是詐尸還是另有隱情,我是刑警寧澤寺酪,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布坎背,位于F島的核電站,受9級特大地震影響寄雀,放射性物質(zhì)發(fā)生泄漏得滤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一盒犹、第九天 我趴在偏房一處隱蔽的房頂上張望懂更。 院中可真熱鬧,春花似錦急膀、人聲如沸沮协。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慷暂。三九已至,卻和暖如春晨雳,著一層夾襖步出監(jiān)牢的瞬間行瑞,已是汗流浹背奸腺。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蘑辑,地道東北人洋机。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像洋魂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子喜鼓,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容