當(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可能性最大

二、響應(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)系!

場(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來接收事件的效果