美文网首页UI 界面iOS开发技术分享Touch事件
深入理解touch事件和手势的关系

深入理解touch事件和手势的关系

作者: 喵子G | 来源:发表于2017-03-24 15:00 被阅读2370次

    1,手势和touch事件的先后关系

    测试视图结构

    为控制器、LightGrayView、RedView、GreenView、YellowView添加手势监听事件:

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"rootView touchBegan");
        [super touchesBegan:touches withEvent:event];
    }
    
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"rootView view touchCancelled");
        [super touchesCancelled:touches withEvent:event];
    }
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"rootView view touchMoved");
        [super touchesMoved:touches withEvent:event];
    }
    
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"rootView view touchEnded");
        [super touchesEnded:touches withEvent:event];
    }
    

    在控制器中为LightGrayView添加一个手势:

        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(jkr_tapGestureAction:)];
        [self.LightGrayView addGestureRecognizer:tapGesture];
    …
    - (void)jkr_tapGestureAction:(UITapGestureRecognizer *)tapGusture {
        NSLog(@"%@", tapGusture);
    }
    

    点击RedView,输出如下:

    yellow view is inside: 0
    yellow view hit: (null)
    lightGray view is inside: 1
    green view is inside: 0
    green view hit: (null)
    red view is inside: 1
    red view hit: <RedView: 0x7fb33a509680; frame = (0 0; 196 100); autoresize = RM+BM; layer = <CALayer: 0x600000033420>>
    lightGray view hit: <RedView: 0x7fb33a509680; frame = (0 0; 196 100); autoresize = RM+BM; layer = <CALayer: 0x600000033420>>
    red view touchBegan
    lightGray view touchBegan
    rootView touchBegan
    Tap action
    red view touchCancelled
    lightGray view touchCancelled
    rootView view touchCancelled
    

    这里可看到,手势方法在所有响应视图的touchBegan方法执行后调用,并且手势方法执行后,所有响应视图的touch事件全部取消。
    为了更详细的观察手势和touch事件的关系,现在自定义个一个获取它的tap事件的手势:

    #import "JKRTapGestureRecognizer.h"
    #import <UIKit/UIGestureRecognizerSubclass.h>
    
    @implementation JKRTapGestureRecognizer
    
    - (instancetype)initWithTarget:(id)target action:(SEL)action
    {
        self = [super initWithTarget:target action:action];
        self.delegate = self;
        return self;
    }
    
    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
    {
        NSLog(@"RecognizerShouldBegin");
        return YES;
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"tapgesture touchBegan");
        [super touchesBegan:touches withEvent:event];
    }
    
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"tapgesture touchCancelled");
        [super touchesCancelled:touches withEvent:event];
    }
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"tapgesture touchMoved");
        [super touchesMoved:touches withEvent:event];
    }
    
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"tapgesture touchEnded");
        [super touchesEnded:touches withEvent:event];
    }
    
    @end
    

    修改LightGrayView添加手势的类型为自定义手势,点击RedView查看输出:

    yellow view is inside: 0
    yellow view hit: (null)
    lightGray view is inside: 1
    green view is inside: 0
    green view hit: (null)
    red view is inside: 1
    red view hit: <RedView: 0x7f9ce840aab0; frame = (0 0; 196 100); autoresize = RM+BM; layer = <CALayer: 0x60800002d480>>
    2017-03-21 16:01:33.589 JKRUIResponderDemo[35855:3086959] lightGray view hit: <RedView: 0x7f9ce840aab0; frame = (0 0; 196 100); autoresize = RM+BM; layer = <CALayer: 0x60800002d480>>
    tapgesture touchBegan
    red view touchBegan
    lightGray view touchBegan
    rootView touchBegan
    tapgesture touchEnded
    RecognizerShouldBegin
    Tap action
    red view touchCancelled
    lightGray view touchCancelled
    rootView view touchCancelled
    

    通过输出可以发现,手势的touch事件是优先于视图的touch事件触发,并且tap手势是在tap手势的touchEnded方法之后才触发手势的识别和响应方法的处理,在手势处理后,视图的touch事件就会取消。

    结论:默认情况下,当一个touch事件发生后,如果touch事件响应者中有视图添加了手势,那么就优先处理添该视图的手势对象中的touch相关方法来处理,视图的touch事件在手势的touch事件之后处理。如果手势事件识别出来,那么手势事件之后的视图的touch事件就会取消。

    2,手势和视图的关系

    测试:

    将LightGrayView的pointInside方法返回值设置为NO,点击RedView观察输出可以发现,当视图无法成为touch事件的响应者的时候,它的手势也是无法识别的。

    结论:视图的手势的能够响应的前提是该视图能够成为touch事件的响应者。

    3,delaysTouchesBegan参数的作用

    该属性默认设置为NO,视图的touchesBegan方法会在手势的touchBegan事件之后执行;视图touchesMoved方法会在手势的touchesMoved方法后执行,视图的touchesEnded方法会在手势的touchesEnded方法后执行。在哪个touch方法中手势识别到并成功处理,并且cancelsTouchesInView为默认值YES,那么视图会调用touchesCancelled方法取消touch事件。
    该属性如果设置为YES,那么视图的触摸事件一定是在手势的touchEnded方法之后才确认是否去执行,如果手势没有识别到,就执行视图的touch方法。如果手势识别到并成功处理并且cancelsTouchesInView属性为YES,那么视图的touch方法都不会被执行。

    4,cancelsTouchesInView的作用

    该属性默认设置为YES,视图的手势在识别并处理后,会取消视图的touch事件。
    该属性如果设置为NO,那么视图的手势在识别并处理后,不会取消视图的touch事件,视图的touch事件继续执行。

    5,手势处理应用

    应用一:处理tap手势和touchesBegan之间的冲突

    思考:之前看到,默认状态下,手势的touchesBegan方法虽然先于视图的touchesBegan方法执行,但是手势的识别却是在视图的touchesBegan方法之后执行的,即手势虽然touch方法的处理优先级高于视图,但是手势还是不能够拦截视图的touchesBegan方法的。

    修改RedView的touchBegan方法,弹出一个弹框:

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"red view touchBegan");
        [super touchesBegan:touches withEvent:event];
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"touch" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        [alertView show];
    }
    

    给控制器的View加一个pan手势:

        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
        [self.view addGestureRecognizer:panGesture];
    

    点击RedView,直接就弹出了弹框,并不能处理滑动事件,并且控制器的View手势事件也不会回调了。

    现在就需要让手势的识别和处理优先级高度视图的touchesBegan方法,之前我们知道,默认状态下,手势的touchesBegan方法执行后马上就回执行视图的touchesBegan方法,所以手势还没有识别就调用了RedView的touchesBegan方法弹出了弹窗。现在设置delaysTouchesBegan为YES,让视图的touchesBegan方法等待到手势事件处理完毕执行touchesEnded方法后才能调用:

    panGesture.delaysTouchesBegan = YES;
    

    设置该属性后,点击了RedView,手势识别了touchesBegan和touchesEnded方法,并没有走touchesMoved方法,也没有成功识别到手势。然后再走RedView的touchesBegan方法,执行弹窗操作。现在就实现了点击RedView后弹出弹窗,在RedView上面滑动调用滑动方法处理滑动事件。

    应用二:处理pan手势和touchesMoved手势之间的冲突

    移除RedView的手势和touchesBegan中的弹窗方法,在RedView中添加一个layer,通过touchesMoved方法改变layer的位置:

    @interface RedView ()
    
    @property (nonatomic, strong) CALayer *jkr_layer;
    @property (nonatomic, assign) CGPoint jkr_layerCenter;
    
    @end
    
    @implementation RedView
    
    - (void)drawRect:(CGRect)rect {
        [super drawRect:rect];
        NSString *index = @"4";
        [index drawInRect:rect withAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor], NSFontAttributeName:[UIFont systemFontOfSize:18]}];
        self.jkr_layer.frame = CGRectMake(self.jkr_layerCenter.x - 10, self.jkr_layerCenter.y - 10, 20, 20);
        [self.layer addSublayer:self.jkr_layer];
    }
    
    - (CALayer *)jkr_layer {
        if (!_jkr_layer) {
            _jkr_layer = [CAShapeLayer new];
            _jkr_layer.frame = CGRectMake(0, 0, 20, 20);
            _jkr_layer.backgroundColor = [UIColor purpleColor].CGColor;
        }
        return _jkr_layer;
    }
    
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
        BOOL isInside = [super pointInside:point withEvent:event];
        NSLog(@"red view is inside: %zd", isInside);
        return isInside;
    }
    
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *view = [super hitTest:point withEvent:event];
        NSLog(@"red view hit: %@", view);
        return view;
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"red view touchBegan");
        [super touchesBegan:touches withEvent:event];
    }
    
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"red view touchCancelled");
        [super touchesCancelled:touches withEvent:event];
    }
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        UITouch *touch = touches.anyObject;
        CGPoint touchPoint = [touch locationInView:self];
        self.jkr_layerCenter = touchPoint;
        [self setNeedsDisplay];
        NSLog(@"red view touchMoved");
        [super touchesMoved:touches withEvent:event];
    }
    
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"red view touchEnded");
        [super touchesEnded:touches withEvent:event];
    }
    
    @end
    

    在RedView中滑动屏幕就可以看到紫色方块跟随手指移动。

    现在给RedView添加一个pan手势:

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
    [self.RedView addGestureRecognizer:pan];
    

    在RedView中滑动屏幕,发现滑动一个很短的距离后紫色方块不能够跟随手指移动了。
    观察输出发现当pan手势开始第一次调用滑动方法的时候,视图的touch事件被取消了:

    …
    rootView view touchMoved
    Pan action
    red view touchCancelle
    ![Uploading pan_750631.gif . . .]d
    …
    

    这是因为手势有一个属性cancelsTouchesInView,这个属性的做用就是决定是否当手势关联的方法调用的时候,调用视图的touchesCancelled:withEvent:方法取消视图的touch事件,默认值为YES。
    现在修改这个值为NO:

    pan.cancelsTouchesInView = NO;
    

    现在就可以在处理视图的滑动手势的同时处理视图的touchesMoved方法。

    pan.gif

    6,手势之间的共存和互斥

    现在有一个需求,需要为RedView添加两个手势,分别识别单击和双击,创建两个tap手势给RedView添加上,一个做单击处理,一个做双击处理:

        UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleAction)];
        [self.RedView addGestureRecognizer:singleTap];
        
        UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleAction)];
        doubleTap.numberOfTapsRequired = 2;
        [self.RedView addGestureRecognizer:doubleTap];
    //处理方法
    - (void)singleAction {
        NSLog(@"single action");
    }
    
    - (void)doubleAction {
        NSLog(@"double action");
    }
    

    要让两个手势区分开来用如下方法:

    [singleTap requireGestureRecognizerToFail:doubleTap];
    

    即让双击手势不成功识别的情况下才回调用单击手势。

    获取授权

    相关文章

      网友评论

        本文标题:深入理解touch事件和手势的关系

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