If I have seen further(than.Descartes)it is by standing upon the shoulders of Giants.

一、事件分類
iOS系統(tǒng)操作設(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)
UIResponder?。響應(yīng)者對(duì)象是指有響應(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)者鏈有以下特點(diǎn):
1、響應(yīng)者鏈通常是由視圖(UIView)構(gòu)成的;
2、一個(gè)視圖的下一個(gè)響應(yīng)者是它視圖控制器(UIViewController)(如果有的話),然后再轉(zhuǎn)給它的父視圖(Super View);
3、視圖控制器(如果有的話)的下一個(gè)響應(yīng)者為其管理的視圖的父視圖;
4、單例的窗口(UIWindow)的內(nèi)容視圖將指向窗口本身作為它的下一個(gè)響應(yīng)者
需要指出的是,Cocoa Touch應(yīng)用不像Cocoa應(yīng)用,它只有一個(gè)UIWindow對(duì)象,因此整個(gè)響應(yīng)者鏈要簡單一點(diǎn);
5、單例的應(yīng)用(UIApplication)是一個(gè)響應(yīng)者鏈的終點(diǎn),它的下一個(gè)響應(yīng)者指向nil,以結(jié)束整個(gè)循環(huán)。

三、事件分發(fā)(Event Delivery)
第一響應(yīng)者(First responder)指的是當(dāng)前接受觸摸的響應(yīng)者對(duì)象(通常是一個(gè)UIView對(duì)象),即表示當(dāng)前該對(duì)象正在與用戶交互,它是響應(yīng)者鏈的開端。整個(gè)響應(yīng)者鏈和事件分發(fā)的使命都是找出第一響應(yīng)者。
UIWindow對(duì)象以消息的形式將事件發(fā)送給第一響應(yīng)者,使其有機(jī)會(huì)首先處理事件。如果第一響應(yīng)者沒有進(jìn)行處理,系統(tǒng)就將事件(通過消息)傳遞給響應(yīng)者鏈中的下一個(gè)響應(yīng)者,看看它是否可以進(jìn)行處理。
iOS系統(tǒng)檢測到手指觸摸(Touch)操作時(shí)會(huì)將其打包成一個(gè)UIEvent對(duì)象,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列,單例的UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來處理,UIWindow對(duì)象首先會(huì)使用hitTest:withEvent:方法尋找此次Touch操作初始點(diǎn)所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖。
UIWindow實(shí)例對(duì)象會(huì)首先在它的內(nèi)容視圖上調(diào)用hitTest:withEvent:,此方法會(huì)在其視圖層級(jí)結(jié)構(gòu)中的每個(gè)視圖上調(diào)用pointInside:withEvent:(該方法用來判斷點(diǎn)擊事件發(fā)生的位置是否處于當(dāng)前視圖范圍內(nèi),以確定用戶是不是點(diǎn)擊了當(dāng)前視圖),如果pointInside:withEvent:返回YES,則繼續(xù)逐級(jí)調(diào)用,直到找到touch操作發(fā)生的位置,這個(gè)視圖也就是要找的hit-test view。

hitTest:withEvent:方法的處理流程如下:
首先調(diào)用當(dāng)前視圖的pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi);
若返回NO,則hitTest:withEvent:返回nil;
若返回YES,則向當(dāng)前視圖的所有子視圖(subviews)發(fā)送hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖返回非空對(duì)象或者全部子視圖遍歷完畢;
若第一次有子視圖返回非空對(duì)象,則hitTest:withEvent:方法返回此對(duì)象,處理結(jié)束;
如所有子視圖都返回非,則hitTest:withEvent:方法返回自身(self)。
四、說明
1、如果最終hit-test沒有找到第一響應(yīng)者,或者第一響應(yīng)者沒有處理該事件,則該事件會(huì)沿著響應(yīng)者鏈向上回溯,如果UIWindow實(shí)例和UIApplication實(shí)例都不能處理該事件,則該事件會(huì)被丟棄。
2、hitTest:withEvent:方法將會(huì)忽略隱藏(hidden=YES)的視圖,禁止用戶操作(userInteractionEnabled=YES)的視圖,以及alpha級(jí)別小于0.01(alpha<0.01)的視圖。如果一個(gè)子視圖的區(qū)域超過父視圖的bound區(qū)域(父視圖的clipsToBounds 屬性為NO,這樣超過父視圖bound區(qū)域的子視圖內(nèi)容也會(huì)顯示),那么正常情況下對(duì)子視圖在父視圖之外區(qū)域的觸摸操作不會(huì)被識(shí)別,因?yàn)楦敢晥D的pointInside:withEvent:方法會(huì)返回NO,這樣就不會(huì)繼續(xù)向下遍歷子視圖了。當(dāng)然,也可以重寫pointInside:withEvent:方法來處理這種情況。
3、還可以重寫hitTest:withEvent:來達(dá)到某些特定的目的。
五、實(shí)際運(yùn)用
?①如何尋找最合適的view
1.首先判斷主窗口(keyWindow)自己是否能接受觸摸事件
2.觸摸點(diǎn)是否在自己身上
3.從后往前遍歷子控件,重復(fù)前面的兩個(gè)步驟(首先查找數(shù)組中最后一個(gè)元素)
4.如果沒有符合條件的子控件,那么就認(rèn)為自己最合適處理

②攔截事件的處理
1.正因?yàn)閔itTest:withEvent:方法可以返回最合適的view,所以可以通過重寫hitTest:withEvent:方法,返回指定的view作為最合適的view。
2.不管點(diǎn)擊哪里,最合適的view都是hitTest:withEvent:方法中返回的那個(gè)view。
3.通過重寫hitTest:withEvent:,就可以攔截事件的傳遞過程,想讓誰處理事件誰就處理事件。
事件傳遞給誰,就會(huì)調(diào)用誰的hitTest:withEvent:方法。
注 意:如果hitTest:withEvent:方法中返回nil,那么調(diào)用該方法的控件本身和其子控件都不是最合適的view,也就是在自己身上沒有找到更合適的view。那么最合適的view就是該控件的父控件。
六、總結(jié)
①事件的傳遞順序是這樣的:
產(chǎn)生觸摸事件->UIApplication事件隊(duì)列->[UIWindow hitTest:withEvent:]->返回更合適的view->[子控件 hitTest:withEvent:]->返回最合適的view
②事件的傳遞和響應(yīng)的區(qū)別:
事件的傳遞是從上到下(父控件到子控件),事件的響應(yīng)是從下到上(順著響應(yīng)者鏈條向上傳遞:子控件到父控件。
③特殊情況:
誰都不能處理事件,窗口也不能處理。
重寫window的hitTest:withEvent:方法return nil
只能有窗口處理事件。
控制器的view的hitTest:withEvent:方法return nil或者window的hitTest:withEvent:方法return self
