事件的 傳遞鏈 響應(yīng)鏈 (ios)

前言(鋪墊一下)

當(dāng)發(fā)生事件時(shí)必須知道由誰(shuí)來響應(yīng)事件。

所有事件響應(yīng)的類都是UIResponder的子類震缭,

響應(yīng)鏈?zhǔn)且粋€(gè)由不同對(duì)象組成的層次結(jié)構(gòu),
其中的每個(gè)對(duì)象將依次獲得響應(yīng)事件消息的機(jī)會(huì)党涕。

當(dāng)發(fā)生事件時(shí) 事件首先被發(fā)送給第一響應(yīng)者巡社,
第一響應(yīng)者往往是事件發(fā)生的視圖,也就是用戶觸摸屏幕的地方肥荔。

事件將沿著響應(yīng)者鏈一直向下傳遞朝群,直到被接受并做出處理。

一般來說第一響應(yīng)者是個(gè)視圖對(duì)象或者其子類對(duì)象誉帅,當(dāng)其被觸摸后事件被交由它處理堵第,如果它不處理事件就會(huì)被傳遞給它的視圖控制器對(duì)象viewcontroller(如果存在)踏志,然后是它的父視圖(superview)對(duì)象(如果存在)针余,以此類推直到頂層視圖凄诞。接下來會(huì)沿著頂層視圖(top view)到窗口(UIWindow對(duì)象)再到程序(UIApplication對(duì)象)。

如果整個(gè)過程都沒有響應(yīng)這個(gè)事件伪朽,該事件就被丟棄烈涮。
在響應(yīng)者鏈中只要由對(duì)象處理事件坚洽,事件就停止傳遞。

典型的 事件響應(yīng)鏈 路線圖:
由離用戶最近的view向系統(tǒng)傳遞鞍盗。
First Responser -- >View -- >SuperView...-- >The Window -- >The Application -- > App Delegate

響應(yīng)者鏈流程經(jīng)常被委托(delegation)打斷跳昼,

一個(gè)對(duì)象(通常是視圖)可能將響應(yīng)工作委托給另一個(gè)對(duì)象來完成(通常是視圖控制器ViewController),所以 做事件響應(yīng)時(shí)在ViewController中必須實(shí)現(xiàn)相應(yīng)協(xié)議來實(shí)現(xiàn)事件委托欣除。

iOS中的UIResponder類,定義了響應(yīng)者對(duì)象的所有方法。UIApplication谱煤、UIView等類都繼承了UIResponder類室叉,UIWindow和UIKit中的控件因?yàn)槔^承了UIView,所以也間接繼承了UIResponder類恼除,這些類的實(shí)例都可以當(dāng)作響應(yīng)者豁辉。




下圖網(wǎng)絡(luò)出處:https://www.cnblogs.com/Julday/archive/2019/12/30/12119536.html

事件的(傳遞气破,響應(yīng)).png

iOS事件的類型

iOS用戶操作設(shè)備的方式主要有三種:觸摸屏幕现使、晃動(dòng)設(shè)備努咐、通過遙控設(shè)施控制設(shè)備渗稍。

對(duì)應(yīng)的事件類型如下:
1竿屹、觸屏事件 (Touch Event)
2、運(yùn)動(dòng)事件 (Motion Event)
3力惯、遠(yuǎn)端控制事件(Remote-Control Event)

慣例我們就以觸屏事件Touch Event為例說明在Cocoa Touch框架中事件的處理流程哮缺。

響應(yīng)鏈(Responder Chain)

響應(yīng)者對(duì)象(Responder Object)指的是有響應(yīng)和處理事件能力的對(duì)象尝苇。
響應(yīng)鏈 就是由一系列的 響應(yīng)者對(duì)象 構(gòu)成的一個(gè)層次結(jié)構(gòu)糠溜。

UIResponder是所有響應(yīng)對(duì)象的基類,在UIResponder類中定義了處理上述各種事件的接口谋竖。我們熟悉的UIApplication豹芯、 UIViewController铁蹈、UIWindow和所有繼承自UIView的UIKit類都直接或間接的繼承自UIResponder握牧,所以它們的實(shí)例都是可以構(gòu)成響應(yīng)者鏈的響應(yīng)者對(duì)象沿腰。


響應(yīng)鏈的基本構(gòu)成.png

