iOS 事件的主要由:響應(yīng)連 和 傳遞鏈 構(gòu)成。一般事件先通過傳遞鏈,傳遞下去。響應(yīng)鏈,如果上層不能響應(yīng),那么一層一層通過響應(yīng)鏈找到能響應(yīng)的UIResponse。
- 響應(yīng)連:由最基礎(chǔ)的
view向系統(tǒng)傳遞,first view->super view-> ... ->view controller->window->Application->AppDelegate - 傳遞鏈:有系統(tǒng)向最上層
view傳遞,Application->window->root view-> ... ->first view
iOS 中只有繼承了UIResponse的對(duì)象才能夠接受處理事件。UIResponse是響應(yīng)對(duì)象的基類,定義了處理上述各種事件的接口。常見的子類有:UIView,UIViewController,UIApplication和UIApplicationDelegate.
我們借用一下官方文檔的圖,可以看出事件響應(yīng)鏈基本上和視圖的層級(jí)一致。一個(gè)UIResponse 的 nextResponse 指向他的下一個(gè)響應(yīng)者,你可以重寫nextResponse方法改變下一個(gè)響應(yīng)者。比如一個(gè)view如果是UIController的根視圖,它的nextResponse會(huì)指向UIController,否則就會(huì)指向它的父視圖。
響應(yīng)者鏈
當(dāng)事件發(fā)生了,必須知道有誰來響應(yīng)。在iOS中,由響應(yīng)者鏈來對(duì)事件進(jìn)行響應(yīng)。
響應(yīng)者鏈?zhǔn)怯梢粋€(gè)不同對(duì)象組成的層次結(jié)構(gòu),其中的每個(gè)對(duì)象將依次獲得響應(yīng)事件的機(jī)會(huì)。當(dāng)發(fā)生事件時(shí),事件首現(xiàn)將被發(fā)送到第一響應(yīng)者,第一響應(yīng)者基本是事件發(fā)生的事圖,也就是用戶觸摸屏幕的地方。事件將沿著響應(yīng)者鏈一直向下傳遞,直到被接受并作出處理。
一般來說,第一響應(yīng)者是個(gè)視圖對(duì)象或者其子類對(duì)象,當(dāng)其被觸摸后事件被交由它處理,如果它不處理,事件就會(huì)被傳遞給它的視圖控制器對(duì)象 ViewController(如果存在),然后是它的父視圖(superview)對(duì)象(如果存在),以此類推,直到頂層視圖。接下來會(huì)沿著頂層視圖(top view)到窗口(UIWindow 對(duì)象)再到程序(UIApplication 對(duì)象)。如果整個(gè)過程都沒有響應(yīng)這個(gè)事件,該事件就被丟棄。
基本上,在響應(yīng)者鏈只要有對(duì)象處理事件,事件酒停止傳遞。
First Response -> Window -> Application -> nil
通常情況下,我們?cè)?strong>First Response 這里就會(huì)響應(yīng)請(qǐng)求,然后下面的事件分發(fā)機(jī)制
事件分發(fā)
First Response(第一響應(yīng)者),指的是當(dāng)前接受觸摸的響應(yīng)者對(duì)象,是響應(yīng)者的開端。響應(yīng)者鏈和事件分發(fā)的使命都是找出第一響應(yīng)者。
iOS 檢測(cè)到手指觸摸操作(Touch)時(shí),會(huì)將其打包成一個(gè) UIEvent 對(duì)象,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列中去。UIApplication會(huì)依次從隊(duì)列里取出事件,傳遞給 window對(duì)象,window會(huì)使用hitTest:withEvent: 方法尋找出操作初始點(diǎn)所在視圖。
事件傳遞的兩個(gè)核心方法
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
其中UIView不接受事件處理的情況有
1. hidden = YES,隱藏的視圖.
2. userInteractionEnabled = NO,禁止用戶操作的視圖.
3. alpha <0.01, 透明視圖
hitTest:withEvent:方法的處理流程如下:
- 首先調(diào)用當(dāng)前視圖的
pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi) - 若返回NO,則
hitTest:withEvent:返回 nil。 - 若返回YES,則向當(dāng)前視圖的所有子視圖發(fā)送
hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂視圖一直到最低層視圖,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖返回非空對(duì)象,或者全部子視圖遍歷完畢。 - 若第一次有子視圖返回非空對(duì)象,則
hitTest:withEvent:返回此對(duì)象,處理結(jié)束。 - 若所有子視圖都返回空,則
hitTest:withEvent:返回自身。
示例性代碼如下:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *touchView = self;
if ([self pointInside:point withEvent:event] &&
(!self.hidden) &&
self.userInteractionEnabled &&
(self.alpha >= 0.01f)) {
for (UIView *subView in self.subviews) {
[subview convertPoint:point fromView:self];
UIView *subTouchView = [subView hitTest:subPoint withEvent:event];
if (subTouchView) {
touchView = subTouchView;
break;
}
}
} else {
touchView = nil;
}
return touchView;
}
說明
- 如果最終
hitTest沒有找到第一響應(yīng)者,或第一響應(yīng)者沒有處理該事件,則該事件會(huì)沿著響應(yīng)者鏈向上回溯。若最終UIWindowUIApplication都不能處理該事件,則會(huì)被丟棄。 -
hitTest:withEvent:方法會(huì)忽略隱藏的視圖,禁止用戶操作的視圖,以及alpha小于0.01的視圖。 - 如果一個(gè)子視圖的區(qū)域超過父視圖的 bound 區(qū)域(父視圖的 clipsToBounds 屬性為 NO,這樣超過父視圖 bound 區(qū)域的子視圖內(nèi)容也會(huì)顯示),那么正常情況下對(duì)子視圖在父視圖之外區(qū)域的觸摸操作不會(huì)被識(shí)別, 因?yàn)楦敢晥D的
pointInside:withEvent:方法會(huì)返回 NO, 這樣就不會(huì)繼續(xù)向下遍歷子視圖了。當(dāng)然,也可以重寫pointInside:withEvent:方法來處理這種情況。 - 我們可以重寫
hitTest:withEvent:來達(dá)到某些特定的目的。