iOS 事件传递以及响应

作者: Codepgq | 来源:发表于2016-08-27 15:14 被阅读281次

    一、事件传递


    • 1、事件传递

    2寻找最合适的View.png

    验证一下是不是这个流程!

    • 1、建立一个工程!搭建如下界面。


      图片

    在修改View背景

    blue
    • 2、建立一个自己的Window,并且在AppDelegate中替换为自己Window
    //使用自己的Window
        self.window = [[PQWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
        //加载界面
        UIStoryboard * board = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        UIViewController * viewController = [board instantiateInitialViewController];
        self.window.rootViewController = viewController;
        [self.window makeKeyAndVisible];
    
    • 3、重写touchesbegan...方法:在包括Window和其他的View、VC
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [super touchesBegan:touches withEvent:event];
        NSLog(@"%s",__func__);
    }
    
    • 4、分别依次点击
    • blue view
    2016-08-27 09:41:06.116 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 09:41:06.116 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
    
    • green view
    2016-08-27 09:42:22.891 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 09:42:22.891 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
    2016-08-27 09:42:22.891 PQHitTest[1647:58543] -[GreenView touchesBegan:withEvent:]
    
    • pink view
    2016-08-27 09:43:08.274 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 09:43:08.275 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
    2016-08-27 09:43:08.275 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
    
    • yellow view
    2016-08-27 09:43:47.010 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 09:43:47.010 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
    2016-08-27 09:43:47.011 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
    2016-08-27 09:43:47.011 PQHitTest[1647:58543] -[YellowView touchesBegan:withEvent:]
    
    • brown view
    2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
    2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
    2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[BrownView touchesBegan:withEvent:]
    
    • black view
    2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
    2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
    2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[BrownView touchesBegan:withEvent:]
    2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[BlackView touchesBegan:withEvent:]
    
    • orange view
    2016-08-27 09:45:02.546 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 09:45:02.546 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
    2016-08-27 09:45:02.546 PQHitTest[1647:58543] -[OrangeView touchesBegan:withEvent:]
    
    • 2、如何寻找最合适的View

    • 1、检查自己是能否接受触摸事件
    • 2、检测触摸点是否在自己身上
    • 3、从后往前遍历子控件,重复1 2
    • 4、如果没有符合的子控件,那么自己就是合适的View

    eg:点击green View

    window → blue view → orange view → pink view → green view

    • 1、首先会响应window的hitTest方法

    • 2、在响应blueView的hitTest方法,从后往前遍历,先找到blue view.subviews的最后一个View,也就是orange view

    • 3、判断点是不是在orange view上,不在的话返回nil,并且继续从blue view.subviews继续从后往前遍历

    • 4、判断点是不是在pink view上,不在的话返回nil,并且继续从blue view.subviews继续从后往前遍历

    • 5、当遍历的到green view时,发现点在green view上,然后继续遍历 green view.subviews,发现并没有子控件。

    • 6、这个时候就返回green view,从而使得 green view变成了最合适的View

    使用程序验证:把touchesBegan...方法全部注释掉,并且在blue / green / pink / orange view以及window中添加下面方法

    2016-08-27 10:36:23.747 PQHitTest[2603:89650] -[PQWindow hitTest:withEvent:]
    2016-08-27 10:36:23.747 PQHitTest[2603:89650] -[OrangeView hitTest:withEvent:]
    2016-08-27 10:36:23.747 PQHitTest[2603:89650] -[PinkView hitTest:withEvent:]
    2016-08-27 10:36:23.748 PQHitTest[2603:89650] -[GreenView hitTest:withEvent:]
    

    通过输出我们可以看到,系统查找过程确实和我们预想的一样。


    eg:点击black View:把所有的View都添加hitTest方法

    window → blue view → orange view → pink view → brown view → black view

    
    2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[PQWindow hitTest:withEvent:]
    2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[OrangeView hitTest:withEvent:]
    2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[PinkView hitTest:withEvent:]
    2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[BrownView hitTest:withEvent:]
    2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[BlackView hitTest:withEvent:
    



    二、事件响应



    1事件响应.png
    发生触摸事件后,系统会自动将该事件加入到一个有UIApplication管理的事件队列中,UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序的主窗口。
    在主窗口的视图中会找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。
    当找到后,则会调用touches中的:
    • touchesBegan...
    • touchesMoved...
    • touchesEnded...

    1、先把所有的hitTest方法给注释掉!

    然后开启VC中和WIndows中的touchesBegan方法,运行程序

    2016-08-27 10:49:32.998 PQHitTest[2972:100633] -[PQWindow touchesBegan:withEvent:]
    2016-08-27 10:49:32.999 PQHitTest[2972:100633] -[ViewController touchesBegan:withEvent:]
    

    如果没有把其他的View的touchesbegan注释掉,则会依次找到View的touchesBegan方法。



    三、练习


    1、不管点击那个View,只响应blue view的touchesBegan...

    解析:

    现在我们知道hitTest方法就是用来查找最合适的View的,并且需要一个返回值,如果返回nil:表示不是合适的View,返回不为空:表示是合适的View。
    通过这个一个特性我们可以把BaseView中的hitTest方法中直接返回nil

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        
        return nil;
    }
    



    2、不管点击那个View,只响应green view的touchesBegan...

    解析:

    hitTest方法是从后往前遍历子控件,所以我们把pink/orange view中的hitTest方法返回nil,把green view中的hitTest方法返回self来达到效果,这里要记得把BaseView中的hitTest方法注释或者super,把VC中的touchesBegan..注释掉,打开green view中的touchesBegan...

    // pink and orange
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        
        return nil;
    }
    //green 
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        
        return self;
    }
    



    打印结果:

    2016-08-27 11:09:59.757 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
    2016-08-27 11:10:00.052 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
    2016-08-27 11:10:00.268 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
    2016-08-27 11:10:00.444 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
    2016-08-27 11:10:00.660 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
    2016-08-27 11:10:00.876 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
    

    3、添加一个导航栏,然后在工程中新建一个VC,搭建如下界面

    搭建界面2


    然后 搭建界面1

    要求:点击红色按钮是响应红色按钮点击事件

    • 3.1在然后创建一个button class、一个View class
    • 3.2并且连接好
    • 3.3添加touchesBegan方法
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [super touchesBegan:touches withEvent:event];
        NSLog(@"%s",__func__);
    }
    
    • 3.4运行后发现只要是点击橙色View,哪怕点同时也在按钮上,并不会触发按钮事件。
    如何达到这一效果?

    1、先明白为什么不会触发按钮事件:

    因为在寻找最合适的View的时候是从后往前遍历
    Application → window → VC.view → 橙色View
    这个时候不会继续去寻找了,因为橙色已经满足了条件。

    所以我要在橙色的hitTest方法中动手脚。



    2、如何动手脚

    思想:当你在查找最合适的View的时候是根据point点去判断的,我只需要判断当你这个点,恰好也在我的Button上的时候,我就返回button,而不是返回橙色View
    需要用到的方法:

    • convertPoint:toView: 把点转化为View中的点
    • pointInside:withEvent: 用与判断点在不在VIew上
    #import "PQViewTwo.h"
    #import "PQButtonTwo.h"
    @interface PQViewTwo ()
    
    @property (nonatomic,weak) IBOutlet PQButtonTwo *button;
    
    @end
    
    @implementation PQViewTwo
    
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        CGPoint p = [self convertPoint:point toView:self.button];
            
        if ([self pointInside:p withEvent:event]) {
            return self.button;
        }
        return [super hitTest:point withEvent:event];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [super touchesBegan:touches withEvent:event];
        NSLog(@"%s",__func__);
    }
    
    @end
    

    4、添加一个导航栏,然后在工程中新建一个VC,搭建如下界面

    界面效果


    然后新建一个类,和button关联 关联后
    最终效果:
    最终效果

    1、重写touchesBegan...touchesMoved....方法完成拖动效果

    #import "ThreeButton.h"
    
    
    
    @implementation ThreeButton{
        CGPoint _startP;
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        UITouch * touce = [touches anyObject];
        _startP = [touce locationInView:self];
    }
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        UITouch * touce = [touches anyObject];
        CGPoint curP = [touce locationInView:self];
        
        CGFloat x = self.frame.origin.x + curP.x - _startP.x;
        CGFloat y = self.frame.origin.y + curP.y - _startP.y;
        
        self.frame = CGRectMake(x, y, self.frame.size.width, self.frame.size.height);
    }
    
    
    @end
    

    2、添加一个超出button大小范围的View

    - (void)awakeFromNib{
        UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(80, -200, 200, 200);
        [button setImage:[UIImage imageNamed:@"222"] forState:UIControlStateNormal];
        [button setImage:[UIImage imageNamed:@"bc"] forState:UIControlStateHighlighted];
        [self addSubview:button];
    }
    

    3、此时运行程序如下:

    效果图1
    鼠标点击无效:并没有切换图片


    log打印如下
    2016-08-27 14:30:58.386 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:30:58.809 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:30:59.050 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:30:59.282 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:30:59.521 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:30:59.721 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:30:59.906 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:31:00.154 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    2016-08-27 14:31:00.377 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
    
    

    4、修改hitTest方法

    • 1、先把点转化
    • 2、判断点在不在图片按钮上
    • 3、做出对应处理
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        CGPoint isSub = [self convertPoint:point toView:self.subviews[0]];
        if ([self.subviews[0] pointInside:isSub withEvent:event]) {
            return self.subviews[0];
        }else {
            return [super hitTest:point withEvent:event];
        }
    }
    

    5、最终效果

    最终效果

    四、hitTest实现原理:

    • 1、检查自己是能否接受触摸事件
    • 2、检测触摸点是否在自己身上
    • 3、从后往前遍历子控件,重复1 2
    • 4、如果没有符合的子控件,那么自己就是合适的View

    实现代码

    重写BaseView的hitTest方法,如果能准确的找到子控件(最合适的View),那么久是正确的代码
    //实现原理
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //   1、检查自己是能否接受触摸事件
        //不接受事件或者隐藏或者透明,都返回nil,不是最合适的View
        if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha == 0) {
            return nil;
        }
    //   2、检测触摸点是否在自己身上
        //如果点不在控件上,返回nil
        if (![self pointInside:point withEvent:event]) {
            return nil;
        }
    //   3、从后往前遍历子控件,重复1 2
        NSInteger count = self.subviews.count;
        for (NSInteger i = count - 1; i >= 0; i--) {
            UIView * view = self.subviews[i];
            CGPoint viewP = [self convertPoint:point toView:view];
            
            if ([view hitTest:viewP withEvent:event]) {
                NSLog(@"%@",view);
                return view;
            }
        }
        
    //   4、如果没有符合的子控件,那么自己就是合适的View
        NSLog(@"%@",self);
        return self;
        
    }
    

    demo

    相关文章

      网友评论

        本文标题:iOS 事件传递以及响应

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