一、事件鏈
用戶點(diǎn)擊屏幕時(shí),首先 UIApplication
對(duì)象先收到該點(diǎn)擊事件狞尔,再依次傳遞給它上面的所有子 view
,直到傳遞到最上層巩掺。即由系統(tǒng)向最上層 view
傳遞偏序,Application -> window -> root view -> sub view -> ... -> first view
即傳遞鏈。
反之胖替,由最基礎(chǔ)的 view
向系統(tǒng)傳遞研儒,first view -> super view -> ... -> view controller -> window -> Application -> AppDelegate
即響應(yīng)鏈豫缨。
簡單總結(jié),事件鏈包含傳遞鏈和響應(yīng)鏈端朵,事件通過傳遞鏈傳遞上去好芭,通過響應(yīng)鏈找到相應(yīng)的 UIResponse
。
二冲呢、誰來響應(yīng)事件 — 傳遞鏈
只有繼承了 UIResponser
的對(duì)象才能夠接受處理事件栓撞。UIResponse
是響應(yīng)對(duì)象的基類,定義了處理各種事件的接口碗硬。在 UIKit
中我們使用響應(yīng)者對(duì)象 Responder
接收和處理事件瓤湘。一個(gè)響應(yīng)者對(duì)象一般是 UIResponder
類的實(shí)例,它常見的子類包括 UIView
恩尾,UIViewController
和 UIApplication
弛说,這意味著幾乎所有我們?nèi)粘J褂玫目丶际琼憫?yīng)者,如 UIButton
翰意,UILabel
等等木人。
在 UIResponder
及其子類中,我們是通過有關(guān)觸摸 UITouch
的方法來處理和傳遞事件 UIEvent
冀偶,具體的方法如下:
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?)
UIResponder 還可以處理 UIPress醒第、加速計(jì)、遠(yuǎn)程控制事件进鸠,這里僅討論觸摸事件稠曼。
在 UITouch
內(nèi),存儲(chǔ)了大量觸摸相關(guān)的數(shù)據(jù)客年,當(dāng)手指在屏幕上移動(dòng)時(shí)霞幅,所對(duì)應(yīng)的 UITouch
數(shù)據(jù)也會(huì)更新,例如:
這個(gè)觸摸是在哪個(gè) window
或者哪個(gè) view
內(nèi)發(fā)生的量瓜?
當(dāng)前觸摸點(diǎn)的坐標(biāo)是司恳?
前一個(gè)觸摸點(diǎn)的坐標(biāo)是?
當(dāng)前觸摸事件的狀態(tài)是绍傲?
這些都存儲(chǔ)在 UITouch
里面扔傅。另外需要注意的是,在這四個(gè)方法的參數(shù)中烫饼,傳遞的是 UITouch
類型的一個(gè)集合 (而不是一個(gè) UITouch
)猎塞,這對(duì)應(yīng)了兩根及以上手指觸摸同一個(gè)視圖的情況。
們以 UIView
來作為視圖層級(jí)的主要組成元素枫弟,便于理解邢享。但不止 UIView
可以響應(yīng)事件鹏往,實(shí)際只要是 UIResponder
的子類淡诗,都可以響應(yīng)和傳遞事件骇塘。
當(dāng)我們觸摸了屏幕。此時(shí)所擁有的信息是觸摸點(diǎn)的坐標(biāo)韩容,但無法直接知道用戶是想點(diǎn)哪個(gè)視圖款违。需要一個(gè)策略來找到這個(gè)第一響應(yīng)者,
UIKit
為我們提供了命中測(cè)試 hit-testing
來確定觸摸事件的響應(yīng)者
以下為UIView不接受事件處理的情況:
view.hidden = YES;
view.userInteractionEnabled = NO;
view.alpha < 0.01;
具體流程如下:
- 用戶在點(diǎn)擊屏幕群凶;
- 系統(tǒng)將點(diǎn)擊事件加入到
UIApplication
管理的消息隊(duì)列中插爹; -
UIApplication
會(huì)從消息隊(duì)列中取出該事件傳遞給UIWindow
對(duì)象; - 在
UIWindow
中調(diào)用方法hitTest:withEvent:
请梢,在hitTest:withEvent:
方法中調(diào)用pointInside:withEvent:
來判斷當(dāng)前點(diǎn)擊的點(diǎn)是否在UIWindow
內(nèi)部赠尾; - 如若返回
yes
,則倒序遍歷其子視圖找到最終響應(yīng)的子view
毅弧; - 如果最終返回一個(gè)
view
气嫁,那么即為最終響應(yīng)view
并結(jié)束事件傳遞,如果無值返回則將UIWindow
作為響應(yīng)者够坐。
其中核心方法如下:
// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
// default returns YES if point is in bounds
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
- 方法
hitTest:withEvent:
用來獲取最終響應(yīng)事件的view
寸宵。 - 方法
pointInside:withEvent:
,用來判斷點(diǎn)擊的位置是否在視圖范圍內(nèi)元咙。
三梯影、怎樣傳遞事件 —— 響應(yīng)鏈
由離用戶最近的view向系統(tǒng)傳遞。如下所示:
圖中淺灰色的箭頭是指將
UIView
直接添加到 UIWindow
上情況庶香。
響應(yīng)鏈應(yīng)該是:ViewB -> ViewC -> ViewA -> UIViewController 對(duì)象 -> UIWindow 對(duì)象 -> UIApplication 對(duì)象 -> App Delegate
觸摸事件首先將會(huì)由第一響應(yīng)者響應(yīng)甲棍,觸發(fā)其 (target action
) 等方法,根據(jù)觸摸的方式不同(如拖動(dòng)赶掖,雙指)救军,具體的方法和過程也不一樣。若第一響應(yīng)者在這個(gè)方法中不處理這個(gè)事件倘零,則會(huì)傳遞給響應(yīng)鏈中的下一個(gè)響應(yīng)者觸發(fā)該方法處理唱遭,若下一個(gè)也不處理,則以此類推傳遞下去呈驶。若到最后還沒有人響應(yīng)拷泽,則會(huì)被丟棄(比如一個(gè)誤觸)。