關於響應者鏈

在IOS應用中,通常有三種接收用戶操做的方式:app

一、觸屏事件(Touch Event)
二、運動事件(Motion Event)如:搖一搖
三、遠端控制事件(Remote-Control Event)如:點擊耳機上面的按鈕ide

今天主要介紹關於第一種「觸摸事件」中的事件傳遞模式。spa

從UIButton提及,UIButton繼承與UIControl能夠接受的事件有:code

typedef NS_OPTIONS(NSUInteger, UIControlEvents) {
    UIControlEventTouchDown           = 1 <<  0,      // on all touch downs
    UIControlEventTouchDownRepeat     = 1 <<  1,      // on multiple touchdowns (tap count > 1)
    UIControlEventTouchDragInside     = 1 <<  2,
    UIControlEventTouchDragOutside    = 1 <<  3,
    UIControlEventTouchDragEnter      = 1 <<  4,
    UIControlEventTouchDragExit       = 1 <<  5,
    UIControlEventTouchUpInside       = 1 <<  6,
    UIControlEventTouchUpOutside      = 1 <<  7,
    UIControlEventTouchCancel         = 1 <<  8,

    UIControlEventValueChanged        = 1 << 12,     // sliders, etc.

    UIControlEventEditingDidBegin     = 1 << 16,     // UITextField
    UIControlEventEditingChanged      = 1 << 17,
    UIControlEventEditingDidEnd       = 1 << 18,
    UIControlEventEditingDidEndOnExit = 1 << 19,     // 'return key' ending editing

    UIControlEventAllTouchEvents      = 0x00000FFF,  // for touch events
    UIControlEventAllEditingEvents    = 0x000F0000,  // for UITextField
    UIControlEventApplicationReserved = 0x0F000000,  // range available for application use
    UIControlEventSystemReserved      = 0xF0000000,  // range reserved for internal framework use
    UIControlEventAllEvents           = 0xFFFFFFFF
};

經過繼承UIControl均可以接受這些用戶事件,固然UIButton也能夠。若是要在UIControl的父類UIView中捕捉用戶事件,通常會或重寫這幾個方法:對象

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  
{  
      
}  
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event  
{  
      
}  
  
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event  
{  
      
}  
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event  
{  
      
} 

咱們用的很是多,可是你們知道這4個方法是誰的實例方法嗎?若是你一下就說出是UIView的,那麼爲何咱們在UIViewController中也能夠用呢,他們不是繼承關係。blog

注意這4個實例方法來自UIView與UIViewController的共同父類:UIResponder。它是咱們今天的主角。繼承

全部的IOS中關於的界面的Class都直接或間接地繼承與UIResponder,點開UIResponder.h發現以上四個方法就在其中聲明。對於用戶事件的分發所有都取決於這個Class。IOS中的事件響應鏈與UIResponder有緊密關係遞歸

在IOS視圖結構中,是呈現出來一個N叉數,一個視圖能夠有N個子視圖,每一個視圖只有一個父視圖,以下圖:事件

 

 

 

當用戶點擊某一個視圖或者按鈕的時候會首先響應application中UIWindow一層一層的向下查找,直到找到用戶指定的view爲止,主要經過如下方法:ip

1 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
2 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;   // default returns YES if point is in bounds

 

 

在上圖中用戶點擊視圖中的ViewD時,UIWindow首先接收到響應,此響應包括用戶點擊的區域和一個封裝好的UIEvent對象,而後UIWindow經過這些信息利用如下方法查找:

1. UIWindow會經過調用pointInside:withEvent:方法返回的YES得知用戶點擊的範圍在ViewA中;

2. ViewA調用hitTest:withEvent:方法,在方法中遍歷全部的subView(ViewB、ViewC)調用hitTest:withEvent:方法;

3. 在遍歷中發現使用ViewC調用pointInside:withEvent:方法時返回YES,得知用戶點擊在ViewC範圍以內;

4. ViewC調用hitTest:withEvent:方法,在方法中遍歷全部的subView(ViewD、ViewE)調用hitTest:withEvent:方法;

5. 在遍歷中發現使用ViewD調用pointInside:withEvent:方法時返回YES,得知用戶點擊在ViewD範圍以內;

6. 在ViewD調用hitTest:withEvent:方法以前發現View的subViews的count爲0,故肯定用戶點擊在ViewD之上。

UIWindow會用遍歷subviews,使用每個subview調用hitTest:withEvent:方法,若是用戶點擊在某一個subview上,pointInside:withEvent:方法返回YES,再用這個subview調用hitTest:withEvent:方法,依次類推,直到當前view沒有子view或點擊的位置沒有在其任何子view之上,便肯定用戶點擊在某view上

大體在某一個view中是這樣實現的:

 1 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
 2     for (UIView *view in self.subviews) {
 3         if([view pointInside:point withEvent:event]){
 4             UIView *hitTestView = [view hitTest:point withEvent:event];
 5             if(nil == hitTestView){
 6                 return view;
 7             }
 8         }
 9     }
10     return nil;
11 }

經過以上這種遞歸的形式就能找到用戶點擊的是哪一個view,其中還要注意的時當前的view是否開啓了userIntercationEnabled屬性,若是這個屬性開啓,以上遞歸也會在開啓屬性的view層終止。固然以上只是簡單的實現,和API真正的實現還有所不用,好比沒有用到event參數。

 

既然找到了用戶點擊的view,那麼當前就應該響應用戶的點擊事件了,也就是利用上面提到的UIResponder了,這個響應點擊事件的過程是上面的逆序操做,這就是用到了UIResponder的nextResponder方法了。

由於UIView和UIViewController都是繼承於UIResponder,因此在調用nextResponder時有幾條規則以下:

1. 當一個view調用其nextResponder會返回其superView;

2. 若是當前的view爲UIViewController的view被添加到其餘view上,那麼調用nextResponder會返回當前的UIViewController,而這個UIViewController的nextResponder爲view的superView;

3. 若是當前的UIViewController的view沒有添加到任何其餘view上,當前的UIViewController的nextResponder爲nil,無論它是keyWinodw或UINavigationController的rootViewController,都是如此;

4. 若是當前application的keyWindow的rootViewController爲UINavigationController(或UITabViewController),那麼經過調用UINavigationController(或UITabViewController)的nextResponder獲得keyWinodw;

5. keyWinodw的nextResponder爲UIApplication,UIApplication的nextResponder爲AppDelegate,AppDelegate的nextResponder爲nil。

經過知道了上述規則,即可以經過用戶點擊的ViewD,查看ViewD是否響應了點擊事件,若是沒有找它的nextResponder,若是沒有再繼續找,直到找到AppDelegate再沒有響應,則此點擊事件被系統丟棄,大體流程以下:

 

事件響應鏈的大體流程就是如此了,大概就是一個先向下找,再向上找的過程!

相關文章
相關標籤/搜索