理解響應者和響應鏈如何處理事件
阿新 • • 發佈:2018-11-05
事件在APP中如何傳遞
先看一下Apple文件是如何解釋事件的傳遞:
APP通過UIResponder的例項來處理事件。UIResponder 是UIView/UIViewController/ UIApplication的父類。Responder接受到事件之後只能選擇處理事件
或者將事件傳遞給另一個responder物件,當你的APP接收到一個事件時,UIKit會自動最合適的responder,這就是第一響應者(first responder)。未處理的事件
是會在一個有效的響應鏈裡傳遞的,這條響應鏈是由你的APP的responder動態配置的。如果你當前接受事件的view沒有處理事件,UIKit會將事件傳遞給它的父檢視,
如果後面的view都沒有處理事件的話,它會一次往後傳遞直到UIApplication。它的路徑大概是View->root View->Viewcontroller->root Viewcontroller-
>UIWindow->UIApplication。
注意:如果你UIResponder的例項已經不再響應鏈之中的話,事件會再從UIApplication傳遞到app delegate
複製程式碼
通過上文的文件我們可以看到,我們的APP之所以能夠處理事件都是依靠responder物件。如果當前接受 到事件的view沒有處理事件,該事件會一直傳遞到UIApplication。
決定first responder
現在我們知道UIKit會自動指定first responder,接下來我們看一下都支援哪些型別的事件:
Event Type First responder
Touch events 發生touch事件的那個view
Press events 聚焦的那個物件
Shake-motion events 由你或者UIKit指定
Remote-control events 由你或者UIKit指定
Editing menu messages 由你或者UIKit指定
注意:
1.Motion events 並不遵循responder chain
2.Control的Action messages並不是events,但是它們也利用responder chain
複製程式碼
通過文件我們知道UIKit會自動查詢first responder,那麼它是如何實現這個查詢過程呢?通過文件 我們可以瞭解到它底層是通過view-based hit-testing來確定哪裡發生了event,具體是通過hitTest:withEvent:
方法來查詢view,將會成為first responder。
注意:下面幾種情況hitTest:withEvent: 方法會直接忽略該view和其所有的subView
1、view的bounds不包含touch的位置
2、如果view的clipsToBounds為false,它的subView的bounds超出了view的bounds,即使該subView的bounds包含touch的位置
複製程式碼
現在我們知道了UIKit通過hitTest:withEvent:
方法來確定first responder,那麼它是如 何確定touch的位置的呢?這個問題我們還是要看一下文件說明:
當觸控發生時,UIKit會建立一個UITouch物件,並將其與檢視相關聯。隨著觸控位置或其他引數的
變化,UIKit會用新資訊更新同一個UITouch物件。唯一不變的屬性是檢視。(即使觸控位置移動到原始檢視之外,觸控的檢視屬性中的值也不會改變。)當觸控結束時,UIKit釋放UITouch物件。
複製程式碼
看完這段話就知道了UIKit是根據UITouch物件來判斷的touch的位置。 OK,現在我們看一下event分發的流程參考:
1.觸碰螢幕產生事件UIEvent並存入UIApplication中事件佇列中,並在整個檢視結構中自下而上進行分發,如下圖
2.UIWindow 接收到事件開始進行最優響應檢視查詢過程(逆序遍歷子檢視)
3.當到UiviewController這一層時同樣對其檢視開始最優響應檢視查詢,該查詢會呼叫上述提到的兩個方法,採用逆序查詢也是為了優化查詢速度,畢竟後新增的檢視易於命中
複製程式碼
修改responder chain
你可以通過重寫你responder object的nextResponder
來修改responder chain。很多UIKit的類已經重寫了該屬性並返回具體的物件。具體詳情請看文件。
實際應用
在日常開發中我們經常會碰到button超出父檢視的需求,在這種情況下,超出部分是預設不相應的。這時候我們要重寫父檢視的func point(inside point: CGPoint, with event: UIEvent?) -> Bool
方法來讓父檢視響應超出的部分。
效果圖:
程式碼示例:
class RootView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
//超出範圍
let radius: CGFloat = 10.0
print(self.subviews.count)
var largeBounds = bounds
largeBounds = CGRect(x: bounds.origin.x - radius, y: bounds.origin.y - radius, width: bounds.size.width + radius, height: bounds.size.height + radius)
return largeBounds.contains(point)
}
}
這樣超出部分就可以點選響應事件了.
複製程式碼
總結
- event分發與傳遞:自上而下(UIApplication-window-.....)
- event響應:自下而上(view-superView-.........)