iOS事件傳遞和響應(yīng)者鏈

iOS中的事件

觸摸事件蕴纳,加速事件(搖一搖)爵卒,遠(yuǎn)程控制事件(耳機(jī)線控,窗口播放)

以最常見的觸摸事件為例堡称,當(dāng)觸摸手機(jī)屏幕時操作系統(tǒng)會將這個事件添加到由UIApplication管理的事件隊列中(FIFO)UIApplication發(fā)送事件到應(yīng)用程序的主窗口(Window)Window會在圖層結(jié)構(gòu)中找到最合適的圖層來處理事件。


UIResponder

UIResponder類是專門用來響應(yīng)用戶的操作處理各種事件的艺演,iOS中大部分控件都繼承自UIResponder却紧,默認(rèn)響應(yīng)事件的方法如下(觸摸事件)

- (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;//觸摸結(jié)束胎撤,手機(jī)離開屏幕
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;//中斷晓殊,被手勢或者系統(tǒng)中斷

事件傳遞鏈

UIApplication傳遞事件到當(dāng)前Window是明確的,接下來就是從Window開始找最佳響應(yīng)視圖伤提,此過程有兩個重要的方法:

hitTest方法繼承自UIView(UIWindow是繼承自UIView的)巫俺。從UIApplication開始調(diào)用Window的hitTest方法,默認(rèn)是遞歸調(diào)用的肿男。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    return [super pointInside:point withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    return [super hitTest:point withEvent:event];
}

傳遞過程如下:

1.系統(tǒng)從UIApplication開始介汹,當(dāng)前window調(diào)用hitTest,hitTest內(nèi)部會通過以下條件判斷window能否能響應(yīng)事件

  • 不允許交互:userInteractionEnabled=NO
  • 隱藏:hidden = YES
  • 透明度:alpha < 0.01舶沛,alpha小于0.01為全透明

2.如果能響應(yīng)嘹承,該函數(shù)內(nèi)部會調(diào)用pointInside判斷當(dāng)前觸摸點是不是在視圖范圍內(nèi)

3.如果在window范圍內(nèi),開始反向遍歷window的子視圖列表subviews如庭,遍歷的同時會調(diào)用subviews中每個子視圖的hitTest叹卷,判斷邏輯和上面的一樣,如果找到循環(huán)就會停止坪它。

4.此過程會遞歸骤竹,直到找到最外層合適的view,最后返回的view就是最佳響應(yīng)視圖往毡。

一種hitTest可能的實現(xiàn)方式如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    if (!self.userInteractionEnabled|| self.hidden || self.alpha == 0.0){
        return nil;
    }
    if (![self pointInside:point withEvent:event]){
        return nil;
    }
    // 后加入的視圖在圖層上方蒙揣,所以反向遍歷是合理的
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--)
    {
        UIView *view = self.subviews[i];
        // 坐標(biāo)的轉(zhuǎn)換
        CGPoint subPoint = [self convertPoint:point toView:view];
        // 繼續(xù)遞歸
        UIView *lastView = [view hitTest:subPoint withEvent:event];
        if (lastView)
        {
            return lastView;
        }
    }
    return self;
}

以上,這就是事件傳遞過程卖擅,由內(nèi)往外的傳遞過程(從window開始到最外層視圖 )

此過程查找結(jié)束返回最終的view鸣奔,UIApplication會調(diào)用UIWindow的sendEvent墨技,從而觸發(fā)對應(yīng)的響應(yīng)方法:

PS:這里通過在UIWIndow中重寫sendEvent而不調(diào)用super的實現(xiàn),你會發(fā)現(xiàn)所有的點擊事件都不會觸發(fā)

- (void)sendEvent:(UIEvent *)event; 

