【原】iOS觸摸事件深度解析

概述

本文主要解析從咱們的手指觸摸蘋果設備到最終響應事件的整個處理機制。本質上講,整個過程能夠分爲兩個步驟:html

步驟1:找目標。在iOS視圖層次結構中找到觸摸事件的最終接受者;ios

步驟2:事件響應。基於iOS響應者鏈(Responder Chain)處理觸摸事件git

找目標

在找目標階段所使用到的兩大利器是UIView的 hitTest:withEvent: 以及 pointInside:withEvent: 方法。找目標的過程也稱爲hit-Testing。先來看一張圖(注: 圖來自MJ)比較直觀:github

下面解釋一下處理原理:編程

一、手指觸摸屏幕,這個動做被包裝成一個UIEvent對象發送給當前活躍的UIApplication (Active Application),Application將該Event對象插到任務隊列的末尾等待處理(先進先出,先來的先處理);數組

二、UIApplication單例將事件發送給APP的主Window(全部顯示的view都添加在Window上);app

三、主Window調用視圖層次結構上逐級使用hit-Testing確認最終的響應目標,這個目標也稱爲hitTesting viewide

 

 在沒有作任何重載操做的前提下,系統默認的hit-Testing的處理機制以下:spa

當前view調用自身的pointInside: withEvent:方法判斷觸摸點是否在本身範圍內:code

  • 若pointInside: withEvent:方法返回NO,則說明觸摸點不在本身範圍內,則 當前view的hitTest: withEvent:方法返回nil,當前view上的全部subview都不作判斷。有點領導的意見一票否決的味道。
  • 若pointInside: withEvent:方法返回YES,則說明觸摸點在本身的範圍內。但沒法判斷是否在本身身上仍是在subview的身上。此時,遍歷全部的subviews,對每一個subview調用hitTest方法。這裏要注意,遍歷的順序是從當前view的subviews數組的尾部開始遍歷。所以離用戶最近的上層的subview會優先被調用hitTest方法。
  • 一旦hitTest方法返回非空的view,則被返回的view就是最終相應觸摸事件的view,尋找hitTesting view的階段到此結束,再也不遍歷。
  • 若當前view的全部subviews的hitTest方法都返回nil,則當前view的hitTest方法返回self做爲最終的hitTesting view,處理結束。

 

以上就是第一階段尋找響應view的機制。這裏咱們結合一個具體的例子再過一遍(圖片引自技術哥的博客):

當用戶點擊ViewD所在的區域時會進行如下hit-Testing:

  • ViewA的pointInside返回YES,由於觸摸點在其bounds內。遍歷ViewA的兩個subview;
  • ViewB的pointInside返回NO,由於觸摸點不在其bounds內,ViewB的hitTest方法返回nil。並且發生一票否決,在ViewB上的全部subviews受到牽連將再也不進行hit-Testing處理。ViewC的pointInside返回YES,由於觸摸點在其bounds範圍內,ViewC的hitTest方法返回默認處理,也就是 return [super hitTest:point withEvent:event]; 遍歷ViewC的兩個subview;
  • ViewD的pointInside返回YES,由於觸摸點在其bounds範圍內,且ViewD沒有subview,所以hitTest方法返回其本身。hitTesting view找到,結束處理。

 

這裏有幾點須要強調:

一、hitTest方法調用pointInside方法;

二、hit-Testing過程是從superView向subView逐級傳遞,也就是從層次樹的根節點向葉子節點傳遞;

三、遇到如下設置時,view的pointInside將返回NO,hitTest方法返回nil:

  • view.isHidden=YES;
  • view.alpah<=0.01;
  • view.userInterfaceEnable=NO;
  • control.enable=NO;(UIControl的屬性)

hit-Testing過程用代碼能夠描述以下:

 

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden) {
        return nil;
    }
    BOOL inside = [self pointInside:point withEvent:event];
    UIView *hitView = nil;
    if (inside) {
        NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
        for (UIView *subview in enumerator) {
            hitView = [subview hitTest:point withEvent:event];
            if (hitView) {
                break;
            }
        }
        if (!hitView) {
            hitView = self;
        }
        return hitView;
    } else {
        return nil;
    }
}

 

  

 

事件響應

上一部分咱們經過hit-Testing機制找到了hitTesting View,這個hitTesting View就是觸摸事件的響應者Responder。在iOS系統中,可以響應並處理事件的對象稱之爲Responder Object,而UIResponder是全部responder的最頂層基類。當hitTesting view作完本身該作的動做後,能夠根據須要將消息傳給下一級響應者。那下一級響應者會是什麼呢?這取決於iOS中的響應者鏈Responder Chain,以下圖所示:

  • UIView的nextResponder屬性,若是有管理此view的UIViewController對象,則爲此UIViewController對象;不然nextResponder即爲其superview
  • UIViewController的nextResponder屬性爲其管理view的superview.
  • UIWindow的nextResponder屬性爲UIApplication對象。
  • UIApplication的nextResponder屬性爲nil。

更具體的:

  1. 若是hit-test view或first responder不處理此事件,則將事件傳遞給其nextResponder處理,如有UIViewController對象則傳遞給UIViewController,傳遞給其superView。
  2. 若是view的viewController也不處理事件,則viewController將事件傳遞給其管理view的superView。
  3. 視圖層級結構的頂級爲UIWindow對象,若是window仍不處理此事件,傳遞給UIApplication.
  4. 若UIApplication對象不處理此事件,則事件被丟棄。

瞭解響應者鏈有時候能夠幫我解決一些實際問題。我舉個例子,咱們知道,當提供給你一個ViewController你能夠很容易獲得它的view,一句代碼的事情:

viewWanted = someViewController.view;

但若是反過來呢?當給你一個view,讓你找到其所在的ViewController呢?這時候響應者鏈能夠幫上忙了,代碼以下:

@implementation UIView (FindController)
-(UIViewController*)parentController{
    UIResponder *responder = [self nextResponder];
    while (responder) {
	if ([responder isKindOfClass:[UIViewController class]]) {
		return (UIViewController*)responder;
	}
	responder = [responder nextResponder];
    }
    return nil;
}
@end

寫在最後

這篇文章解析了iOS響應觸摸事件的機制。或許你如今找不到這個知識的應用點,可是一旦你理解了,能夠幫助你實現一些特別的需求,好比點擊某個按鈕,響應的倒是另外一個按鈕;穿透某個view點擊到view下面的view...  更有甚者,你能夠用上面的知識解決不規則區域觸摸問題(看我以前的文章)、不添加任何view就能擴大控件的可觸摸區域等。天馬行空,任我翱翔!

 

=======================================================

原創文章,轉載請註明 編程小翁@博客園,郵件zilin_weng@163.com,歡迎各位與我在C/C++/Objective-C/機器視覺等領域展開交流!

歡迎跳轉個人GitHub主頁,關注個人開源代碼。也歡迎你Star/Fork/Watch個人項目。

 =======================================================

相關文章
相關標籤/搜索