iOS事件傳遞和響應(yīng)機(jī)制

開場(chǎng)白

iOS開發(fā)這么多年或听,其實(shí)從來就沒關(guān)心過時(shí)間傳遞和響應(yīng)機(jī)制這么個(gè)事。當(dāng)我看到這篇文章史上最詳細(xì)的iOS之事件的傳遞和響應(yīng)機(jī)制-原理篇后笋婿,發(fā)現(xiàn)其中有很多東西可以細(xì)細(xì)品味一下的誉裆。

1.簡(jiǎn)述事件流程

整個(gè)事件傳遞和處理流程,簡(jiǎn)單概括為:

事件-事件傳遞到指定界面-找到可響應(yīng)的界面-響應(yīng)

我開始的理解誤區(qū)就是‘傳遞到指定界面’和‘可響應(yīng)界面’理解成同一個(gè)界面了缸濒,造成我在看上面的文章的時(shí)候足丢,有些混亂粱腻。其實(shí)這兩個(gè)可以是兩個(gè)界面。

例如:我在touchBegin一個(gè)view的時(shí)候斩跌,需求是view不響應(yīng)绍些,而superview響應(yīng)。而事件傳遞是傳遞到view中耀鸦。這種情況兩個(gè)view就是不相同的界面柬批。

2.事件傳遞

  1. 當(dāng)有用戶觸摸屏幕的時(shí)候產(chǎn)生事件,系統(tǒng)硬件進(jìn)程獲取到這個(gè)事件袖订,并處理封裝保存在系統(tǒng)中氮帐,由于系統(tǒng)硬件進(jìn)程和app進(jìn)程是兩個(gè)不同的進(jìn)程,所以使用進(jìn)程間的端口通信洛姑。
  2. 系統(tǒng)會(huì)將這個(gè)事件加入到UIApplication的事件管理隊(duì)列中上沐,事件從隊(duì)列中出隊(duì)后通常會(huì)發(fā)送給app的keywindow處理。
  3. keywindow會(huì)找到一個(gè)最適合的視圖去處理事件楞艾。也就是從super控件到子控件中参咙。
  4. 簡(jiǎn)單總結(jié):UIApplication->window->尋找處理事件最合適的view

2.1 找到適合視圖的過程

  1. 首先keywindow是可以接受事件的
  2. 判斷是否事件發(fā)生在自己的可視范圍內(nèi),例如:觸摸點(diǎn)擊在自己的bound中硫眯。
  3. 子控件數(shù)組按照從后往前的順序查找適合的子控件蕴侧,重復(fù)步驟1和步驟2。(從后往前的意思就是subviews中從最后一個(gè)元素開始向前找舟铜,這種方式可以減少遍歷次數(shù)戈盈,提高效率)
  4. 找到子控件后再繼續(xù)找它的子控件。
  5. 如果沒有找到合適的子控件谆刨,那么當(dāng)前的控件就是最適合的塘娶。

2.2 UIView不能接收觸摸事件的三種情況

  • 不允許交互:userInteractionEnabled = NO,例如UIImageView中addSubview一個(gè)button痊夭,button的點(diǎn)擊是沒有反應(yīng)的刁岸。
  • 隱藏:如果把父控件隱藏,那么子控件也會(huì)隱藏她我,隱藏的控件不能接受事件
  • 透明度:如果設(shè)置一個(gè)控件的透明度<0.01虹曙,會(huì)直接影響子控件的透明度。

如果不想讓view處理事件番舆,而是想讓superview處理酝碳,就可以吧view的userInteractionEnabled設(shè)置為no。

2.3 最適合的子控件

系統(tǒng)api中提供了兩個(gè)方法恨狈,

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   // default returns YES if point is in bounds

為了方便:hitTest:withEvent:方法在文章后續(xù)用hitTest代替疏哗,pointInside:withEvent:用pointInside代替

通過注釋了解到hitTest方法是遞歸的調(diào)用pointInside方法。point是在接受控件坐標(biāo)系內(nèi)的禾怠。

