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.gif6,手势之间的共存和互斥
现在有一个需求,需要为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];
即让双击手势不成功识别的情况下才回调用单击手势。
网友评论