iOS 事件響應(yīng)鏈

當(dāng)我們觸摸手機(jī)屏幕到事件觸發(fā)可以分為兩步: 事件傳遞和事件響應(yīng)

一、傳遞: 當(dāng)我們觸摸屏幕時(shí),需要找到一個(gè)最合適的view,查找方式是從內(nèi)向外,UIApplication -> UIWindow -> view

查找過程:

查找過程中需要判斷透明度是否小于0.01,交互userInteractionEnabled是否關(guān)閉,動(dòng)畫交互是否關(guān)閉,如果有一項(xiàng)不成立,則該view不是最合適的view,也就是事件不會(huì)由該view和該view的子視圖接收,都成立后繼續(xù)判斷- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event;方法,該方法的返回值就是最合適的view,內(nèi)部實(shí)現(xiàn)是先判斷點(diǎn)擊的區(qū)域是否在該view上,再遍歷view的子視圖,子視圖的子視圖的hitTest方法,可以看出這是一個(gè)遞歸,而且是倒序reverseObjectEnumerator遍歷子視圖,說白了就是從最外層的子view開始遍歷,也就是最后addSubView上的view最先被遍歷到(可以自己去驗(yàn)證倒序問題),這樣效率會(huì)更高,因?yàn)樽钔鈱禹憫?yīng)事件的view可能性最大


hitTest內(nèi)部實(shí)現(xiàn)

二、響應(yīng): 當(dāng)我們找出最適合的view后,還需要找出可以響應(yīng)此事件的view,查找順序就是從這個(gè)最合適的view開始一直到UIApplication,從外到內(nèi)查找,view ->?UIWindow -> UIApplication?

查找過程:

先查看最合適的view是否有綁定手勢(shì)或者重寫touch的四個(gè)方法,如果滿足其中一個(gè)則處理響應(yīng)事件,整個(gè)事件響應(yīng)鏈結(jié)束,如果都沒有則繼續(xù)向下查看nextResponder也就是我們通常說的父view(也不一定是父view,如果控制器A跳轉(zhuǎn)到控制器B,那么控制器B的nextResponder就是控制器A,還有就是控制器A作為根視圖,那控制器A的nextResponder就是UIWindow了)是否有綁定手勢(shì)或者重寫touch方法,一直到UIApplication,直到事件被拋棄

驗(yàn)證響應(yīng)順序可以利用runtime Method Swizzling交換UIView的touchesBegan方法來驗(yàn)證

廢話不多說,直接看下面的場(chǎng)景!

有三個(gè)view,v1(紅色)、v2(藍(lán)色)、v3(黑色)

場(chǎng)景一:v1在最下面,父視圖是控制器的view,v2在v1的上面(擋住了v1半邊),父視圖也是控制器的view,v3是v2的子視圖,而且擋住了v1一部分,當(dāng)分別點(diǎn)擊v1、v2、v3的各個(gè)部位,接收事件的view是哪一個(gè),或者多個(gè)?

驗(yàn)證得出,不管點(diǎn)擊哪個(gè)view,都只有最外層與手指直接接觸的view產(chǎn)生響應(yīng),就算點(diǎn)擊多個(gè)view層疊的部位也只有最上層的view響應(yīng)

場(chǎng)景二:將v2的userInteractionEnabled屬性置為NO,不接收事件

可以發(fā)現(xiàn),在點(diǎn)擊v2或者v3的時(shí)候,v2、v3都不會(huì)接收響應(yīng),v2不接收響應(yīng)是由于交互被關(guān)閉了,但v3為什么不能接收響應(yīng)呢?原因是由于在第一步傳遞過程查找最合適的view時(shí),查找方式是從內(nèi)到外,也就是說作為v3的父視圖v2是優(yōu)先被找到,當(dāng)發(fā)現(xiàn)v2的交互關(guān)閉時(shí)不會(huì)遍歷v2的子視圖了,所以導(dǎo)致v3也不會(huì)響應(yīng),但是如果點(diǎn)擊v2或者v3區(qū)域的下方是v1,那么v1就會(huì)接收到事件響應(yīng)

場(chǎng)景三:想做到在點(diǎn)擊子視圖v3的時(shí)候,v2也要同時(shí)接收響應(yīng)事件

