從用戶點擊屏幕到程序作出反應(yīng)之間都發(fā)生了什么? --- iOS事件響應(yīng)

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

1.事件類型

type

? ? ? ? 當我們操作手機時,一般有觸摸屏幕、搖晃手機、遠程遙控三種方式,分別對應(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é)合一個實例來加深理解:


hit_testing

假設(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é)合下圖進行解釋:


nextResponder

左邊為例:

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)者就會嘗試處理事件。


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

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

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