在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再沒有響應,則此點擊事件被系統丟棄,大體流程以下:
事件響應鏈的大體流程就是如此了,大概就是一個先向下找,再向上找的過程!