事件傳遞和響應機制

蘋果文檔
https://developer.apple.com/documentation/uikit?language=objc

(一)事件傳遞過程
蘋果註冊了一個source1(基於mach port)用來接收系統事件,其回調函數爲_IOHIDEventSystemClientQueueCallback()。當一個硬件事件(觸摸、搖晃鎖屏等)發生後,首先由IOKit.framework生成一個IOHIDEvent事件,由SpringBoard接收;這個SpringBoard是一個標準的應用程序,用來管理iOS的主屏幕,除此之外像windowSever(窗口服務器)、bootstrapping(引導應用程序)以及在啓動時一些初始化設置都是由這個應用程序負責的。它是iOS程序中事件的第一個接受者。只能接受少數的事件比如:按鍵(鎖屏/靜音)、加速、觸摸、加速、接近傳感器等幾種Event,隨後用mach_port轉發給需要的App的進程。隨後蘋果註冊的source1會觸發回調,並調用_UIApplicationHandleEventQueue()進行應用的內部分發。
_UIApplicationHandleEventQueue()會把IOHIDEvent處理包裝成UIEvent對象進行處理和分發,其中包括識別UIGesture/處理屏幕旋轉/發送給UIWindow等。通常事件比如UIButton點擊,touchesBegin/Move/End/Cancle事件都在這個回調中完成。
分發事件首先是尋找點擊位置最合適的view:
1)首先判斷keyWindow是否能接受觸摸事件
2)如果能,觸摸點是否在自己的有效區域內
3)從後往前遍歷子控件(從數組倒序遍歷),若是有符合條件的,就會重複前兩個步驟與

UIView不能接受觸摸的情況:
1.不接受交互userInteractionEnabled = NO;
2.隱藏hidden = YES;
3.透明度小於等於0.01 alpha = 0~0.01

尋找事件點擊的最合適View過程中,事件在不斷的往上層視圖傳遞,通過:

  • (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
  • (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

視圖接收事件後,會調用hitTest: withEvent:方法尋找更合適的View,先通過此方法把事件傳遞給子View,子控件再調用這個方法尋找,若子View沒有出現合適的View,那麼父View則是最合適的View
在調用hitTest: withEvent:方法後,會在內部調用pointInside: withEvent:判斷是否在有效範圍內,若返回YES則會繼續倒序遍歷子View,若沒有則返回自己,若爲NO,則說明不是有效區域,hitTest: withEvent:返回nil
在這裏插入圖片描述Hit-Test和Hit-Testing
假設用戶接觸了上圖的View E區域,那麼 iOS將會按下面的順序反覆檢測 subview 來尋找 Hit-Test View
1 觸摸區域視圖A內,所以檢測視圖A的subview B和C;
2 觸摸區域不在視圖B內,但是在C內,所以檢查視圖C的subview D和E;
3 觸摸區域不在D內,在E內,視圖E在整個視圖體系中是lowest view ,所以視圖E就是Hit-Test View 。
hitTest:withEvent實現原理如下 :

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.判斷當前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2. 判斷點在不在當前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    // 3.從後往前遍歷自己的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        // 把當前控件上的座標系轉換成子控件上的座標系
       CGPoint childP = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) { // 尋找到最合適的view
            return fitView;

        }
    }
    // 循環結束,表示沒有比自己更合適的view
    return self;
}

(二)事件響應過程過程

在找到合適的View後就會調用該View的touches方法進行響應處理具體的事件,找不到View就不會調用touches方法。
響應者鏈條是在事件傳遞過程中(UIResponder)組合形成的一個響應者鏈條。
響應事件查找時是和傳遞過程相反,若當前View爲控制器View,則上一個responder爲Controller,若不是控制器的View,上個響應者爲parentView,一直追溯到控制器,若控制器也沒有響應,則會到window,若依然沒有響應則會交給UIApplication,如果都不響應事件就作廢了。
在這裏插入圖片描述
如上圖,響應者鏈有以下特點:
響應者鏈通常由initial view 開始
UIView的 nextResponder是它的superview;如果 UIView已經是其所在 UIViewController 的top view,那麼 UIView 的nextResponder就是 UIViewController
UIViewController 如果有 Super ViewController ,那麼它的nextResponder 爲其Super ViewController 最表層的View;如果沒有它的 nextResponder 就是 UIWindow
UIWindow 的contentView 指向 UIApplication ,將其作爲nextResponder;
UIApplication 是響應者鏈的終點,它的 nextResponder指向 nil,整個responder chain 結束。
(三)總結
事件的鏈有兩條:事件的響應鏈; Hit-Testing 時事件的傳遞鏈。
響應鏈:由離用戶最近的view向系統傳遞。 initial view –> super view –> …–> view controller –> window –> Application –> AppDelegate
· Hit-Testing傳遞鏈:由系統向離用戶最近的 view傳遞。 UIKit –> active app’s event queue –> window –> root view –>…–>lowest view
事件的傳遞是從上到下從父控件到子控件,事件的響應是從下到上,從子控件到父控件,順着響應者鏈條向上傳遞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章