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<UITouch *> *)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<UITouch *> *)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<UITouch *> *)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<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
    [super touchesBegan:touches withEvent:event];
}

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

- (void)touchesCancelled:(NSSet<UITouch *> *)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。所以具体事件具体分析,理解原理后万变不离其宗。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容