底層的事件傳遞實(shí)現(xiàn)就是:
產(chǎn)生觸摸事件->UIApplication事件隊(duì)列->[UIWindow hitTest:withEvent:]->返回更合適的view->[子控件 hitTest:withEvent:]->返回最合適的view->...->返回最合適的view

2.4 攔截事件傳遞

我們可以重寫hitTest方法返奉,來攔截系統(tǒng)的事件傳遞贝搁,讓指定的view處理事件。例如自定義view中芽偏,想讓view中的一個(gè)subview處理事件雷逆,就可以在自定義view中重寫該方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if ([self pointInside:point withEvent:event]) {
        return self;
    }
    
    return nil;
}

示例代碼我返回的是self,這里可以改成指定的subview污尉,或者遍歷subview中的一個(gè)膀哲。

3. 響應(yīng)鏈

在很多文章中都看到了這張圖,不清楚是不是官方十厢,但圖片中的邏輯是沒有問題的等太,ios控件間的擺放都是有層級(jí)關(guān)系的,這張圖表示的很清晰蛮放。響應(yīng)者對(duì)象就是繼承與UIResponder的子類們。

3.1 UIResponder的子類

UIResponder的子類有一下幾個(gè):

  • AppDelegate
  • UIApplication
  • UIViewController
  • UIView

p.s. UIWindow的父類是UIView

3.2 nextResponder

UIResponder的子類是通過nextResponder進(jìn)行連接的奠宜。

響應(yīng)鏈創(chuàng)建方式包颁,本人個(gè)人理解,應(yīng)該是鏈表的頭插法形式:

  1. AppDelegate作為整個(gè)鏈的根基压真,是第一個(gè)被創(chuàng)建出來的娩嚼,在main函數(shù)中被調(diào)用。它的nextResponder為nil滴肿。當(dāng)前鏈表的狀態(tài):AppDelegate->nil
  2. 系統(tǒng)提供給我們的UIApplication單例岳悟,響應(yīng)鏈變?yōu)椋?strong>UIApplication->AppDelegate->nil
  3. UIApplication會(huì)創(chuàng)建keyWindow,是UIWindow類型泼差,父類是UIView贵少,也是UIResponder的子類,所以響應(yīng)鏈變?yōu)椋?strong>keyWindow->UIApplication->AppDelegate->nil
  4. keyWindow中會(huì)設(shè)置一個(gè)rootViewController堆缘,是UIViewController類型滔灶,是UIResponder子類,rootViewController->keyWindow->UIApplication->AppDelegate->nil
  5. rootViewController中有view吼肥,我們?cè)陂_發(fā)中把自定義的view加載vc的view中录平,最終響應(yīng)鏈為:自定義view->superview->rootViewController->keyWindow->UIApplication->AppDelegate->nil

這里只是簡(jiǎn)單舉個(gè)例子,其實(shí)項(xiàng)目中會(huì)有更復(fù)雜的層級(jí)關(guān)系缀皱。

3.3 官方文檔可以證明

很多人會(huì)問如何證明呢斗这,我們來看看官方文檔中的解釋:


Summary

Returns the next responder in the responder chain, or nil if there is no next responder.

返回響應(yīng)者鏈中的下一個(gè)響應(yīng)者,如沒有下一個(gè)響應(yīng)者返回nil啤斗。

Disussion

The UIResponder class does not store or set the next responder automatically, so this method returns nil by default. Subclasses must override this method and return an appropriate next responder. For example, UIView implements this method and returns the UIViewController object that manages it (if it has one) or its superview (if it doesn’t). UIViewController similarly implements the method and returns its view’s superview. UIWindow returns the application object. The shared UIApplication object normally returns nil, but it returns its app delegate if that object is a subclass of UIResponder and has not already been called to handle the event.

