美文网首页iOS-响应链相关
iOS开发---图解事件的产生和响应

iOS开发---图解事件的产生和响应

作者: 祀梦_ | 来源:发表于2019-08-18 15:24 被阅读0次

iOS开发---图解事件的产生和响应

本博客会从事件的产生和响应这两个方面来完整的解释事件。

我们先看一下本文的主要内容,正文我会一一讲解:

产生:事件如何从父控件传递到子控件并寻找到最合适的view

响应:找到最合适的view后事件的处理(touches方法的重写,也就是事件的响应)

事件的产生

什么是事件(UIEvent)?

  • UIEvent:称为事件对象,记录事件产生的时刻和类型

    NS_CLASS_AVAILABLE_IOS(2_0) @interface UIEvent : NSObject
    //事件类型
    @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;
    
    #if UIKIT_DEFINE_AS_PROPERTIES
    @property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
    #else
    @end
    
  • iOS中的事件可以分为4大类型:

    1. 触摸事件
    2. 加速计事件(运动事件,比如重力感应和摇一摇等)
    3. 远程遥控事件
    4. 按压事件(iOS9之后出现的)
    //这是UIEventType
    typedef NS_ENUM(NSInteger, UIEventType) {
        UIEventTypeTouches,
        UIEventTypeMotion,
        UIEventTypeRemoteControl,
        UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
    };
    
  • 下面是官方给出的一张图

image
  • 下面主要讲解Touch Events(触摸事件)UIApplication通过一个触摸检测来决定最合适来处理该事件的响应者,一般情况下,这个响应者是UIView对象。

事件的产生

  1. 当用户的手指触摸屏幕的某一个view的时候,此时就发生了触摸事件,产生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,为什么是队列而不是栈?因为队列的特点是FIFO,即先进先出,先产生的事件先处理才符合常理,所以把事件添加到队列。
  2. UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
  3. 然后由主窗口决定如何将事件交给最合适的响应者(UIResponder)来处理

事件的传递

  • 在上面提到主窗口决定如何将事件交给最合适的响应者(UIResponder)来处理这个过程就称之为事件的传递,说直白一点事件的传递就是从UIApplication->window->寻找处理事件最合适的view的过程

如何寻找最合适的UIView

UIView接收触摸事件的条件
  1. userInteractionEnabled属性为YES,该属性表示允许控件同用户交互。
  2. Hidden属性为NO。控件都看不见,自然不存在触摸
  3. opacity属性值0 ~0.01。
  4. 触摸点在这个UIView的范围内。
事件传递的流程
  1. 触摸事件发生后,UIApplication会先发送事件给应用程序的主窗口(keyWindow),判断主窗口(keyWindow)自己是否能接受触摸事件
  2. 触摸点是否在自己身上
  3. 在子控件数组中从后往前遍历子控件,重复前面的两个步骤(首先查找数组中最后一个元素)
  4. 如果没有符合条件的子控件,那么就认为自己最合适处理
  • 下面我们通过一个🌰来了解这个过程是如何发生的:
image
  • 假设我点击了绿色的view

    1. 主窗口(UIWindow)接收到应用程序传递过来的事件后,首先判断自己能否接收触摸事件。如果能,那么再判断触摸点在不在窗口自己身上。
    2. 如果触摸点也在窗口身上,那么主窗口会从后往前遍历自己的子控件(白色view),判断自己能否接收触摸事件。如果能,那么再判断触摸点在不在白色view自己身上。
    3. 如果触摸点也在白色view身上,那么白色view会从后往前遍历自己的子控件(绿色view),判断自己能否接收触摸事件。如果能,那么再判断触摸点在不在绿色view自己身上。
    4. 如果触摸点也在绿色view身上,那么绿色view会从后往前遍历自己的子控件,发现自己没有子控件,那么我们就找到了最合适的view就是绿色view

    注意⚠️:之所以会采取从后往前遍历子控件的方式寻找最合适的view只是为了做一些循环优化。因为相比较之下,后添加的view在上面,降低循环次数。

事件传递的底层实现

前面我们为大家清楚的介绍了事件传递的流程,但是大家肯定会有两个疑惑:

一、你怎么知道自己能否接收触摸事件

二、你怎么知道触摸点在不在自己身上

  • 上个两个疑惑都会在hitTest:withEvent:方法中找到答案,下面我们认真看一下这个方法的底层实现