上圖可見 響應(yīng)者鏈的一些特點(diǎn):
1习蓬、響應(yīng)者鏈通常是由視圖(UIView)構(gòu)成的;
2枫慷、視圖的下一個(gè)響應(yīng)者是它de視圖控制器UIViewController(當(dāng)然如果有它的話)或听,然后再轉(zhuǎn)給它的父視圖Super View;
3萌抵、視圖控制器 的下一個(gè)響應(yīng)者shi其管理的視圖的父視圖;
4讨永、單例的窗口UIWindow的內(nèi)容視圖將指向窗口本身作為它的下一個(gè)響應(yīng)者卿闹;
5锻霎、單例的應(yīng)用UIApplication是一個(gè)響應(yīng)者鏈的終點(diǎn)旋恼,它的下一個(gè)響應(yīng)者指向nil冰更,結(jié)束整個(gè)循環(huán)舟铜。

Cocoa應(yīng)用可以有多個(gè)UIWindow對(duì)象。
Cocoa Touch應(yīng)用只有一個(gè)UIWindow對(duì)象涣觉,整個(gè)響應(yīng)者鏈比較簡(jiǎn)單官册。

事件分發(fā)(Event Delivery)

第一響應(yīng)者First responder指的是當(dāng)前接受觸摸的響應(yīng)者對(duì)象(通常是一個(gè)UIView對(duì)象)膝宁,即當(dāng)前該對(duì)象正在與用戶交互员淫,它是響應(yīng)者鏈的開端介返。

事件傳遞的最終目的:找出一個(gè)能處理并響應(yīng)事件的對(duì)象(第一響應(yīng)者First responder)

事件傳遞的流程圖.png

如何尋找第一響應(yīng)者(事件傳遞的過程)

事件傳遞鏈:
由系統(tǒng)向離用戶最近的view傳遞圣蝎。
UIKit –> active app's event queue –> window –> root view –> …… –> lowest view

1、當(dāng)iOS程序發(fā)生觸摸事件后关面,系統(tǒng)會(huì)利用Runloop將事件加入到UIApplication的任務(wù)隊(duì)列中等太,具體過程可以參考深入理解RunLoop
2辛燥、UIApplication分發(fā)觸摸事件到UIWindow挎塌,然后UIWindow依次向下分發(fā)給UIView
3榴都、UIView調(diào)用hitTest:withEvent:方法看看自己能否處理事件竿音,以及觸摸點(diǎn)是否在自己上面春瞬。
4宽气、如果滿足條件萄涯,就遍歷UIView上的子控件涝影。重復(fù)上面的動(dòng)作。
5伯襟、直到找到最頂層的一個(gè)滿足條件(既能處理觸摸事件逗旁,觸摸點(diǎn)又在上面)的子控件,此子控件就是我們需要找到的第一響應(yīng)者英古。

hitTest:withEvent:的處理流程

(上面的查找其實(shí)就是由該方法遞歸調(diào)用實(shí)現(xiàn)的)
1、首先調(diào)用當(dāng)前視圖的pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi)唠叛;
2艺沼、若返回NO,則hitTest:withEvent:返回nil;
3调鲸、若返回YES,則向當(dāng)前視圖的所有子視圖(subviews)發(fā)送hitTest:withEvent:消息藐石,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數(shù)組的末尾向前遍歷办素,直到有子視圖返回非空對(duì)象或者全部子視圖遍歷完畢性穿;
4吗坚、若第一次有子視圖返回非空對(duì)象,則hitTest:withEvent:方法返回此對(duì)象谋减,處理結(jié)束出爹;
5、如所有子視圖都返回非梢为,則hitTest:withEvent:方法返回自身(self)。

hitTest:withEvent:方法的偽代碼大致如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.userInteractionEnabled || !self.hidden || self.alpha <= 0.01) {
        return nil;
    }
    
    if ([self pointInside:point withEvent:event]) {
        
        for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
            CGPoint subPoint = [subView convertPoint:point fromView:self];
            
            UIView *bestView = [subView hitTest:subPoint withEvent:event];
            if (bestView) {
                return bestView;
            }
        }
        return self;
    }

    return nil;
}

事件的響應(yīng)流程

通過上面的 hitTest:withEvent: 尋找到第一響應(yīng)者后,需要逆著尋找第一響應(yīng)者的方向(從第一響應(yīng)者->UIApplication)來響應(yīng)事件。

流程如下

1.首先通過 hitTest:withEvent: 確定第一響應(yīng)者,以及相應(yīng)的響應(yīng)鏈
2.判斷第一響應(yīng)者能否響應(yīng)事件芋哭,如果第一響應(yīng)者能進(jìn)行響應(yīng)則事件在響應(yīng)鏈中的傳遞終止豌习。如果第一響應(yīng)者不能響應(yīng)則將事件傳遞給 nextResponder也就是通常的superview進(jìn)行事件響應(yīng)
3.如果事件繼續(xù)上報(bào)至UIWindow并且無法響應(yīng),它將會(huì)把事件繼續(xù)上報(bào)給UIApplication
4.如果事件繼續(xù)上報(bào)至UIApplication并且也無法響應(yīng)稚失,它將會(huì)將事件上報(bào)給其Delegate
5.如果最終事件依舊未被響應(yīng)則會(huì)被系統(tǒng)拋棄

