iOS事件觸摸事件處理機制

       移動平臺上的開發主要關注數據以及數據的處理,事件的處理以及UI。因此事件的分發處理是很重要的一個環節,對於一個平臺的優劣來講也是一項重要的參數。若是事件的分發設計的很差,一些複雜的UI場景就會變得很難寫甚至無法寫。從小屏沒有觸摸的功能機開始到如今大屏多點觸摸的智能機,對於事件的分發處理基本思路都是同樣的——鏈(設計模式中有個模式就是職責鏈chain of responsibility),只是斷定的複雜程度不一樣。設計模式

        iOS中的事件有3類,觸摸事件(單點,多點,手勢)、傳感器事件(加速度傳感器)和遠程控制事件,這裏我介紹的是第一種事件的分發處理。數組

        

        上面的這張圖來自蘋果的官方。描述了Responder的鏈,同時也是事件處理的順序。經過這兩張圖,咱們能夠發現:app

        1. 事件順着responder chain傳遞,若是一環不處理,則傳遞到下一環,若是都沒有處理,最後回到UIApplication,再不處理就會拋棄ide

        2. view的下一級是包含它的viewController,若是沒有viewController則是它的superViewspa

        3. viewController的下一級是它的view的superView.net

        4. view以後是window,最後傳給application,這點iOS會比OS X簡單(application就一個,window也一個)設計

         總結出來傳遞規則是這樣的:code

        

        這樣事件就會從first responder逐級傳遞過來,直到被處理或者被拋棄。orm


        因爲UI的複雜,這個responder chain是須要根據事件來計算的。好比,我如今在一個view內加入了2個Button,先點擊了一個,則first responder確定是這個點擊過的button,但我下面能夠去點擊另外一個button,因此顯然,當觸摸事件來時,這個chain是須要從新計算更新的,這個計算的順序是事件分發的順序,基本上是分發的反過來。對象

        

        不管是哪一種事件,都是系統自己先得到,是iOS系統來傳給UIApplication的,由Application再決定交給誰去處理,因此若是咱們要攔截事件,能夠在UIApplication層面或者UIWindow層面去攔截。

        

        

        UIView是如何斷定這個事件是不是本身應該處理的呢?iOS系統檢測到一個觸摸操做時會打包一個UIEvent對象,並放入Application的隊列,Application從隊列中取出事件後交給UIWindow來處理,UIWindow會使用hitTest:withEvent:方法來遞歸的尋找操做初始點所在的view,這個過程成爲hit-test view。

        hitTest:withEvent:方法的處理流程以下:調用當前view的pointInside:withEvent:方法來斷定觸摸點是否在當前view內部,若是返回NO,則hitTest:withEvent:返回nil;若是返回YES,則向當前view內的subViews發送hitTest:withEvent:消息,全部subView的遍歷順序是從數組的末尾向前遍歷,直到有subView返回非空對象或遍歷完成。若是有subView返回非空對象,hitTest方法會返回這個對象,若是每一個subView返回都是nil,則返回本身。

處理原理以下:

• 當用戶點擊屏幕時,會產生一個觸摸事件,系統會將該事件加入到一個由UIApplication管理的事件隊列中

• UIApplication會從事件隊列中取出最前面的事件進行分發以便處理,一般,先發送事件給應用程序的主窗口(UIWindow)

• 主窗口會調用hitTest:withEvent:方法在視圖(UIView)層次結構中找到一個最合適的UIView來處理觸摸事件

(hitTest:withEvent:實際上是UIView的一個方法,UIWindow繼承自UIView,所以主窗口UIWindow也是屬於視圖的一種)

• hitTest:withEvent:方法大體處理流程是這樣的:

首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內:

▶ 若pointInside:withEvent:方法返回NO,說明觸摸點不在當前視圖內,則當前視圖的hitTest:withEvent:返回nil

▶ 若pointInside:withEvent:方法返回YES,說明觸摸點當前視圖內,則遍歷當前視圖的全部子視圖(subviews),調用子視圖的hitTest:withEvent:方法重複前面的步驟,子視圖的遍歷順序是從top到bottom,即從subviews數組的末尾向前遍歷,直到有子視圖的hitTest:withEvent:方法返回非空對象或者所有子視圖遍歷完畢:

▷ 若第一次有子視圖的hitTest:withEvent:方法返回非空對象,則當前視圖的hitTest:withEvent:方法就返回此對象,處理結束

▷ 若全部子視圖的hitTest:withEvent:方法都返回nil,則當前視圖的hitTest:withEvent:方法返回當前視圖自身(self)

• 最終,這個觸摸事件交給主窗口的hitTest:withEvent:方法返回的視圖對象去處理。

拿到這個UIView後,就調用該UIView的touches系列方法。

1.二、消息處理過程,在找到的那個視圖裏處理,處理完後根據須要,利用響應鏈nextResponder可將消息往下一個響應者傳遞。

UIAppliactionDelegate <- UIWindow <- UIViewController <- UIView <- UIView