hitTest:withEvent:方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.判断自己能否接收触摸事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2.判断触摸点在不在自己范围内
    if (![self pointInside:point withEvent:event]) return nil;
    // 3.从后往前遍历自己的子控件,看是否有子控件更适合响应此事件
    int count = self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        CGPoint childPoint = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childPoint withEvent:event];
        if (fitView) {
            return fitView;
        }
    }
    // 没有找到比自己更合适的view
    return self;
}
  • 什么时候调用?
    • 只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法
  • 这个方法作用?
    • 寻找并返回最合适的view(能够响应事件的那个最合适的view)
  • 注意⚠️:不管这个控件能不能处理事件,也不管触摸点在不在这个控件上,事件都会先传递给这个控件,随后再调用hitTest:withEvent:方法来进行判断
pointInside:withEvent:方法
  • pointInside:withEvent:方法判断点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。

事件的响应

找到最合适的视图控件后,就会调用控件的touches方法来做具体的事件处理,这就是事件的响应。

UITouch对象

  • 什么是UITouch?

    • 在 iOS 中,每一个“触摸”(touch)行为对象就代表单根手指在屏幕上的一次运动操作,在 iOS 中以UITouch 类对象进行抽象表示。

    注意⚠️:

    • 当用户用一根手指触摸屏幕时,会创建一个与手指相关的UITouch对象
    • 一根手指对应一个UITouch对象
    • 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
    • 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
  • UITouch的作用

    • 保存着跟手指相关的信息,比如触摸的位置、时间、阶段
    • 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
    • 当手指离开屏幕时,系统会销毁相应的UITouch对象
  • UITouch的属性

    //触摸产生时所处的窗口
    @property(nonatomic,readonly,retain) UIWindow *window;
    //触摸产生时所处的视图
    @property(nonatomic,readonly,retain) UIView *view;
    //短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
    @property(nonatomic,readonly) NSUInteger tapCount;
    //记录了触摸事件产生或变化时的时间,单位是秒
    @property(nonatomic,readonly) NSTimeInterval timestamp;
    //当前触摸事件所处的状态
    @property(nonatomic,readonly) UITouchPhase phase;
    
  • UITouch的方法

    - (CGPoint)locationInView:(UIView *)view;
    // 返回值表示触摸在view上的位置
    // 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
    // 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
    
    - (CGPoint)previousLocationInView:(UIView *)view;
    // 该方法记录了前一个触摸点的位置
    

响应者对象

  • 在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接受并处理事件,我们称之为“响应者对象”。以下都是继承自UIResponder的,所以都能接收并处理事件。

    • UIApplication
    • UIViewController
    • UIView
  • 那么为什么继承自UIResponder的类就能够接收并处理事件呢?

    • 因为UIResponder中提供了以下4个对象方法来处理触摸事件
    // 一根或者多根手指开始触摸view,系统会自动调用view的下面方法
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    // 一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    // 一根或者多根手指离开view,系统会自动调用view的下面方法
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    // 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
    
  • 上面有两个参数(NSSet *)touches(UIEvent *)event

    • (NSSet *)touches:就是UITouch对象的集合

      • 举例:

        • 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
        • 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
    • (UIEvent *)event:就是最开始我们介绍的UIEevent,它记录事件产生的时刻和类型

事件的响应流程

  1. 当我们找到最合适的View后,如果当前的View有添加手势,那么直接响应相应的事件,如果没有手势事件,那么会看其是否实现了如下的方法:
- (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;://触摸终端
  1. 如果有实现那么就由此View响应,如果没有实现(即不能处理当前事件),那么事件将会沿着响应者链(Responder Chain)进行传递,直到遇到能处理该事件的响应者(Responsder Object)

响应者链

  • 由很多响应者链接在一起组合起来的一个链条称之为响应者链条

    • 补充:响应者链条其实还包括视图控制器、UIWindow和UIApplication

  • 下面我们用一张图来展示响应者链

image
  • 首先initial view会把事件传递给橘黄色的view橘黄色view又把事件给时间传递给了蓝绿色view蓝绿色view把时间传递给了控制器view控制器view把事件传递给了窗口,窗口把事件传递给了Application对象,如果最后Application还是不能处理此事件则将其丢弃.
    • 在事件的响应中,如果某个控件实现了touches方法,则这个事件将由该控件来接受,如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches方法
  • 如何判断上一个响应者
    1. 如果当前这个view是控制器的view,那么控制器就是上一个响应者
    2. 如果当前这个view不是控制器的view,那么父控件就是上一个响应者

事件的整个流程总结

  1. 当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的传递,也就是寻找最合适的view的过程
  2. 接下来是事件的响应。首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView);如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传递;(对于第二个图视图控制器本身还在另一个视图控制器中,则继续交给父视图控制器的根视图,如果根视图不能处理则交给父视图控制器处理);一直到window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃
  3. 事件的响应中,如果某个控件实现了touches方法,则这个事件将由该控件来接受,如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者;接着就会调用上一个响应者的touches方法
  • 注意⚠️:事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件)。

