面試被問(wèn)到了一個(gè)Button,如果點(diǎn)擊無(wú)效果,你會(huì)怎么排查? 這個(gè)問(wèn)題我當(dāng)時(shí)回答的比較簡(jiǎn)單,但是面試官追問(wèn)的比較深,我回答的有些含糊,課后補(bǔ)習(xí)一下。
前言:
借鑒了一些簡(jiǎn)書(shū)大佬的文章的文章,然后自己寫了Demo,測(cè)試完畢后自己總結(jié)下自己的理解。
iOS中的事件可以分為3大類型:觸摸事件-加速計(jì)事件-遠(yuǎn)程控制事件。
此文只針對(duì)觸摸事件。
查看源碼可以很輕易發(fā)現(xiàn):




由此可見(jiàn):UIViewController - UIView - UIWindow - UIApplication都是繼承UIResponder,那UIResponder又是什么呢,圖中都繼承UIResponder,能代表又有什么結(jié)論呢?
先說(shuō)說(shuō)UIResponder。
在iOS中UIResponder類是專門用來(lái)響應(yīng)用戶的操作處理各種事件的,包括觸摸事件(Touch Events)、運(yùn)動(dòng)事件(Motion Events)、遠(yuǎn)程控制事件(Remote Control Events)。具體的UIResponder內(nèi)部又是如何處理事件,這里不做說(shuō)明。
查看UIResponder源碼可以發(fā)現(xiàn):

由此可見(jiàn),官方讓我們重新這幾個(gè)方法,就可以實(shí)現(xiàn)觸摸相關(guān)的活動(dòng)事件。
UIView,UIApplication,UIWindow,UIViewController繼承UIResponder,同樣具有父類的這些觸摸事件。
這里以UIViewController和UIView為例,其他的道理是相同的。
在UIViewController重新touchesBegan方法,點(diǎn)擊當(dāng)前的Controller,
直接上代碼,因?yàn)榇a比較簡(jiǎn)單,我就截圖說(shuō)明。

觸摸事件參數(shù)有兩個(gè)UITouch,UIEvent,兩個(gè)參數(shù)有著自己不同的任務(wù)。
UITouch
當(dāng)用戶用一根手指觸摸屏幕時(shí),會(huì)創(chuàng)建一個(gè)與手指相關(guān)的UITouch對(duì)象.
UITouch對(duì)象,封裝了當(dāng)前的Touch事件的對(duì)象集合。

Touch對(duì)象保存著跟手指相關(guān)的信息,比如觸摸的位置、時(shí)間、階段
當(dāng)手指移動(dòng)時(shí),系統(tǒng)會(huì)更新同一個(gè)UITouch對(duì)象,使之能夠一直保存該手指在的觸摸位置
當(dāng)手指離開(kāi)屏幕時(shí),系統(tǒng)會(huì)銷毀相應(yīng)的UITouch對(duì)象。
重寫:touchesBegan
點(diǎn)擊一次,當(dāng)前操作只有一次,所以Set集合只有一個(gè)Touch事件,而且事件tap count = 1。(單擊事件)。
重寫:touchesMoved
隨著手指的拖動(dòng),set集合的Touch對(duì)象會(huì)隨之拖到而改變,且此時(shí)的Touch對(duì)象是不一樣的,當(dāng)前移動(dòng)的位置和上一次不一樣。
正因?yàn)槭种冈诓粩嗟囊苿?dòng),所以當(dāng)前的點(diǎn)和上次移動(dòng)的點(diǎn),官方也給了相關(guān)方法來(lái)獲取。

?UIEvent

觸摸的目的是生成觸摸事件供響應(yīng)者響應(yīng),一個(gè)觸摸事件(UITouch)對(duì)應(yīng)一個(gè)UIEvent對(duì)象,其中的type屬性標(biāo)識(shí)了事件的類型,事件有如下幾種類型:
根據(jù)手勢(shì)的不同操作(單擊,滑動(dòng),按壓,平移等動(dòng)作),所出發(fā)的事件效果會(huì)不同。