UIResponder類不會(huì)自動(dòng)存儲(chǔ)和設(shè)置下一個(gè)響應(yīng)者(next responder)表箭,這個(gè)方法默認(rèn)返回nil。子類必須復(fù)寫這個(gè)方法并且返回一個(gè)合適的下一個(gè)響應(yīng)者争占。例如燃逻,UIView實(shí)現(xiàn)這個(gè)方法序目,如果是被UIViewController對(duì)象管理的下一個(gè)響應(yīng)者就是UIViewController;如不哦不是被UIViewController對(duì)象管理的伯襟,下一個(gè)響應(yīng)者就是superview猿涨。UIViewController同樣實(shí)現(xiàn)這個(gè)方法,并且返回它自己view的superview姆怪。UIWindow返回application對(duì)象叛赚。shared UIApplication對(duì)象通常返回nil,但是如果該對(duì)象是一個(gè)UIRespnder的子類并且還沒有被調(diào)用去處理事件稽揭,它返回的是app的delegate俺附。

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

通過上面例子中的響應(yīng)鏈自定義view->superview->rootViewController->keyWindow->UIApplication->AppDelegate->nil的順序,逐層向后查找可做響應(yīng)的響應(yīng)者(UIResponder子類)溪掀。

如果多層有實(shí)現(xiàn)了UIResponder的相關(guān)方法事镣,例如touchesBegan,這多層都可以響應(yīng)揪胃。

舉個(gè)例子:
vc中init一個(gè)自定義的TestView璃哟,并且在vc和TestView中都實(shí)現(xiàn)了touchesBegan方法

vc部分代碼:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = UIColor.lightGrayColor;
    
    TestView *view1 = [[TestView alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
    view1.tag = 1;
    view1.backgroundColor = [UIColor redColor];
    [self.view addSubview:view1];
}

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

TestView部分代碼:

@implementation TestView

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

運(yùn)行后的效果:


點(diǎn)擊紅色區(qū)域后查看控制臺(tái):



TestView和VC的touchesBegan方法都調(diào)用了。

注意:TestView中的touchesBegan要調(diào)用super touchesBegan喊递,如果不調(diào)用随闪,vc中無法打印。因?yàn)椴徽{(diào)用就不會(huì)繼續(xù)查找響應(yīng)鏈中后續(xù)的響應(yīng)者了骚勘。vc中touchesBegan中調(diào)用了super也是同理目的铐伴。

4. 簡(jiǎn)單總結(jié)

事件的傳遞和響應(yīng)的區(qū)別:
事件的傳遞是從上到下(父控件到子控件),事件的響應(yīng)是從下到上(順著響應(yīng)者鏈條向上傳遞:子控件到父控件)俏讹。

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

參考這篇文章:iOS事件響應(yīng)鏈中hitTest的應(yīng)用示例

其中包括:

  • 擴(kuò)大UIButton的響應(yīng)熱區(qū)
  • 子view超出了父view的bounds響應(yīng)事件
  • 使部分區(qū)域失去響應(yīng).
  • 讓非scrollView區(qū)域響應(yīng)scrollView拖拽事件
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末当宴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藐石,更是在濱河造成了極大的恐慌即供,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件于微,死亡現(xiàn)場(chǎng)離奇詭異逗嫡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)株依,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門驱证,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恋腕,你說我怎么就攤上這事抹锄。” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵伙单,是天一觀的道長获高。 經(jīng)常有香客問我,道長吻育,這世上最難降的妖魔是什么念秧? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮布疼,結(jié)果婚禮上摊趾,老公的妹妹穿的比我還像新娘。我一直安慰自己游两,他們只是感情好砾层,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贱案,像睡著了一般肛炮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宝踪,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天铸董,我揣著相機(jī)與錄音,去河邊找鬼肴沫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蕴忆,可吹牛的內(nèi)容都是我干的颤芬。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼套鹅,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼站蝠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卓鹿,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤菱魔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吟孙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澜倦,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年杰妓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了藻治。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡巷挥,死狀恐怖桩卵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤雏节,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布胜嗓,位于F島的核電站,受9級(jí)特大地震影響钩乍,放射性物質(zhì)發(fā)生泄漏辞州。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一件蚕、第九天 我趴在偏房一處隱蔽的房頂上張望孙技。 院中可真熱鬧,春花似錦排作、人聲如沸牵啦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哈雏。三九已至,卻和暖如春衫生,著一層夾襖步出監(jiān)牢的瞬間裳瘪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工罪针, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彭羹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓泪酱,卻偏偏與公主長得像派殷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子墓阀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351