iOS 的事件機(jī)制

響應(yīng)鏈

iOS 大多數(shù)的事件分發(fā)都是依賴 UIResponder 響應(yīng)鏈來完成,響應(yīng)鏈?zhǔn)怯梢幌盗墟溄釉谝黄鸬捻憫?yīng)者組成的。一般情況下,一條響應(yīng)鏈開始于第一響應(yīng)者,結(jié)束于application對(duì)象。如果一個(gè)響應(yīng)者不能處理事件,則會(huì)將事件沿著響應(yīng)鏈傳到下一響應(yīng)者。那么當(dāng)我點(diǎn)擊屏幕,系統(tǒng)發(fā)生了什么呢?

我們以如下的視圖層級(jí)為例:

hit-test

在點(diǎn)擊之后,系統(tǒng)會(huì)優(yōu)先找出響應(yīng)的視圖,此過程叫做 hit-test

關(guān)于 hit-test 有 2 個(gè)方法與其有關(guān)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; 
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

當(dāng)你點(diǎn)擊到 View E 的時(shí)候系統(tǒng)做發(fā)生一些什么呢,

  1. 系統(tǒng)底層發(fā)出一個(gè) event
  2. 系統(tǒng)分發(fā)事件到我們 App 的 UIApplication
  3. UIApplication 會(huì)對(duì)當(dāng)前的 KeyWindow 調(diào)用 hitTest 方法來確定觸摸的位置時(shí)候在 Window 內(nèi),hitTest 方法會(huì)調(diào)用 pointInSide 方法來確認(rèn)一個(gè)點(diǎn)是否包含在我們 Window 視圖之類。
  4. UIWindow 會(huì)對(duì)其子視圖,也就是 View A 調(diào)用 hitTest ,hitTest 如步驟 3,調(diào)用 pointInSide 來確定點(diǎn)時(shí)候愛自身之類,這個(gè)時(shí)候返回 NO, 因?yàn)?E 不在 A 的里面。
  5. ViewC 對(duì)其 自身做 hitTest 和 pointInSide ,返回 YES, 因?yàn)?E 在 C 內(nèi)。
  6. View D 對(duì)其 自身做 hitTest 和 pointInSide ,返回 NO, 因?yàn)?E 不在 D 內(nèi)。
  7. View E 對(duì)其 自身做 hitTest 和 pointInSide ,返回 YES
  8. 由于 View E 是 hitTest 的最終點(diǎn),沒有其他子視圖,于是結(jié)束 hitTest 的流程,返回自身,結(jié)束遞歸回調(diào)。

像上面的步驟,我們將尋找的過程稱之為 hit-Testing, 這個(gè)被找到的 View 稱之為 hit-Test View 。

找到 hit-Test View 之后我們便可以確定響應(yīng)的對(duì)象,

touchesEvent

確定了第一響應(yīng)者之后, touchesBegan,touchesMoved,touchesEnded 方法會(huì)根據(jù)情況依次被調(diào)用。

關(guān)于 touch 的方法有 4 個(gè)分別是:

open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

事件傳遞

最有機(jī)會(huì)處理事件的對(duì)象是hit-test視圖或第一響應(yīng)者。如果這兩者都不能處理事件,UIKit就會(huì)將事件傳遞到響應(yīng)鏈中的下一個(gè)響應(yīng)者。每一個(gè)響應(yīng)者確定其是否要處理事件或者是通過nextResponder方法將其傳遞給下一個(gè)響應(yīng)者。這一過程一直持續(xù)到找到能處理事件的響應(yīng)者對(duì)象或者最終沒有找到響應(yīng)者。


事件傳遞的流程
事件傳遞的流程

當(dāng)系統(tǒng)檢測到一個(gè)事件時(shí),將其傳遞給初始對(duì)象,這個(gè)對(duì)象通常是一個(gè)視圖。然后,會(huì)按以下路徑來處理事件(我們以左圖為例):

初始視圖(initial view)嘗試處理事件。如果它不能處理事件,則將事件傳遞給其父視圖。
初始視圖的父視圖(superview)嘗試處理事件。如果這個(gè)父視圖還不能處理事件,則繼續(xù)將視圖傳遞給上層視圖。
上層視圖(topmost view)會(huì)嘗試處理事件。如果這個(gè)上層視圖還是不能處理事件,則將事件傳遞給視圖所在的視圖控制器。
視圖控制器會(huì)嘗試處理事件。如果這個(gè)視圖控制器不能處理事件,則將事件傳遞給窗口(window)對(duì)象。
窗口(window)對(duì)象嘗試處理事件。如果不能處理,則將事件傳遞給單例app對(duì)象。
如果app對(duì)象不能處理事件,則丟棄這個(gè)事件。
從上面可以看到,視圖、視圖控制器、窗口對(duì)象和app對(duì)象都能處理事件。

手勢識(shí)別器對(duì)響應(yīng)鏈的影響

手勢和UIView 響應(yīng)鏈的區(qū)別
手勢和UIView 響應(yīng)鏈的區(qū)別

手勢相對(duì)于 UIView 的響應(yīng)鏈擁有更高的級(jí)別,在事件分發(fā)的時(shí)候會(huì)優(yōu)先給手勢處理,是否進(jìn)行正常的響應(yīng)鏈流程則有手勢識(shí)別器來決定。

cancelsTouchesInView

此屬性可以中斷響應(yīng)鏈的流程,響應(yīng)流程會(huì)取消,使 touchesEnded 中斷,而 touchesCancelled 被調(diào)用。

delaysTouchesBegan

此屬性可以延遲響應(yīng)鏈的開始的流程,使 touchesBegan 延后導(dǎo)致響應(yīng)鏈被取消。

delaysTouchesEnded

此屬性可以延遲響應(yīng)鏈的開始的末尾,使 touchesEnded 延后,從而響應(yīng)鏈不能完整的結(jié)束。

手勢的代理

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;

手勢識(shí)別以及后續(xù)事件是否開始激活,返回 NO 則終止了手勢。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

這個(gè)方法指示出 gestureRecognizer 是否可以和其他的 gestureRecognizer 一起處理事件。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);

返回 YES 時(shí),當(dāng) gestureRecognizer 和 otherGestureRecognizer 同時(shí)響應(yīng)時(shí)候,gestureRecognizer 失效

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);

返回 YES 時(shí),當(dāng) gestureRecognizer 和 otherGestureRecognizer 同時(shí)響應(yīng)時(shí)候,otherGestureRecognizer 失效

UIControl 及其子類的特殊化

上文提到 手勢識(shí)別器可以攔截一個(gè)響應(yīng)鏈,那么我的 UIButton 為什么可以正常工作呢?
文檔中有注明

When a control-specific event occurs, the control calls any associated action methods right away. Action methods are dispatched through the current UIApplication object, which finds an appropriate object to handle the message, following the responder chain if needed.

一個(gè) UIControl 及其子類 、方法,其響應(yīng)事件的方式不同于普通的UIView,它們的事件處理由UIApplication直接分發(fā),和普通的UIView完全不同。所以自然手勢識(shí)別器無法影響。

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

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

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