以下是需要注意的點:

  • 實際調(diào)用hitTest過程挎狸,系統(tǒng)為了找到精準(zhǔn)的觸摸點會多次調(diào)用

  • 如果重寫hitTest返回self扣汪,傳遞過程就會終止,當(dāng)前view就是最合適的view锨匆;返回nil,傳遞也會終止崭别,父視圖superView就是最合適的view

  • 如果遍歷subviews的過程都沒找到合適的view,那么subviews中的子view的hitTest會都會被被調(diào)用一次

  • hitTest會調(diào)用pointInside判斷當(dāng)前視圖是否在點擊區(qū)域恐锣,所以超出父視圖邊界的控件無法響應(yīng)事件

  • 同一個view上的兩個子視圖有重疊部分茅主,后加入的視圖會被加入到事件傳遞鏈


事件響應(yīng)鏈

首先,響應(yīng)者鏈中的各個響應(yīng)者都繼承自UIResponder土榴,常見的UIView,viewController,UIWindow以及AppDelegate都繼承自UIResponder诀姚。響應(yīng)者鏈上的響應(yīng)者在hitTest過程中就已經(jīng)確定,可以通過迭代nextResponder查看所有的響應(yīng)者玷禽。

事件響應(yīng)鏈如下:

  1. 通過hitTest返回的view為當(dāng)前事件的第一響應(yīng)者赫段,nextResponder為上一個響應(yīng)者

  2. 如果當(dāng)前view默認(rèn)不去重寫,或者重寫調(diào)用了父類的實現(xiàn)矢赁,響應(yīng)就會就會沿著響應(yīng)者鏈向上傳遞(上一個響應(yīng)者一般是superView糯笙,可以通過nextResponder屬性獲取上一個響應(yīng)者)

  3. 如果上一個響應(yīng)者是viewController,由viewController的view處理撩银,view本身沒處理给涕,則傳遞給viewController本身

  4. 重復(fù)上述過程,直到傳遞到window额获,window如果也不能處理够庙,傳遞到UIApplication,如果UIApplication的delegate繼承自UIResponder咪啡,則交給delegate處理首启,delegate也不處理最后丟棄

以上就是響應(yīng)者鏈,事件響應(yīng)過程是從外向內(nèi)傳遞撤摸,和事件傳遞的過程正好相反

通過遍歷查找所有響應(yīng)者:

UIResponder *respon = self;
while (respon) {
    NSLog(@"%@",respon);
    respon = respon.nextResponder;
}

有手勢的情況下

手勢識別器的作用就是毅桃,識別到對應(yīng)的手勢后發(fā)送消息給target。iOS中的手勢分為兩種准夷,Apple文檔中有提到:

  • 離散型手勢 (UITapGestureRecognizer钥飞,UISwipeGestureRecognizer)
  • 持續(xù)性手勢 (UIPinchGestureRecognizer,UIPanGestureRecognizer衫嵌,UIRotationGestureRecognizer读宙,UILongPressGestureRecognizer)

離散型手勢的情況:

view未添加點擊手勢,點擊一次屏幕會調(diào)用touchesBegan和touchesEnde楔绞,當(dāng)我們不考慮touchesEnde的時候可以認(rèn)為它是一次性的

touchesBegan
touchesEnde

view添加tap手勢结闸,點擊屏幕會觸發(fā)手勢對應(yīng)的方法唇兑,touchesBegan和touchesCancelled,這里雖然調(diào)用了touchesCancelled,但實際上touchesBegan已經(jīng)觸發(fā)了

touchesBegan
tap
touchesCancelled

連續(xù)型手勢的情況:

view未添加連續(xù)手勢桦锄,當(dāng)手指在屏幕上拖動時扎附,先touchesBegan,然后touchesMoved隨著手指拖動持續(xù)調(diào)用结耀,停止后調(diào)用touchesEnde

touchesBegan
touchesMoved
...
touchesEnded

view添加pan拖拽手勢留夜,當(dāng)手指在屏幕上拖動時,touchesBegan和touchesMoved會先調(diào)用图甜,當(dāng)pan手勢方法觸發(fā)以后碍粥,touchesMoved將不再出現(xiàn),同時touchesCancelled也觸發(fā)了

touchesBegan
touchesMoved
pan //識別到 pan之后黑毅,就只有pan手勢會響應(yīng)
touchesCancelled
pan
pan
...