hitTest:和pointInside:的应用

屏蔽

  • 如果将某个view的pointInsdie:方法直接返回NO(无论子控件的pointInsdie:返回什么),影响的是子控件区域和自身区域的点击事件处理,这些区域不再响应事件。其余区域响应点击事件不发生变化。
  • 如果将某个viewpointInside:方法直接返回YES,自身区域响应点击事件不变。其它区域改变:
    1. 父控件所有区域点击事件交给该view处理。
    2. 再看该view处于父控件的子控件数组中的位置。数组前面的兄弟控件的点击事件交给该view处理,数组后面的兄弟控件的点击事件由其兄弟控件处理。
    3. view的子控件原来能够自己处理点击的区域继续由子控件处理,子控件原来不能够自己处理点击的(超出了该view范围)区域仍然可以由子控件处理了
  • 总结:想要屏蔽掉某个view响应点击事件,如果其没有子控件或者子控件响应事件也想屏蔽掉,直接将该view的pointInside返回为NO就行了。而在一般情况下,不建议将view的pointInside直接返回YES(影响范围太广,不好控制)。

穿透

  • 我们先假设有一个黄色控件和白色控件,白色空间覆盖在黄色控件上,如果你不想让你点击的那个白色view处理该事件,而是想要黄色view来响应该事件,就需要我么所说的穿透

  • 我们有两种方式来实现穿透:

    1. 如果whiteViewyellowView的兄弟控件。

      1. 可以重写whiteView里面的hitTest:方法:判断触摸在whiteView上的点,如果在yellowView上,hitTest:返回yellowView,交给其响应。
      - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
          CGPoint yellowPoint = [self convertPoint:point toView:_yellowView];
              if ([_yellowView pointInside:yellowPoint withEvent:event]) {
                     return _yellowView;
          }
          return [super hitTest:point withEvent:event];
      }
      
      1. 也可以重写whiteViewpointInside:方法:如果触摸点属于yellowView范围,返回NO,该范围内whiteView不响应点击。
      - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
          CGPoint yellowPoint =[_yellowView convertPoint:point fromView:self];
          if ([_yellowView pointInside:yellowPoint withEvent:event]){
             return NO;
          } else {
                 return [super pointInside:point withEvent:event];
          }
      }
      
      
    2. 如果whiteViewyellowView的子控件

      1. 需要重写whiteView里面的hitTest方法:
      - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
          CGPoint yellowPoint = [self convertPoint:point toView:_yellowView];
          if ([_yellowView pointInside:yellowPoint withEvent:event]) {
                 return _yellowView;
          }
          return [super hitTest:point withEvent:event];
      }
      
      

相关文章

  • iOS开发---图解事件的产生和响应

    iOS开发---图解事件的产生和响应 本博客会从事件的产生和响应这两个方面来完整的解释事件。我们先看一下本文的主要...

  • iOS 响应链

    iOS开发 - 事件传递响应链iOS 响应者链,事件的传递事件传递之响应链Cocoa Touch事件处理流程--响...

  • 二、事件传递链和响应者链

    iOS触摸事件详解iOS开发-事件传递响应链 响应者链 UIResponser包括了各种Touch message...

  • iOS 事件的传递和响应机制

    简单分享一下iOS开发中事件在view之间如何传递,如何响应事件,如有错误,请大家指正。 一、事件的产生 应用外层...

  • iOS 事件响应链机制

    iOS中的事件的产生和传递 首先要知道 事件传递和响应过程 相反的。 事件的传递 当你点击了屏幕会产生一个触摸事件...

  • iOS响应者链

    参考好文 iOS开发-事件传递响应链,用运行时分析 iOS事件传递:响应者链[译] http://www.jian...

  • UI部分-事件处理

    iOS事件处理- 用户使用App产生的事件及响应方法: iOS中不是任何对象都能处理事件,只有继承UIRespon...

  • IOS开发 摇晃事件

    iOS开发之摇晃事件 iso开发实现摇晃事件 摇晃事件相对简单,视图出现时成为第一响应者,视图移除时取消第一响应者...

  • iOS事件处理过程

    在IOS开发中会遇到各种操作事件,通过程序可以对这些事件做出响应。 首先,当发生事件响应时,必须知道由谁来响应事件...

  • 初识iOS事情处理机制

    参考:史上最详细的iOS之事件的传递和响应机制-原理篇iOS触摸事件全家桶史上最详细的iOS之事件的传递和响应机制...

网友评论

    本文标题:iOS开发---图解事件的产生和响应

    本文链接:https://www.haomeiwen.com/subject/lcirsctx.html