关于响应者链

关于响应者链

在IOS应用中,一般有三种接收用户操作的方式:

1、触屏事件(Touch Event)
2、运动事件(Motion Event)如:摇一摇
3、远端控制事件(Remote-Control Event)如:点击耳机上面的按钮

今天主要介绍关于第一种“触摸事件”中的事件传递模式。

从UIButton说起,UIButton继承与UIControl可以接受的事件有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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中捕捉用户事件,一般会或重写这几个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(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中也可以用呢,他们不是继承关系。

注意这4个实例方法来自UIView与UIViewController的共同父类:UIResponder。它是我们今天的主角。

所有的IOS中关于的界面的Class都直接或间接地继承与UIResponder,点开UIResponder.h发现以上四个方法就在其中声明。对于用户事件的分发全部都取决于这个Class。IOS中的事件响应链与UIResponder有紧密关系

在IOS视图结构中,是呈现出来一个N叉数,一个视图可以有N个子视图,每个视图只有一个父视图,如下图:

 

 

 

当用户点击某一个视图或者按钮的时候会首先响应application中UIWindow一层一层的向下查找,直到找到用户指定的view为止,主要通过以下方法:

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再没有响应,则此点击事件被系统丢弃,大致流程如下:

 

事件响应链的大致流程就是如此了,大概就是一个先向下找,再向上找的过程!

posted @ 2017-02-14 10:23  o.0...w  阅读(125)  评论(0编辑  收藏  举报