以下結(jié)論主要針對連續(xù)型手勢:

  • 若手勢成功識別事件嚼摩,就會取消第一響應(yīng)者view對事件的響應(yīng)(touchesCancelled)
  • 若手勢沒能識別事件,第一響應(yīng)者view就會接手事件的處理

通過斷點在sendEvent:處查看UIEvent事件博肋,在event->_allTouchesMutable->_gestureRecognizers手勢中可以看到當(dāng)前touch對象中包含所有的手勢對象低斋,通過斷點可以看到數(shù)組中第一個手勢的對象地址0x10510a2d0正是添加的tap手勢的地址。因此可以說明匪凡,手勢會先響應(yīng)

touch的gestureRecognizers數(shù)組:

_gestureRecognizers __NSArrayM *    @"6 elements"   0x0000000282bd46f0
[0] UITapGestureRecognizer *    0x10510a2d0 0x000000010510a2d0
[1] UIPanGestureRecognizer *    0x10510a3f0 0x000000010510a3f0
[2] UITapGestureRecognizer *    0x10510a1b0 0x000000010510a1b0
[3] UITapGestureRecognizer *    0x105107f70 0x0000000105107f70
[4] _UISystemGestureGateGestureRecognizer * 0x105011020 0x0000000105011020
[5] _UISystemGestureGateGestureRecognizer * 0x10500ff50 0x000000010500ff50
添加的tap手勢對象:
[2890:328171] tap:<UITapGestureRecognizer: 0x10510a2d0; state = Ended; view = <BlueView 0x105109e40>; target= <(action=tap:, target=<FirstViewController 0x1050114b0>)>>

有UIControl(按鈕)的情況

以Button為例,給Button添加添加tap手勢和TouchDown類型target掘猿,結(jié)果和上面的例子一樣病游,對于一次性手勢都會響應(yīng)

touchesBegan
TouchDown
tap
touchesCancelled

給Button只添加TouchDragInside類型target,touchesMoved和TouchDragInside都會響應(yīng)

touchesMoved
TouchDragInside

給Button添加pan手勢和TouchDragInside類型target,系統(tǒng)識別到pan手勢后就會touchesCancelled稠通,只有手勢pan會執(zhí)行

touchesMoved
TouchDragInside
pan //識別到 pan之后衬衬,就只有pan手勢會響應(yīng)
touchesCancelled
pan
pan
...
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市改橘,隨后出現(xiàn)的幾起案子滋尉,更是在濱河造成了極大的恐慌,老刑警劉巖飞主,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狮惜,死亡現(xiàn)場離奇詭異,居然都是意外死亡碌识,警方通過查閱死者的電腦和手機(jī)碾篡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筏餐,“玉大人开泽,你說我怎么就攤上這事】桑” “怎么了穆律?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵惠呼,是天一觀的道長。 經(jīng)常有香客問我峦耘,道長剔蹋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任贡歧,我火速辦了婚禮滩租,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘利朵。我一直安慰自己律想,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布绍弟。 她就那樣靜靜地躺著技即,像睡著了一般。 火紅的嫁衣襯著肌膚如雪樟遣。 梳的紋絲不亂的頭發(fā)上而叼,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音豹悬,去河邊找鬼葵陵。 笑死,一個胖子當(dāng)著我的面吹牛瞻佛,可吹牛的內(nèi)容都是我干的脱篙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼伤柄,長吁一口氣:“原來是場噩夢啊……” “哼绊困!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起适刀,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤秤朗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后笔喉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體取视,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年然遏,在試婚紗的時候發(fā)現(xiàn)自己被綠了贫途。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡待侵,死狀恐怖丢早,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤怨酝,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布傀缩,位于F島的核電站,受9級特大地震影響农猬,放射性物質(zhì)發(fā)生泄漏赡艰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一斤葱、第九天 我趴在偏房一處隱蔽的房頂上張望慷垮。 院中可真熱鬧,春花似錦揍堕、人聲如沸料身。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芹血。三九已至,卻和暖如春楞慈,著一層夾襖步出監(jiān)牢的瞬間幔烛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工囊蓝, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留饿悬,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓聚霜,卻偏偏與公主長得像乡恕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子俯萎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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