可以看出,只需要在子視圖v3中重寫touchesBegan方法,并加上[self.nextResponder touchesBegan:touches withEvent:event]這一句代碼將事件傳遞給下一個(gè)響應(yīng)者就可以實(shí)現(xiàn)上面的需求,這里順便提一下touchesBegan這個(gè)方法,該方法是UIResponder中的方法,UIView又是繼承自UIResponder的;看下面的touchesBegan方法的源碼可以看出touchesBegan里面的實(shí)現(xiàn)就是將事件傳遞給下一個(gè)響應(yīng)者,如果重寫了touchesBegan方法,而且沒有調(diào)用[self.nextResponder touchesBegan:touches withEvent:event]; 那么響應(yīng)事件就不會(huì)繼續(xù)傳下去,所以要想事件繼續(xù)傳下去必須加上[self.nextResponder touchesBegan:touches withEvent:event];這一句代碼,需要注意的是這里不能寫成[self.superview touchesBegan:touches withEvent:event]; 由于上面已經(jīng)提到過,當(dāng)前view的下一個(gè)響應(yīng)者有可能不是父子關(guān)系!

touchesBegan底層實(shí)現(xiàn)

場(chǎng)景四:不管點(diǎn)擊哪里(包括空白區(qū)域),v3都需要接收響應(yīng)事件

可以看出,只需要重寫v1中的hitTest方法并返回v3就可以達(dá)到上面的需求,至于原因上面也提到過,查找合適的view時(shí)遍歷到v1就直接找到最合適的view,也就是v3,查找過程就結(jié)束了,重寫hitTest方法剪切到v2中,也是實(shí)現(xiàn)相同的效果,因?yàn)関1和v2是平級(jí)的,都是self.view的直系子視圖,都會(huì)被遍歷到,但是如果v2的父視圖時(shí)v1,v3的父視圖時(shí)v2,這樣就又不一樣了

可以看出hitTest方法寫在v2里面,點(diǎn)擊空白處或者v1以外的區(qū)域(包括點(diǎn)擊v2或者v3的區(qū)域不在v1內(nèi))是不會(huì)有任何反應(yīng)的,因?yàn)樵诓檎业臅r(shí)候遍歷到v1時(shí),會(huì)判斷觸摸的區(qū)域是否在v1上面,如果不在則不會(huì)遍歷v1的子視圖v2和v3,只有在v1的區(qū)域才會(huì)觸發(fā)v2中的hitTest方法,進(jìn)而達(dá)到由v3來接收事件的效果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言: 按照時(shí)間順序,事件的生命周期是這樣的: 事件的產(chǎn)生和傳遞(事件如何從父控件傳遞到子控件并尋找到最合適的vi...
    小蟲筆墨閱讀 1,145評(píng)論 0 14
  • 響應(yīng)鏈?zhǔn)侨绾涡纬傻模?當(dāng)我們觸碰到屏幕的時(shí)候,整個(gè)iOS系統(tǒng)發(fā)生了什么呢? 這里有個(gè)思路需要轉(zhuǎn)變一下,本質(zhì)上,我們...
    點(diǎn)燃火焰閱讀 3,659評(píng)論 5 24
  • 這個(gè)問題啊經(jīng)常問,網(wǎng)上資料非常多,但是自己老是答不好: 響應(yīng)鏈:響應(yīng)事件的一系列響應(yīng)者組成的一個(gè)層次結(jié)構(gòu)。 事件,...
    iCoreMan閱讀 239評(píng)論 0 0
  • 事件的產(chǎn)生與傳遞 發(fā)生觸摸事件后,系統(tǒng)會(huì)將該事件加入到一個(gè)由UIApplication管理的事件隊(duì)列中 (為什么...
    LD_左岸閱讀 302評(píng)論 0 0
  • 新買的書《一個(gè)人,從心活》,今晚可以連夜啃讀了,學(xué)會(huì)不完美,做到不強(qiáng)求,不將就,不迷茫,不彷徨,以自己喜歡的方式去...
    敏子慧慧閱讀 204評(píng)論 0 0

友情鏈接更多精彩內(nèi)容