回到前面說(shuō)的Button的案例。
有個(gè)UIButton,Button的點(diǎn)擊事件我們也實(shí)現(xiàn)了,卻點(diǎn)擊無(wú)效,無(wú)法響應(yīng)與之對(duì)應(yīng)的點(diǎn)擊方法,這時(shí)候我們?cè)撊绾稳ゲ椋?/p>
一般都會(huì)檢查自己寫addTarget是否有誤,點(diǎn)擊事件ButtonClick是否實(shí)現(xiàn)。
其次我們也可以從View的事件傳遞考慮。
View事件傳遞有三個(gè)原則不會(huì)傳遞:
1.當(dāng)前View或者控件的userInteractionEnabled = NO;
2.當(dāng)前View或者控件的Hidden為YES
3.同理當(dāng)前View或者控件透明的<=0.01,也就是(0-0.01)。
除此之外,我們也可以通過(guò)重寫View的hitTest來(lái)查看當(dāng)前事件可傳遞的View。

這里使用下比較經(jīng)典的一個(gè)圖解:

舉例:
點(diǎn)擊黃色View,因?yàn)槲覀儾](méi)有寫以上阻斷事件傳遞的三種方式,所以黃色的父控件(藍(lán)色),父父控件(橙色),以及父父父控件(白色)打印的結(jié)果都是一直的,指向黃色View。

應(yīng)用如何找到最合適的控件來(lái)處理事件?
1.首先判斷主窗口(keyWindow)自己是否能接受觸摸事件
2.觸摸點(diǎn)是否在自己身上
3.從后往前遍歷子控件,重復(fù)前面的兩個(gè)步驟(首先查找數(shù)組中最后一個(gè)元素)
4.如果沒(méi)有符合條件的子控件,那么就認(rèn)為自己最合適處理
上面的說(shuō)法是比較官方的解釋,用比較通俗的解釋就是說(shuō):
UIApplication 問(wèn) keyWindow?:小子,我給你個(gè)事件你能不能處理下?
keyWindow唯唯諾諾的說(shuō):我。。我不能,觸摸點(diǎn)不在我身上。
keyWindow 又去問(wèn)它的子控件橙色View。
keyWindow:小橙,老大安排了事情,我不能處理,你小子能不能給處理這個(gè)點(diǎn)擊事件?
OrangeView同樣唯唯諾諾的說(shuō):我不能,觸摸點(diǎn)不在我身上。
。。。。。。
直到最后一層黃色View。
OrangeView:沒(méi)問(wèn)題啦~觸摸點(diǎn)在我身上,老大們放心吧,我會(huì)處理好的(觸摸事件里的操作)。
大概用比較歡喜的理解就是這樣傳遞。
特殊情況:
hitTest:withEvent:中return nil的意思是調(diào)用當(dāng)前hitTest:withEvent:方法的view不是合適的view,子控件也不是合適的view。如果同級(jí)的兄弟控件也沒(méi)有合適的view,那么最合適的view就是父控件。
1.所有的子控件都有攔截事件傳遞的情況,比如都給攔截掉了。
你們他們最終的父控件會(huì)返回nil,代表沒(méi)有其子控件可以處理,只能自己處理了。
子控件打?。?/p>

父控件打印:

代表只能自己去處理這個(gè)事件。
其次還需要一點(diǎn)說(shuō)明,如果1234中的某個(gè)view攔截事件了,就不會(huì)傳遞給其子控件啦。
3有攔截事件功能,4就不能操作這個(gè)事件,只能是3的父,父父,父父父View來(lái)處理。
以上就是我對(duì)View事件和傳遞的自己的理解,記錄一下,作為知識(shí)點(diǎn)儲(chǔ)存。
如果有誤或者更好的理解,望大家留言,謝謝。