哪些視圖不響應(yīng)呢吸占?

hidden = YES 視圖被隱藏
userInteractionEnabled = NO 不接受響應(yīng)事件
alpha <= 0.01,透明視圖不接收響應(yīng)事件
子視圖超出父視圖范圍
需響應(yīng)視圖被其他視圖蓋住
是否重寫了其父視圖以及自身的hitTest方法
是否重寫了其父視圖以及自身的pointInside方法

應(yīng)用場(chǎng)景

方形按鈕點(diǎn)擊四角無效,點(diǎn)擊中間的圓形區(qū)域有效件蚕。
核心思路:在pointInside: withEvent:方法中修改對(duì)應(yīng)的區(qū)域排作。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 如果控件不允許與用用戶交互,那么返回nil
    if (!self.userInteractionEnabled || [self isHidden] || self.alpha <= 0.01) {
        return nil;
    }

    //判斷當(dāng)前視圖是否在點(diǎn)擊范圍內(nèi)
    if ([self pointInside:point withEvent:event]) {
        //遍歷當(dāng)前對(duì)象的子視圖(倒序)
        __block UIView *hit = nil;
        [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            //坐標(biāo)轉(zhuǎn)換蕾久,把當(dāng)前坐標(biāo)系上的點(diǎn)轉(zhuǎn)換成子控件坐標(biāo)系上的點(diǎn)
            CGPoint convertPoint = [self convertPoint:point toView:obj];
            //調(diào)用子視圖的hitTest方法履因,判斷自己的子控件是不是最適合的View
            hit = [obj hitTest:convertPoint withEvent:event];
            //如果找到了就停止遍歷
            if (hit) *stop = YES;
        }];

        //返回當(dāng)前的視圖對(duì)象
        return hit?hit:self;
    }else {
        return nil;
    }
}

// 該方法判斷觸摸點(diǎn)是否在控件身上栅迄,是則返回YES毅舆,否則返回NO憋活,point參數(shù)必須是方法調(diào)用者的坐標(biāo)系
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {   
    CGFloat x1 = point.x;
    CGFloat y1 = point.y;
    
    CGFloat x2 = self.frame.size.width / 2;
    CGFloat y2 = self.frame.size.height / 2;
    
    //判斷是否在圓形區(qū)域內(nèi)
    double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    if (dis <= self.frame.size.width / 2) {
        return YES;
    }
    else{
        return NO;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吮成,一起剝皮案震驚了整個(gè)濱河市粱甫,隨后出現(xiàn)的幾起案子茶宵,更是在濱河造成了極大的恐慌节预,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锅棕,死亡現(xiàn)場(chǎng)離奇詭異裸燎,居然都是意外死亡德绿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人稻薇,你說我怎么就攤上這事塞椎〕佬迹” “怎么了莺戒?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵瘪校,是天一觀的道長(zhǎng)阱扬。 經(jīng)常有香客問我麻惶,道長(zhǎng)窃蹋,這世上最難降的妖魔是什么警没? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任押搪,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绵载。我一直安慰自己,他們只是感情好购裙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蓬抄,像睡著了一般嚷缭。 火紅的嫁衣襯著肌膚如雪阅爽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音杂伟,去河邊找鬼赫粥。 笑死越平,一個(gè)胖子當(dāng)著我的面吹牛晦溪,可吹牛的內(nèi)容都是我干的三圆。 我是一名探鬼主播修噪,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼黄琼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脏款!你這毒婦竟也來了弛矛?” 一聲冷哼從身側(cè)響起比然,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闰歪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體论矾,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年钻注,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了幅恋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捆交。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窒舟,死狀恐怖惠豺,靈堂內(nèi)的尸體忽然破棺而出洁墙,到底是詐尸還是另有隱情热监,我是刑警寧澤孝扛,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布寞钥,位于F島的核電站,受9級(jí)特大地震影響咨油,放射性物質(zhì)發(fā)生泄漏役电。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦千所、人聲如沸淫痰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)适瓦。三九已至玻熙,卻和暖如春揭芍,著一層夾襖步出監(jiān)牢的瞬間称杨,已是汗流浹背姑原。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唤殴,地道東北人朵逝。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓啤咽,卻偏偏與公主長(zhǎng)得像宇整,于是被迫代替她去往敵國(guó)和親鳞青。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盼玄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345