了解IOS事件處理的本質關鍵要先掌握幾個概念。首先是事件的派發(Event Delivery)的過程, 一個是響應者鏈條如何構成。
S1: 正是因為當我們點擊屏幕上某個點的時候, IOS會檢查到手指觸摸操作(Touch),并生產一個UITouch對象,將其打包成一個UIEvent對象。然后將其放入當前活動的application的事件對列, UIApplication會從事件對列中按照對列的順序,取出觸摸事件傳遞給UIWindow處理,UIWindow對象會使用hitTest:withEvent:方法來尋找此次的觸摸操作初始點所在的最深層次的視圖(View).**即調用hitTest:withEvent會返回該觸摸點所在的最深層次的視圖。 **
S2: 這就要說到深度優先搜索算法,hitTest:withEvent正是基于深度優先搜索的方式來找到最深層次的視圖對象。所以我來介紹以下深度優先算法的思想, 要理解該思想, 你首先要有樹結構這一概念(參見數據結構中的樹結構)。該思想是從根節點開始遍歷樹,而遍歷的順序是采用把下一個子節點當做當前根節點繼續遍歷。所以其是先遍歷到最樹的最深的一層再層層回朔到根節點,接著在把另外一個子節點當做當前根節點繼續遍歷。正是基于這種思想, 所以我們可以很方便的采用遞歸來實現。如果還是不理解,有兩種辦法幫助你,一種是去找深度優先的動態圖,一看就懂了我說的。另外一種方法是去復習數據結構與算法。
S3:根據官方文檔給出的條件是(hidden == YES || userInteractionEnabled == YES || alpha < 0.01 || subViews.bounds > subViews.superView.bounds)
S4: 首先先明確何為響應者? ===> 在ios開發中繼承自UIResponder的類或子類就是響應者,顧明思意,響應者是用來相應事件的(觸摸事件、運動事件、遠程遙控事件)。所以所謂的響應者鏈條就是一系列響應者構成的層次結構。
S5: 響應者鏈條是通過nextResponder方法的返回值來組成這種層次結構的 ,蘋果有一段官方解釋如下:
The UIResponder class does not store or set the next responder automatically, instead returning nil by default. Subclasses must override this method to set the next responder. UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t); UIViewController implements the method by returning its view’s superview; UIWindow returns the application object, and UIApplication returns nil.
也就是說,響應者對象是不會自動設置和存儲下一個響應者,默認情況下是直接返回nil。而繼承自UIResponder的子類必須重寫這個方法來設置下一個響應者,并且需要遵循如下規范
1. 如果子類是UIView,那么其getter方法的nextResponder必須返回其UIViewController對象。 如果不存在控制器,則返回其父視圖對象。 2. 如果子類是UIViewController對象, 那么重寫的nextResponder方法必須返回其view視圖的父視圖對象。 3. 如果子類是UIWindow對象,那么重寫的nextResponder方法返回的是application對象 4. 如果子類是UIApplication對象,那么重寫的nextResponder方法,返回nil。
通過上述規范,結合下圖,你應該能很容易理解所謂的響應者鏈條如何構成:
#import "SWPButton.h"@implementation SWPButton- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesBegan:touches withEvent:event];}@end
所以此時如果你點擊按鈕, 其會先調用這個自定義按鈕的touchesBegan, 因為點擊事件傳遞到了button身上,所以會調用touchesBegan來響應該事件,但是該事件不在交給父類處理,所以不會調用action。只會繼續將其拋給上一個響應者。
@implementation UIView (ParentController)-(UIViewController*)parentController{ UIResponder *responder = [self nextResponder]; while (responder) { if ([responder isKindOfClass:[UIViewController class]]) { return (UIViewController*)responder; } responder = [responder nextResponder]; } return nil;}@end
思路很簡單,就是利用響應者鏈條來尋找UIViewController.那么這個UIView的上一個響應者只有兩種情況,一種是依然是一個UIView對象或其子對象,也就是說它是這個UIView的子View。一種是這個 UIView的控制器。所以我們才需要循環判斷,然后不斷找(循環),直到第一次找到的控制器,就是這個UIView所在的控制器。如果找不到,就返回nil。
是不是更加理解你事件派發的過程,所謂的事件派發過程,其實就是尋找最合適的視圖的時候,事件隨著這個尋找過程,不斷傳遞。為什么要傳遞UIEvent呢?因為通過它給以獲得Touch對象,而通過Touch對象我們可以獲得初始觸摸點。也就是說hitTest:withEvent主要實現的功能是,傳遞事件,找到最合適的視圖。
新聞熱點
疑難解答