【關鍵】:要理解的有三點:一、iOS判斷哪一個界面能接受消息是從View層級結構的父View向子View傳遞,即樹狀結構的根節點向葉子節點遞歸傳遞。二、hitTest和pointInside成對,且hitTest會調用pointInside。三、iOS的消息處理是,當消息被人處理後默認再也不向父層傳遞。

        好了,咱們仍是看個例子:

        

        這裏ViewA包含ViewB和ViewC,ViewC中繼續包含ViewD和ViewE。假設咱們點擊了viewE區域,則hit-test View斷定過程以下:

       1. 觸摸在A內部,因此須要檢查B和C

       2. 觸摸不在B內部,在C內部,因此須要檢查D和E

       3. 觸摸不在D內部,但在E內部,因爲E已是葉子了,因此斷定到此結束


        咱們能夠運行一段代碼來驗證,首先從UIView繼承一個類myView,重寫裏面的

[objc] view plain copy 在CODE上查看代碼片派生到個人代碼片

  1. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event  

  2. {  

  3.     UIView *retView = nil;  

  4.     NSLog(@"hitTest %@ Entry! event=%@"self.name, event);  

  5.       

  6.     retView = [super hitTest:point withEvent:event];  

  7.     NSLog(@"hitTest %@ Exit! view = %@"self.name, retView);  

  8.      

  9.     return retView;  

  10. }  


[objc] view plain copy 在CODE上查看代碼片派生到個人代碼片

  1. - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event  

  2. {  

  3.     BOOL ret = [super pointInside:point withEvent:event];  

  4. //    if ([self.name isEqualToString:@"viewD"]) {  

  5. //        ret = YES;  

  6. //    }  

  7.     if (ret) {  

  8.         NSLog(@"pointInside %@ = YES"self.name);  

  9.     } else {  

  10.         NSLog(@"pointInside %@ = NO"self.name);  

  11.     }  

  12.       

  13.     return ret;  

  14. }  

        在viewDidLoad方法中手動加入5個view,都是myView的實例。

[objc] view plain copy 在CODE上查看代碼片派生到個人代碼片

  1. - (void)viewDidLoad  

  2. {  

  3.     [super viewDidLoad];  

  4.       

  5.     _viewA = [[myView alloc] initWithFrame:CGRectMake(1010300200) Color:[UIColor blackColor] andName:@"viewA"];  

  6.     [self.view addSubview:_viewA];  

  7.     [_viewA release];  

  8.       

  9.     _viewB = [[myView alloc] initWithFrame:CGRectMake(10240300200) Color:[UIColor blackColor] andName:@"viewB"];  

  10.     [self.view addSubview:_viewB];  

  11.     [_viewB release];  

  12.       

  13.     _viewC = [[myView alloc] initWithFrame:CGRectMake(1010120180) Color:[UIColor blueColor] andName:@"viewC"];  

  14.     [_viewB addSubview:_viewC];  

  15.     [_viewC release];  

  16.       

  17.     _viewD = [[myView alloc] initWithFrame:CGRectMake(17010120180) Color:[UIColor blueColor] andName:@"viewD"];  

  18.     [_viewB addSubview:_viewD];  

  19.     [_viewD release];  

  20.       

  21.     _viewE = [[myView alloc] initWithFrame:CGRectMake(304060100) Color:[UIColor redColor] andName:@"viewE"];  

  22.     [_viewD addSubview:_viewE];  

  23.     [_viewE release];  

  24.   

  25. }  

        這個樣式以下:


        當我點擊viewE的時候,打印信息以下:

2014-01-25 18:32:46.538 eventDemo[1091:c07] hitTest viewB Entry! event=<UITouchesEvent: 0x8d0cae0> timestamp: 6671.26 touches: {(

)}

2014-01-25 18:32:46.538 eventDemo[1091:c07] pointInside viewB = YES

2014-01-25 18:32:46.539 eventDemo[1091:c07] hitTest viewD Entry! event=<UITouchesEvent: 0x8d0cae0> timestamp: 6671.26 touches: {(

)}

2014-01-25 18:32:46.539 eventDemo[1091:c07] pointInside viewD = YES

2014-01-25 18:32:46.539 eventDemo[1091:c07] hitTest viewE Entry! event=<UITouchesEvent: 0x8d0cae0> timestamp: 6671.26 touches: {(

)}

2014-01-25 18:32:46.540 eventDemo[1091:c07] pointInside viewE = YES

2014-01-25 18:32:46.540 eventDemo[1091:c07] hitTest viewE Exit! view = <myView: 0x8c409f0; frame = (30 40; 60 100); layer = <CALayer: 0x8c40a90>>

2014-01-25 18:32:46.540 eventDemo[1091:c07] hitTest viewD Exit! view = <myView: 0x8c409f0; frame = (30 40; 60 100); layer = <CALayer: 0x8c40a90>>

2014-01-25 18:32:46.541 eventDemo[1091:c07] hitTest viewB Exit! view = <myView: 0x8c409f0; frame = (30 40; 60 100); layer = <CALayer: 0x8c40a90>>

2014-01-25 18:32:46.541 eventDemo[1091:c07] touchesBegan viewE

2014-01-25 18:32:46.624 eventDemo[1091:c07] touchesEnded viewE

        從打印信息能夠看到,先判斷了viewB,而後是viewD,最後是viewE,但事件就是直接傳給了viewE。

相關文章
相關標籤/搜索