iOS 的事件機制

響應(yīng)鏈

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

我們以如下的視圖層級為例:

hit-test

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

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

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

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

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

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

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

touchesEvent

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

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

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?)

事件傳遞

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


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

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

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

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

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

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

cancelsTouchesInView

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

delaysTouchesBegan

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

delaysTouchesEnded

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

手勢的代理

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

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

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

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

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

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

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

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

UIControl 及其子類的特殊化

上文提到 手勢識別器可以攔截一個響應(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.

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內(nèi)容