美文网首页
iOS事件的传递和响应机制

iOS事件的传递和响应机制

作者: 炒河粉儿 | 来源:发表于2019-08-03 12:19 被阅读0次

    iOS中的事件

    事件的生命周期

    事件的生命周期从事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view、寻找最合适的view的底层实现、拦截事件的处理)->事件的响应(找到最合适的view后对事件进行处理,touches方法的重写)

    事件的分类

    iOS中的事件可以分为3大类型:

    触摸事件

    加速计事件

    远程控制事件

    响应者对象

    在iOS中只有继承了UIResponder的对象才能接收和处理事件,UIResponder的子类包括UIView,UIViewController,UIApplication等。响应者接收到原始事件数据,必须处理事件转发到另一个响应者对象,当APP接收到一个事件事,UIKit自动引导事件到最合适的的响应者对象,也叫做第一响应者。
    UIResponder内部提供了以下方法来处理事件

    触摸事件
    - (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;
    加速计事件
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
    - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
    远程控制事件
    - (void)remoteControlReceivedWithEvent:(UIEvent *)event;
    

    UITouch对象

    UITouch对象的产生

    当用户触摸屏幕的时候,会创建一个与手指相关的UITouch对象,一根手指对应一个UITouch对象,如果两根手指同时触摸,则touchesBegan的方法中参数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;
    // 该方法记录了前一个触摸点的位置
    

    这两个方法则实现了对UITouch对象的一个更新。

    iOS事件的产生和传递

    事件的产生

    发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中,因为队列的特点是先进先出,先产生的事件先处理,这样符合常理。

    UIApplication会从事件队列中取出最前面的事件,并将事件分发下去,通常会发送给应用程序的主窗口,按照视图层级结构一级级向下寻找对合适的视图来处理事件。

    找到合适的视图后,会调用touches的方法来做具体的处理,是处理这个事件还是往上传递交给上一级处理。

    事件的传递

    触摸事件的传递是从上到下的传递,由父控件传递给子控件。UIApplication->window->寻找处理事件最合适的view

    如果父控件不能接受触摸事件,那么子控件也就不可能接收到触摸事件。

    1. 首先判断主窗口(key window)自己能否接收这个触摸事件
    2. 判断触摸点是否在自己身上
    3. 遍历当前视图的子控件数组,从后往前遍历子控件数组再重复前面两个步骤(先查找子控件数组中的最后一个元素,再执行1,2两个步骤)。
    4. 假设遍历过后找到了一个较为合适的view,则把这个事件交给这个view,再对这个view进行遍历他的子控件数组,像前三步一样继续寻找更合适的view,直到找不到更合适的view。
    5. 如果找不到符合条件的子控件,则认为自己是最适合处理这个事件的控件,也就是自己是最合适的view。

    UIView不能接收事件的三种情况

    1. 不允许交互:userInteractionEnabled = NO
    2. 隐藏:hidden = yes 父控件被隐藏后,子控件也跟着杯隐藏,隐藏的控件不能接收事件
    3. 透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明

    默认UIImageView不能接收触摸事件,如果希望UIImageView可以交互,需要设置UIImageView的userInteractionEnabled = YES。

    总结

    1. 产生一个触摸事件,由UIApplication添加到事件队列中。
    2. UIApplication取出最前面的事件把事件传递给主窗口(keyWindow)
    3. 根据视图层级一级级寻找最合适的处理事件的view。

    最核心的点就是如何寻找最合适的view

    寻找最合适的view原则就只有两个。一个是判断当前控件是否能够处理事件,第二是当前的触摸点是否在自己身上。符合这两个条件,则事件就会传递给当前控件。再由当前控件作为父控件进行遍历,寻找最合适的子控件。依次逐级寻找,当再也找不到更合适的子控件时,则当前的父控件为最合适的view。

    实现这两个条件的判断则需要用到最关键的两个方法

    hitTest:withEvent:方法

    pointInside方法
    hit:withEvent:方法底层会调用pointInside:withEvent:方法判断点在不在方法调用者的坐标系上。

    hitTest:withEvent:方法

    只要事件传递给一个控件,则必然会调用这个控件的hitTest:withEvent:方法,这个方法会返回寻找到的最合适的view。也可能是一个view,则view是更为合适的,继续将事件传递给该view,也啃呢个返回一个nil,则代表该控件和该控件下的子控件都不是最合适的view,则最合适的view是该控件的父控件。

    事件传递给窗口或控件的后,就调用hitTest:withEvent:方法寻找更合适的view。所以是,先传递事件,再根据事件在自己身上找更合适的view。
    不管子控件是不是最合适的view,系统默认都要先把事件传递给子控件,经过子控件调用子控件自己的hitTest:withEvent:方法验证后才知道有没有更合适的view。即便父控件是最合适的view了,子控件的hitTest:withEvent:方法还是会调用,不然怎么知道有没有更合适的!即,如果确定最终父控件是最合适的view,那么该父控件的子控件的hitTest:withEvent:方法也是会被调用的。

    技巧:想让谁成为最合适的view就重写谁自己的父控件的hitTest:withEvent:方法返回指定的子控件,或者重写自己的hitTest:withEvent:方法 return self。但是,建议在父控件的hitTest:withEvent:中返回子控件作为最合适的view!

    特殊情况:
    谁都不能处理事件,窗口也不能处理。

    重写window的hitTest:withEvent:方法return nil

    只能有窗口处理事件。

    控制器的view的hitTest:withEvent:方法return nil或者window的hitTest:withEvent:方法return self

    return nil的含义:
    hitTest:withEvent:中return nil的意思是调用当前hitTest:withEvent:方法的view不是合适的view,子控件也不是合适的view。如果同级的兄弟控件也没有合适的view,那么最合适的view就是父控件。

    事件的响应

    触摸事件处理的整个过程

    当通过事件传递,找到最合适的view之后,则进行事件的响应。事件的传递是由上到下的寻找最合适的view,事件的响应则是由下往上去找到最合适的响应者去进行处理。

    1. 如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
    2. 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
    3. 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
    4. 如果UIApplication也不能处理该事件或消息,则将其丢弃

    总结

    1. 触摸屏幕产生触摸事件后,触摸事件会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)
    2. UIApplication会从事件队列中取出最前面的事件,把事件传递给应用程序的主窗口(keyWindow)
    3. 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)
    4. 最合适的view会调用自己的touches方法处理事件
    5. touches默认做法是把事件顺着响应者链条向上抛。直到找到实现了touches方法的view,则把事件处理掉。否则一直向上一级响应者传递,直到事件处理或者最后也无法处理则丢弃。

    如何做到一个事件多个对象处理:
    因为系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到一个事件多个对象处理的目的。

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
    // 1.自己先处理事件...
    NSLog(@"do somthing...");
    // 2.再调用系统的默认做法,再把事件交给上一个响应者处理
    [super touchesBegan:touches withEvent:event]; 
    }
    

    事件的传递和响应的区别:
    事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。

    相关文章

      网友评论

          本文标题:iOS事件的传递和响应机制

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