UIResponder事件響應鏈學習筆記

1、什麼是響應鏈?

大多數事件的分發都是依賴響應鏈的。響應鏈是由一系列連接在一塊兒的響應者(UIResponse子類:UIApplicationUIViewControllerUIView)組成的。通常狀況下,一條響應鏈開始於第一響應者,結束於application對象。若是一個響應者不能處理事件,則會將事件沿着響應鏈傳到下一響應者。ios

@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
複製代碼

2、事件的傳遞與響應

2.一、事件的傳遞:尋找事件的第一響應者(Hit-Testing)

事件被蘋果分爲3種大類型: 觸摸事件加速計事件以及遠程遙控事件git

當一個事件發生後,事件會從父控件傳給子控件,也就是說由github

硬件 -> 系統 -> UIApplication -> UIWindow -> SuperView -> SubViewbash

以上就是事件的傳遞,也就是尋找第一響應者的過程。 符合第一響應者的條件包括:app

  • touch事件的位置在響應者區域內 pointInside:withEvent: == YES
  • 響應者 self.hidden != NO
  • 響應者 self.alpha > 0.01
  • 響應者 self.userInteractionEnabled = YES
  • 遍歷 subview 時,是從上往下順序遍歷的,即 view.subviews 的 lastObject 到 firstObject 的順序,找到合適的響應者view,即中止遍歷.

第一響應者對於接收到的事件有3種操做:ide

  • 不攔截,默認操做。事件會自動沿着默認的響應鏈往下傳遞
  • 攔截,再也不往下分發事件。重寫 touchesBegan:withEvent: 進行事件處理,不調用父類的 touchesBegan:withEvent:
  • 攔截,繼續往下分發事件。重寫 touchesBegan:withEvent: 進行事件處理,同時調用父類的 touchesBegan:withEvent: 將事件往下傳遞

下圖展現了Hit-Testing的邏輯ui

2.二、事件的響應:一旦事件的第一響應者肯定了,這個事件所處的響應鏈就肯定了

案例一:下圖是官網對於響應鏈的示例展現

  • 圖中虛線箭頭是指若該UIView是做爲UIViewController根視圖存在的,則其nextResponderUIViewController對象;
  • 如果直接add在UIWindow上的,則其nextResponder爲UIWindow對象。
// 若觸摸發生在UITextField上,則事件的傳遞順序是:
UITextField ——> UIView ——> UIView ——> UIViewController ——> UIWindow ——> UIApplication ——> UIApplicationDelegation
複製代碼

案例二:參考下圖

  • 一、 首先由 view 來嘗試處理事件,若是他處理不了,事件將被傳遞到他的父視圖 superview
  • 二、superview 也嘗試來處理事件,若是他處理不了,繼續傳遞他的父視圖 UIViewcontroller.view
  • 三、UIViewController.view 嘗試來處理該事件,若是處理不了,將把該事件傳遞給 UIViewController
  • 四、UIViewController 嘗試處理該事件,若是處理不了,將把該事件傳遞給主窗口 Window
  • 五、主窗口 Window 嘗試來處理該事件,若是處理不了,將傳遞給應用單例 Application
  • 六、若是 Application 也處理不了,則該事件將會被丟棄

事件的傳遞和響應的區別?

事件的傳遞是從上到下(父控件到子控件),事件的響應是從下到上(順着響應者鏈條向上傳遞:子控件到父控件。atom

如何判斷上一個響應者?

若是當前這個view是控制器的view,那麼控制器就是上一個響應者 若是當前這個view不是控制器的view,那麼父控件就是上一個響應者spa

響應者鏈條的事件傳遞過程?

若是view 的控制器存在,就傳遞給控制器;若是控制器不存在,則將其傳遞給它的父視圖 在視圖層次結構的最頂級視圖,若是也不能處理收到的事件或消息,則其將事件或消息傳遞給 window 對象進行處理 若是 window 對象也不處理,則其將事件或消息傳遞給 UIApplication 對象 若是 UIApplication 也不能處理該事件或消息,則將其丟棄(銷燬)code

如何作到一個事件多個對象處理?

由於系統默認作法是把事件上拋給父控件,因此能夠經過重寫本身的touches方法和父控件的touches方法來達到一個事件多個對象處理的目的。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
    // 1.本身先處理事件...
    NSLog(@"do somthing...");
    // 2.再調用系統的默認作法,再把事件交給上一個響應者處理
    [super touchesBegan:touches withEvent:event]; 
}
複製代碼

事件的生命週期

一、系統響應階段

二、APP響應階段

應用場景:

  • 一、 重寫子view的point:inside`` → 擴大Button的點擊區域(上下左右各增長20)
  • 二、 重寫父view的point:insde`` →子view超出了父view的bounds響應事件
  • 三、 若是一個Button被一個View蓋住了,在觸摸View時,但願該Button可以響應事件
  • 四、 特殊的UIScrollView
  • 五、 利用響應鏈傳遞自定義UI事件

總結

  • 一、若是父控件不能接收觸摸事件,則子控件也沒法接收觸摸事件
  • 二、若是想讓控件不處理觸摸事件,能夠設置userInteractionEnabled = NO,結果是包括父控件在內的全部子控件都不能處理觸摸事件(雖然設置透明度和hidden=YES也能夠,可是那樣就看不見了注意:若是父控件的透明度設置爲0或者hidden=YES,那麼子控件也是不可見的。)
  • 三、遍歷一個控件的子控件的順序是從上到下的(最後添加的view最早被遍歷)。
  • 四、指定某一個子控件響應事件,只須要在父控件的hitTest中返回指定的子控件就能夠。
  • 五、若是一個控件的isUserInteractionEnabled=false,想讓它繼續繼續處理觸摸事件,能夠在它的父控件的hitTest方法中直接返回它。
  • 六、hitTest查找第一響應者的時候,即使父控件是第一響應者,仍是要調用子控件的hitTest方法,不然怎麼知道是否是還有其餘最合適的響應者
  • 七、
    • → 一、先調用父控件的point:inside:方法
    • → 二、調用最上面子控件的point:inside:方法
    • → 三、若是最上面子控件的point:inside:方法返回false,則對應的hitTest返回nil
    • → 四、若是最上面子控件的point:inside:方法返回true,則調用對應的hitTest方法重複上面的操做返回子控件的最合適子控件

疑問?

  • UIGestureRecongnizerUIContorl均可以處理觸摸事件
    • UIGestureRecognizer:使用addGestureRecognizer方法處理事件
    • UIControl:使用addTarget方法處理事件
    • UIResponder:使用touches等一系列方法處理事件

UIButton繼承自UIControlUIControl繼承自UIView,若是給UIButton添加了手勢,並實現了本身的處理事件的>>方法,當點擊UIButton的時候發現touches方法走了,手勢方法(addGestureRecognizer)也走了,本身的方法(addTarget)沒有走。

由此能夠得出一個結論:UIGestureRecognizer的優先級 > >UIContol的優先級,當一個UIButton即實現了本身的方法(addTarget),又添加了手勢方法(addGestureRecognizer)的話,本身的方法(addTarget)會被屏蔽掉,不論是否添加了手勢,touches方法都會處理。

參考連接:

Hit-Testing in iOS

iOS觸摸事件全家桶

iOS事件處理之Hit-Testing

iOS 事件的傳遞響應機制

iOS事件響應鏈中Hit-Test View的應用

UIView的hitTest和pointInside方法

相關文章
相關標籤/搜索