touch类方法,target action及手势响应间的比较

前言

就iOS而言,app与用户间的交互一般通过UIResponder中的touch类方法,UIControl中的target action方法以及UIGestureRecognizer中的手势来完成。那么三者间的区别和联系究竟是什么?

touch类方法与target action比较

相信大家都知道,UIControl继承自UIView,而UIView继承自UIResponder,即UIControl<--UIView<--UIResponder。所以,先分析touch类方法与target action间的关系。

自定义TControl类,并实现touchesBegan方法

@interface TControl : UIControl
@end

@implementation TControl
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
    [super touchesBegan:touches withEvent:event];
}
@end

在VC中这样写

- (void)viewDidLoad {
    [super viewDidLoad];
    
    TControl *control = [[TControl alloc] initWithFrame:self.view.bounds];
    [control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:control];
}

- (void)controlAction:(TControl *)control {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}

启动后点击屏幕发现,先响应touchesBegan后响应controlAction。在controlAction中打上断点,再次点击后可见调用栈如下:

target action

可见,target action的响应依赖于UIControl中实现的touchesEnded,而touchesEnded的响应需要依赖于touchesBegan,可猜测,如果将TControl类touchesBegan中的super调用方法去掉,controlAction无法响应,即:

@implementation TControl
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}
@end

重复以上步骤可发现,controlAction确实不会响应。

结论1:touch类方法优先于target action响应,target action的响应依赖于touch类方法,因此也可通过touch类方法实现阻断target action的响应。

target action与手势比较

添加手势需要调用UIView中的addGestureRecognizer方法,而UIControl继承自UIView(UIControl<--UIView),因此这两者作比较。

如果代码这样写:

@interface TControl : UIControl
@end

@implementation TControl
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
    [super touchesBegan:touches withEvent:event];
}
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    TControl *control = [[TControl alloc] initWithFrame:self.view.bounds];
    [control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchUpInside];
//    [control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:control];
    
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
    [control addGestureRecognizer:tap];
}

- (void)controlAction:(TControl *)control {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}
- (void)tapAction:(UITapGestureRecognizer *)tap {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}
@end

会发现,无论TControl类的touchesBegan中是否调用super,tapAction始终响应,controlAction始终不响应,并且响应顺序为先touch后gesture。同样在tapAction中打上断点,再次点击后调用栈如下:


gesture

可以看到,并没有任何的touch类方法被调用,这也说明了为什么TControl中的touchesBegan即使没调用super,手势依然可以响应。

同时,在UIGestureRecognizer文档中可以找到如下描述:

A gesture recognizer doesn’t participate in the view’s responder chain.

即,手势识别器不参与响应链传递,由此可得:touch类方法优先gesture响应,但gesture响应不依赖于touch类方法。

那么是否说明gesture的响应优先于target action?毕竟在TControl类的touchesBegan中调用了super,但是target action并没有响应。

继续改造代码:

@implementation TControl
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
    [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
    [super touchesCancelled:touches withEvent:event];
}
@end

运行后点击屏幕发现,touchesCancelled被调用从而导致touchesEnded不会响应,上文分析过当UIControl的UIControlEvents为UIControlEventTouchUpInside时,响应action需要调用UIControl的touchesEnded方法才行。此时touchesEnded不响应,所以target action不响应。

同理,在touchesCancelled打上断点再次点击屏幕后查看调用栈,可以发现的确是经gesture传递后调用的touchesCancelled方法。

touchesCancelled

在UIGestureRecognizer.h中搜索cancel关键字,会发现cancelsTouchesInView这个BOOL属性。属性默认值为YES,当手势被识别后会调用手势附加view的touchesCancelled方法,此时可能会导致部分事件无法响应。当属性设置为NO时,响应链正常传递。

    tap.cancelsTouchesInView = NO;

当加上这句代码时,可以发现tapAction和controlAction都是可以响应的。

顺便说一下UIGestureRecognizer中另外两个和touch相关的属相delaysTouchesBegandelaysTouchesEnded。这两个属性也都是BOOL值,其中delaysTouchesBegan默认值为NO,delaysTouchesEnded默认值为YES。

上文通过分析得出过一个结论:touch类方法优先gesture响应,但gesture响应不依赖于touch类方法。

其实并不完全正确,这个结论的前提是delaysTouchesBegan == NO,如果设置为YES,touch类的方法并不会调用。

    tap.delaysTouchesBegan = YES;

结论2:当gesture. delaysTouchesBegan == NO 时,touch类方法优先gesture响应。当gesture. delaysTouchesBegan == YES 时,只响应gesture方法。同时,gesture响应不依赖于touch类方法。

gesture. delaysTouchesBegan == YES时,在手势识别的过程中不会响应touch类方法。在手势识别失败后,延迟调用当前view的touchesBegan方法(值为NO时,识别失败不延时调用,识别成功不调用)。
同样,当gesture.delaysTouchesEnded == YES时,在手势识别的过程中不会响应touch类方法。在手势识别失败后,延迟调用当前view的touchesEnded方法(值为NO时,识别失败不延时调用,识别成功不调用)。

易错点

本文采用的是UIControl的UIControlEventTouchUpInside事件与UITapGestureRecognizer做对比,细心的看官可以发现,code中有一句注释掉的代码

//    [control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchDown];

这里写的是UIControl的UIControlEventTouchDown事件,这个事件只要control的touchesBegan可以响应就会调用,不需要touchesEnded。所以具体事件具体分析,理解原理后万变不离其宗。

你可能感兴趣的:(touch类方法,target action及手势响应间的比较)