? ? ? ? 如今我們但凡看到一塊屏幕我們都會忍不住去點擊,幾乎每一塊屏幕都能多點觸控。我們用多點觸控屏幕是那么自然,就像生來就有的技巧。那么在我們手指觸碰屏幕的一瞬間,到底發(fā)生了什么呢?首先我們需要先了解事件類型。
1.事件類型

? ? ? ? 當我們操作手機時,一般有觸摸屏幕、搖晃手機、遠程遙控三種方式,分別對應(yīng)的事件類型是:
? ? ? ? 1. ?觸摸事件 (Multitouch events)
? ? ? ? 2. 運動事件 ?(Accelerometer events)
? ? ? ? 3. 遠程控制 ?(Remote control events)
? ? ? ? 當這些事件發(fā)生時,iOS會生成對應(yīng)的響應(yīng)鏈, 來查找第一響應(yīng)對象并進行事件的分發(fā),最后處理事件,完成相應(yīng)操作。下面我們接著看關(guān)于響應(yīng)鏈的概念。
2.響應(yīng)鏈
? ? ? ? 響應(yīng)鏈,顧名思義,就是有一系列響應(yīng)對象的集合成的一個層次結(jié)構(gòu)。那什么又是響應(yīng)對象呢?Cocoa里面規(guī)定:凡是繼承于UIResponder或者UIResponder的子類的對象都可以作為響應(yīng)對象,比如UIApplication、UIViewController和UIView。
? ? ? ? 在響應(yīng)用戶觸摸等事件中,APP具體會通過下面三步來完成操作:
? ? ? ? 1. ?生成事件。當用戶點擊屏幕時,會產(chǎn)生一個觸摸事件,并放入由Application管理的事件隊列中,然后在隊列中取出最前面的事件交給Window處理。
? ? ? ? 2. ?查找第一響應(yīng)對象。Window收到事件后會在視圖層次結(jié)構(gòu)中找到最適合的一個視圖來處理事件,通常一個窗口中最適合處理當前事件的對象稱為第一響應(yīng)對象。
? ? ? ? 3. ?處理事件。通常最后是第一響應(yīng)對象處理事件,如果第一響應(yīng)對象無法處理事件,就會把事件傳遞給下一個響應(yīng)對象,直到Application。如果Application也無法處理,那就丟棄掉此事件。
? ? ? ? 在上述系列操作中,所參與到的UIApplication、UIViewController和UIView就作為響應(yīng)對象構(gòu)成這次事件的響應(yīng)鏈。
2.1 查找第一響應(yīng)對象
? ? ? ? 當Window收到事件后,會用一種類似二分法的方式來查找第一響應(yīng)對象,通常就是用戶點擊處最上層的一個View。
? ? ? ? Window實例對象會首先在它的內(nèi)容視圖上調(diào)用hitTest:withEvent:,此方法會在其視圖層級結(jié)構(gòu)中的每個視圖上調(diào)用pointInside:withEvent:(該方法用來判斷點擊事件發(fā)生的位置是否處于當前視圖范圍內(nèi),以確定用戶是不是點擊了當前視圖),如果pointInside:withEvent:返回true,則繼續(xù)逐級調(diào)用,直到找到touch操作發(fā)生的位置,這個視圖也就是要找的hit-test view。
? ? ? ? hitTest:withEvent:方法的處理流程如下:
? ? ? ? 首先調(diào)用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內(nèi),若返回false,則對應(yīng)hitTest:withEvent:返回nil; 若返回true, 則向當前視圖的所有子視圖發(fā)送hitTest:withEvent:消息,直到有子視圖返回非空對象或者全部子視圖遍歷完畢;若最后一層某個子視圖pointInside:withEvent:方法返回true,則對應(yīng)hitTest:withEvent:方法返回此對象,直到把此對象依次向上返回到Application則處理結(jié)束。
? ? ? ?我們結(jié)合一個實例來加深理解:

假設(shè)View A 是Window的根視圖,用戶點了View E之后:
1. Window首先會對View A進行hit-test,具體表現(xiàn)為View A調(diào)用hitTest:withEvent:,而此方法進而會調(diào)用pointInside:withEvent:方法,顯然返回true,并對View A所有的子視圖(View B,View C)進行hit-test;
2. View B調(diào)用pointInside:withEvent:方法,返回false,對應(yīng)hitTest:withEvent:返回nil;
3. View C調(diào)用pointInside:withEvent:方法,返回true,則對View C所有的子視圖(View D,View E)進行hit-test;
4. View D調(diào)用pointInside:withEvent:方法,返回false,對應(yīng)hitTest:withEvent:返回nil;
5. View E調(diào)用pointInside:withEvent:方法,返回true,而且View E沒有子視圖了,則hitTest:withEvent:返回View E,再往回溯,View C對應(yīng)hitTest:withEvent:也返回View E,View A也返回View E,這樣Application就知道了View E是第一響應(yīng)對象。
2.1 處理事件
? ? ? ? 當Application就知道了第一響應(yīng)對象后,就會把事件交給第一響應(yīng)對象來處理,如果第一響應(yīng)對象能順利處理事件,則整個響應(yīng)結(jié)束,但是第一響應(yīng)對象如果無法處理事件,就會把事件傳遞給下一個響應(yīng)對象(nextResponder),一直沿著響應(yīng)鏈向上回溯。那么第一響應(yīng)對象的下一響應(yīng)對象是誰呢?我們結(jié)合下圖進行解釋:

左邊為例:
1. 如果接收到事件的初始View無法處理事件, 那么這個事件會交給他的SuperView, 因為他不是viewController等級中的最高級View。
2. SuperView嘗試處理事件,如果SuperView無法處理,則這個事件會交給他的SuperView,因為他不是viewController等級中的最高級View。
3. 這樣事件就傳遞到viewController等級中的最高級View,如果最高級View不能處理就會徹底給viewController。
4. viewController嘗試處理事件,如果無法處理就傳遞給Window,Window嘗試處理,無法處理就傳遞給Application。
5. Application嘗試處理,如果無法處理就就丟棄該事件。
總之,當view無法處理事件時,如果是最高級view,并存在viewController,則傳遞給viewController,否則傳遞給SuperView,繼續(xù)往上嘗試處理事件。
view -> ViewController -> window -> Application -> 丟棄
3. 注意事項
1. 遍歷查找最佳響應(yīng)者時,從所有子視圖的最上層view往下遍歷(從subviews數(shù)組最后一個元素往前便利)。
2. 遍歷查找最佳響應(yīng)者時,當一個子視圖告訴OS沒有被點擊時,則它的子視圖不會被檢查(類似二分法)。
3. 子視圖在父視圖邊界外時,并且父親的clipsToBounds屬性為false時,子視圖接受不到事件。
4. 一個UIWindow對象在某一時刻只能有一個響應(yīng)者對象可以成為第一響應(yīng)者。
5. 成為第一響應(yīng)者必須要canBecomeFirstResponder,才能becomeFirstResponder。
6. 手動設(shè)置某個view becomeFirstResponder時,當有事件發(fā)生時,該view不一定最先響應(yīng)。比如點擊button時會觸發(fā)自身響應(yīng),而不管有無其他becomeFirstResponder的view。
7. 第一響應(yīng)者主要體現(xiàn)在,事件發(fā)生時沒有響應(yīng)者出來處理事件,這時候第一響應(yīng)者就會嘗試處理事件。