• 《iOS基础系列》iOS事件分发机制


    下文摘自该链接

    iOS事件分发机制与实践

    iOS中的事件

    • iOS中事件一共有四子类型,包括触摸事件,运动事件,远程控制事件,按压事件,接下里我仅讨论最常用的触摸事件。事件通过UIEvent对象描述。

    UIEvent

    • UIEvent描述了单次的用户与应用的交互行为,例如触摸屏幕时会产生触摸事件,晃动手机时会产生运动事件。UIEvent对象中记录了事件发生的时间,类型,对于触摸事件,还记录了一组UITouch对象,下面是UIEvent的几个属性:
    @property(nonatomic,readonly) UIEventType     type NS_AVAILABLE_IOS(3_0);  //事件的类型
    @property(nonatomic,readonly) UIEventSubtype  subtype NS_AVAILABLE_IOS(3_0);
    @property(nonatomic,readonly) NSTimeInterval  timestamp;  //事件的时间
    
    @property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;  //事件包含的touch对象
    

    UITouch

    • UITouch记录了手指在屏幕上触摸时产生的一组信息,包含触摸的时间,位置,所在的窗口或视图,触摸的状态,力度等信息
    @property(nonatomic,readonly) NSTimeInterval      timestamp;  //时间
    @property(nonatomic,readonly) UITouchPhase        phase;  //状态,例如begin,move,end,cancel
    @property(nonatomic,readonly) NSUInteger          tapCount;   // 短时间内单击的次数
    @property(nonatomic,readonly) UITouchType         type NS_AVAILABLE_IOS(9_0);  //类型
    @property(nonatomic,readonly) CGFloat majorRadius NS_AVAILABLE_IOS(8_0);  //触摸半径
    @property(nonatomic,readonly) CGFloat majorRadiusTolerance NS_AVAILABLE_IOS(8_0);
    @property(nullable,nonatomic,readonly,strong) UIWindow                        *window;  //触摸所在窗口
    @property(nullable,nonatomic,readonly,strong) UIView                          *view;  //触摸所在视图
    @property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);  //正在接收该触摸对象的手势识别器
    @property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0);  //触摸的力度
    

    每一根手指的触摸都会产生一个UITouch对象,多个手指触摸便会有多个UITouch对象,当手指在屏幕上移动时,系统会更新UITouch的部分属性值,在触摸结束后系统会释放UITouch对象。

    当事件产生后,系统会寻找可以响应该事件的对象来处理事件,如果找不到可以响应的对象,事件就会被丢弃。那么哪些对象可以响应事件呢?只有继承于UIResponder的对象才能够响应事件,UIApplication,UIView,UIViewcontroller均继承于UIResponder,因此它们能够响应事件。UIResponder提供了响应事件的一组方法:

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;  //手指触摸到屏幕
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; //手指在屏幕上移动或按压
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; //手指离开屏幕
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; //触摸被中断,例如触摸时电话呼入
    

    如果我们想要对事件进行自定义的处理(比如手指在屏幕滑动时让某个view跟着移动),我们需要重写以上四个方法,对于UIViewcontroller,我们只需要在UIViewcontroller中重写上面四个方法,对于UIView,我们需要创建继承于UIView的子类,然后在子类中重写上面的方法,这点需要注意

    事件的传递

    • 事件产生之后,会被加入到由UIApplication管理的事件队列里,接下来开始自UIApplication往下传递,首先会传递给主window,然后按照view的层级结构一层层往下传递,一直找到最合适的view(发生touch的那个view)来处理事件。查找最合适的view的过程是一个递归的过程,其中涉及到两个重要的方法 hitTest:withEvent:和pointInside:withEvent:

    当事件传递给某个view之后,会调用view的hitTest:withEvent:方法,该方法会递归查找view的所有子view,其中是否有最合适的view来处理事件,整个流程如下所示:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        //首先判断是否可以接收事件
        if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
        //然后判断点是否在当前视图上
        if ([self pointInside:point withEvent:event] == NO) return nil;
        //循环遍历所有子视图,查找是否有最合适的视图
        for (NSInteger i = self.subviews.count - 1; i >= 0; i--) {
            UIView *childView = self.subviews[i];
            //转换点到子视图坐标系上
            CGPoint childPoint = [self convertPoint:point toView:childView];
            //递归查找是否存在最合适的view
            UIView *fitView = [childView hitTest:childPoint withEvent:event];
            //如果返回非空,说明子视图中找到了最合适的view,那么返回它
            if (fitView) {
                return fitView;
            }
        }
        //循环结束,仍旧没有合适的子视图可以处理事件,那么就认为自己是最合适的view
        return self;
    }
    
    • pointInside:withEvent:方法作用是判断点是否在视图内,是则返回YES,否则返回NO
    • 判断一个view是否能够接收事件有三个条件,分别是,是否禁止用户交互(userInteractionEnabled = NO),是否被隐藏(hidden = YES)以及透明度是否小于等于0.01(alpha <=0.01)
    • 从递归的逻辑我们知道,如果触摸的点不在父view上,那么其上的所有子view的hitTest都不会被调用,需要指出的是,如果子view尺寸超出了父view,并且属性clipsToBounds设置为NO,触摸发生在子view超出父view的区域内,依旧不返回子view。反过来,如果触摸的点在父view上并且父view就是最合适的view,那么它的所有子view的hitTest还是会被调用,因为如果不调用无法知道是否还有比父view更合适的子view存在

    事件的响应

    • 在找到最合适的view之后,会调用view的touches方法对事件进行响应,如果没有重写view的touches方法,touches默认的做法是将事件沿着响应者链往上抛,交给下一个响应者对象。也就是说,touches方法默认不处理事件,只是将事件沿着响应者链往上传递。那么响应者链是什么呢?

    响应者链

    • 在应用程序中,视图放置都是有一定层次关系的,点击屏幕之后该由下方的哪个view来响应需要有一个判断的方式。响应者链是由一系列可以响应事件的对象(继承于UIResponder)组成的,它决定了响应者对象响应事件的先后顺序关系。下图展示了UIApplication,UIViewcontroller以及UIView之间的响应关系链:

    • 响应者链在递归查找最合适的view的时候形成,所找到的view将成为第一响应者,会调用它的touches方法来响应事件,touches方法默认的处理是将事件往上抛给下一个响应者,而如果下一个响应者的touches方法没有重写,事件会继续沿着响应者链往上走,一直到UIApplication,如果依旧不能处理事件那么事件就被丢弃

    各控件的下一响应者
    1. UIView:如果view是viewcontroller的根view,那么下一个响应者是viewcontroller,否则是super view
    2. UIViewController:如果viewcontroller的view是window的根view,那么下一个响应者是window;如果viewcontroller是另一个viewcontroller模态推出的,那么下一个响应者是另一个viewcontroller;如果viewcontroller的view被add到另一个viewcontroller的根view上,那么下一个响应者是另一个viewcontroller的根view
    3. UIWindow:UIWindow的下一个响应者是UIApplication
    4. UIApplication:通常UIApplication是响应者链的顶端(如果app delegate也继承了UIResponder,事件还会继续传给app delegate)
    时间并不会因为你的迷茫和迟疑而停留,就在你看这篇文章的同时,不知道有多少人在冥思苦想,在为算法废寝忘食,不知道有多少人在狂热地拍着代码,不知道又有多少提交一遍又一遍地刷新着OJ的status页面…… 没有谁生来就是神牛,而千里之行,始于足下!
  • 相关阅读:
    2018年工作总结
    通过js date对象获取各种开始结束日期的示例
    位运算
    Nhibernate官方体系结构图部分中文翻译
    javascript中判断变量是否存在的正确方式
    .net core 项目加载提示项目文件不完整缺少预期导入的解决办法
    dotnet core 运行程序注意事项
    关于.net 项目 nuget包还原项目失败的记录
    c#利用反射实现对类中的常量进行取值和对应常量的注释
    在ie9下在textbox框里面输入内容按enter键会触发按钮的事件
  • 原文地址:https://www.cnblogs.com/bianjunting/p/15120777.html
Copyright © 2020-2023  润新知