iOS 中事件的響應(yīng)鏈和傳遞鏈

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,UIApplicationUIApplicationDelegate.

我們借用一下官方文檔的圖,可以看出事件響應(yīng)鏈基本上和視圖的層級(jí)一致。一個(gè)UIResponsenextResponse 指向他的下一個(gè)響應(yīng)者,你可以重寫nextResponse方法改變下一個(gè)響應(yīng)者。比如一個(gè)view如果是UIController的根視圖,它的nextResponse會(huì)指向UIController,否則就會(huì)指向它的父視圖。

image

響應(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;
}

說明

  1. 如果最終hitTest 沒有找到第一響應(yīng)者,或第一響應(yīng)者沒有處理該事件,則該事件會(huì)沿著響應(yīng)者鏈向上回溯。若最終 UIWindow UIApplication都不能處理該事件,則會(huì)被丟棄。
  2. hitTest:withEvent:方法會(huì)忽略隱藏的視圖,禁止用戶操作的視圖,以及alpha小于0.01的視圖。
  3. 如果一個(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: 方法來處理這種情況。
  4. 我們可以重寫 hitTest:withEvent: 來達(dá)到某些特定的目的。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容