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

iOS---事件传递和响应机制

作者: jeff_guan | 来源:发表于2017-08-08 21:47 被阅读0次

    iOS 中的事件

    • 触摸事件
    • 加速计事件
    • 远程控制事件

    响应者对象(UIResponder)

    只有继承 UIResponder 的对象才能响应事件

    • UIApplication
    • UIViewControl
    • UIView
    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;
    



    touchesBegan 这个方法,如果是两个手指同时触摸,这个方法只会调用一次,方法中包含两个 UITouch 的对象 , 如果是一前一后的触摸,就会调用两次这个方法,方法中包含一个 UITouch 对象

    注意:
    1.想要处理 UIView 的触摸事件,就要继承 UIView ,然后重写 UIView 的触摸事件方法
    2.UIViewControl 在其触摸事件方法中就可以处理

    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 对象
    • 一根手指对应一个 UITouch 对象
    • 当手指移动时,系统会更新 UITouch 对象的位置等信息
    • 当手指离开时,相应的 UITouch 对象就会销毁

    IOS 的事件产生和传递

    事件产生

    1. 发生触摸事件后,系统会把事件加入到由 UIApplication 管理的事件队列
    2. UIApplication 会从事件队列中取出最前面的事件,并将事件分发下去处理,通常是先发给应用程序的KeyWindow
    3. KeyWindow 会找其视图层结构找到一个最适合的View 来处理事件

    事件的传递

    UIApplication -> Window -> 寻找最合适的View

    如何寻找最合适的 View?

    1. 首先判断 KeyWind 是否能接受触摸事件
    2. 判断触摸点是否在窗口身上
    3. 从后往前遍历子控件,重复上述两个步骤
    4. 遍历到最合适的 View
    5. 如果没有最合适的 View ,那么就由 KeyWind 处理



    ⚠️注意点:

    1. 透明度 < 0.01 的控件,是不能接受触摸事件
    2. 如果父控件不能接收事件,那么子控件也不能
    3. 不允许交互:userInteractionEnabled = NO
    4. 不管控件能否接收触摸事件,主要有触摸,就会产生事件,只是这个事件会不会被处理

    如何寻找最合适的 View 的底层揭秘

    hitTest:withEvent:
    pointInside

    hitTest:withEvent:方法

    • 调用:主要有事件传递给一个控件,控件就会调用该方法
    • 作用:找到最合适的 View ,并返回该 View

    拦截事件的处理

    • 正因为hitTest:withEvent:方法可以返回最合适的view,所以可以通过重写hitTest:withEvent:方法,返回指定的view作为最合适的view。
    • 不管点击哪里,最合适的view都是hitTest:withEvent:方法中返回的那个view。
    • 通过重写hitTest:withEvent:,就可以拦截事件的传递过程,想让谁处理事件谁就处理事件。

    如果想指定 View 作为最适合的 View,有以下两种方法

    1. 在父控件的hitTest:withEvent:返回子控件
    2. 在子控件的hitTest:withEvent:返回自

    ⚠️注意点:
    推荐方法一,不推荐方法二的原因是有可能不能成功返回子控件,如果触摸点不在子控件,而在另一个子控件,那么没办法返回该子控件。

    事件传递的正真顺序

    1. 产生触摸事件
    2. UIApplication 事件队列
    3. [UIWindow hitTest:withEvent:];
    4. 返回更合适的 View
    5. [子控件 hitTest:withEvent:];
    6. 返回更合适的 View
    #import "WSWindow.h"
    @implementation WSWindow
    // 什么时候调用:只要事件一传递给一个控件,那么这个控件就会调用自己的这个方法
    // 作用:寻找并返回最合适的view
    // UIApplication -> [UIWindow hitTest:withEvent:]寻找最合适的view告诉系统
    // point:当前手指触摸的点
    // point:是方法调用者坐标系上的点
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        // 1.判断下窗口能否接收事件
         if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha = 0; i--)     { 
        // 获取子控件
        UIView *childView = self.subviews[i]; 
        // 坐标系的转换,把窗口上的点转换为子控件上的点 
        // 把自己控件上的点转换成子控件上的点 
        CGPoint childP = [self convertPoint:point toView:childView]; 
        UIView *fitView = [childView hitTest:childP withEvent:event]; 
        if (fitView) {
        // 如果能找到最合适的view 
        return fitView; 
        }
        } 
        // 4.没有找到更合适的view,也就是没有比自己更合适的view 
        return self;
        }
        // 作用:判断下传入过来的点在不在方法调用者的坐标系上
        // point:是方法调用者坐标系上的点
        //- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
        //{
        // return NO;
        //}
        - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
        NSLog(@"%s",__func__);
        }
        @end
    

    pointInside:withEvent:方法

    pointInside:withEvent:方法判断点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件

    事件响应

    响应者链条:是由多个响应者连接起来的链条。

    事件响应的流程

    1.找到最合适的 View 的时候,就会调用自己 touchs 的方法处理事件。
    2.touches默认做法是把事件顺着响应者链条向上抛.
    2.1 首先看 initail View 能不能处理事件
    2.2 不能就把事件传递给父控件,继续判断
    2.3 没有一个控件能处理就抛给 Window
    2.4 Window 处理不了,最后抛给 UIApplication
    2.5 UIApplication 处理不了,把事件丢弃。
    如果以上一个响应者重写了 touches 方法,就能处理事件

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

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

    事件传递和事件响应的过程总结

    1. 事件产生,添加到 UIApplication 的事件队列
    2. UIApplication 把事件队列最前的事件传递给UIWindow
    3. UIWindow 调用 hitTest:withEvent: 返回一个合适的 View
    4. View 继续调用 hitTest:withEvent: 找到一个最适合的 View
    5. 事件传递给最适合的 View 就开始响应事件
    6. 如果最适合 View 不能处理事件,把事件抛给父控件
    7. 一直都没有控件可以处理事件,就把事件抛给 UIWindow
    8. UIWindow 处理不了就抛给 UIApplication
    9. 最后处理不了就把事件抛弃。

    相关文章

      网友评论

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

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