• 关于响应者链


    关于响应者链

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

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

  • 相关阅读:
    DataGrid 样式
    MVC调用部分视图PartialView
    JavaScript/jQuery判断变量是否是undefined
    form表单只单个input框按回车键页面会自动刷新
    无法使用前导 .. 在顶级目录上退出
    Response.Redirect:正在中止线程
    图片压缩是出现白边如何去除
    C#.Net调试时调无法“编辑并继续”
    TimeSpan时间间隔
    拒绝了对对象 'sp_OACreate' (数据库 'mssqlsystemresource',架构 'sys')的 EXECUTE权限
  • 原文地址:https://www.cnblogs.com/yuwei0911/p/6396468.html
Copyright © 2020-2023  润新知