一部iOS設(shè)備會產(chǎn)生各種各樣的事件(UIEvent 實例
)比如:觸摸屏幕、遠程控制等芽偏,這些事件發(fā)生了就需要有響應(yīng)者(UIResponder 實例
)去響應(yīng)這些事件雷逆。這就需要一套事件響應(yīng)機制。
事件類型
查看UIEventType
的定義,我們知道有4種事件類型哮针。
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches,
UIEventTypeMotion,
UIEventTypeRemoteControl,
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
};
其中UIEventTypeTouches
就是觸摸手機屏幕產(chǎn)生的事件关面。UIEventTypeMotion
我們能接觸到的就可能是手機的shake,也就是搖晃事件了十厢。UIEventTypeRemoteControl
等太,UIEventTypePresses
對于手機App開發(fā)者一般遇不到。UIEventTypePresses
的名字有一定的迷惑性蛮放,其實它指的是物理按鍵被按下缩抡,比如電視的遙控器。
除了UIEventTypeTouches
其他事件我們都難以遇到包颁。
事件響應(yīng)者和響應(yīng)鏈
能響應(yīng)事件的都是UIResponder
及其子類瞻想。常見的UIResponder
的子類有:UIView
,UIViewController
娩嚼,UIApplication
和UIApplicationDelegate
下面這張圖是Apple官方文檔中的圖蘑险,可以看出事件的響應(yīng)鏈與視圖的層級關(guān)系基本一致。值得注意的是響應(yīng)鏈的末端是...->UIWindow->UIApplication->UIApplicationDelegate
岳悟。一個UIResponder
的nextResponder
指向它的下一個響應(yīng)者佃迄。你可以重寫(override)nextResponder方法改變下一個響應(yīng)者。實際上有些類已經(jīng)重寫了nextResponder贵少,比如一個UIView如果是UIController的根視圖呵俏,它的nextResponder會指向view controller。詳見 Using Responders and the Responder Chain to Handle Events
的Altering the Responder Chain
一節(jié)滔灶。
如果順著響應(yīng)鏈沒有發(fā)現(xiàn)能夠響應(yīng)事件的響應(yīng)者普碎,那么這個事件就會被忽略。
我們看到录平,UIResponder中對應(yīng)每種事件類型都有對應(yīng)的事件響應(yīng)方法麻车,比如對于touch事件來說有touchesBegan: withEvent:
,touchesMoved: withEvent:
,touchesCancelled: withEvent:
,touchesEnded: withEvent:
,還有對于motion事件來說有motionBegan: withEvent:
,motionEnded: withEvent:
,motionCancelled: withEvent:
缀皱。還有針對其他事件的方法,你可以去UIResponder類里去查看动猬。
如果一個UIResponder子類重寫(override)了上述所說事件響應(yīng)方法唆鸡,那么事件就算這個被這個類的實例響應(yīng)了。事件就不再會沿著響應(yīng)鏈傳遞枣察。一個UIControl(UIButton是UIControl的子類),不管它有沒有被增加一個target燃逻,事件傳遞到它這里就終止了序目。我猜UIControl內(nèi)部實現(xiàn)了上面說的幾個響應(yīng)事件的方法。
比如一個UIView子類重寫了touchesBegan: withEvent:
,touchesMoved: withEvent:
,touchesCancelled: withEvent:
,touchesEnded: withEvent:
伯襟,如果有觸摸事件傳遞到這個子類的實例猿涨,就會調(diào)用這些方法,并停止向下傳遞姆怪。
事件傳遞機制
上面說的事件響應(yīng)鏈有一個起點叛赚,這個起點叫做first responder。每一種事件都有找到或者指定first responder的規(guī)則稽揭,這里我們只關(guān)心觸摸事件(touch event)俺附。
當手機屏幕被觸摸,系統(tǒng)將其包裝成觸摸事件(touch event)并傳遞給UIApplication,UIApplication傳遞給UIWindow(也是一個UIView的子類)溪掀,UIWindow調(diào)用hitTest:withEvent:
淋昭,得到一個能夠響應(yīng)觸摸事件(touch event)的UIView缚忧。
UIView
的hitTest:withEvent:
方法,會遍歷view的層級結(jié)構(gòu),找到能夠響應(yīng)這個事件的最深層的子視圖痕鳍,成為觸摸事件的first responder。如果first responder不能響應(yīng)事件冰垄,這個事件就會沿事件響應(yīng)鏈傳遞直到被響應(yīng)或者被忽略滑绒。
上面的圖片展示了hitTest:withEvent:
是如何實現(xiàn)的。其中幾個點我們需要注意骚勘,首先判斷的是hidden,userInteractionEnable和alpha這幾個屬性铐伴,然后使用pointInside:withEvent:
判斷點擊事件是否發(fā)生在該UIView內(nèi)。然后是倒序遍歷所有子視圖并調(diào)用他們的hitTest:withEvent:
方法调鲸。
事件傳遞機制原理的幾個應(yīng)用
1盛杰、非矩形可點擊區(qū)域。比如一個button的一個圓形區(qū)域可以被點擊藐石,其他區(qū)域不可點擊即供。只要重寫pointInside:withEvent:
,在圓形區(qū)域的點返回YES于微,不在圓形區(qū)域的返回NO逗嫡。
2青自、按鈕超出父視圖。默認情況下驱证,按鈕超出父視圖的部分是不可以點擊的延窜。我們可以重寫父視圖的pointInside:withEvent:
方法,讓按鈕超出父視圖的部分也返回YES抹锄。
參考:
1. Using Responders and the Responder Chain to Handle Events