iOS点击事件和手势冲突

(一)、问题场景

父视图上添加了一个UITabelView和一个UIButton。
在parentView上添加了UITapGestureRecognizer之后,subview中的UITableView实例无法正常响应点击事件了,但UIButton实例仍可以正常工作。

(二)模拟场景现象:

image.png

图中baseView 有两个subView,分别是testView和testBtn。我们在baseView和testView都重载touchsBegan:withEvent、touchsEnded:withEvent、
touchsMoved:withEvent、touchsCancelled:withEvent方法,并且在baseView上添加单击手势,action名为tapAction,给testBtn绑定action名为testBtnClicked。
主要代码如下:

//baseView
- (void)viewDidLoad {
    [super viewDidLoad];
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
    [self.view addGestureRecognizer:tap];
    ...
    [_testBtn addTarget:self action:@selector(testBtnClicked) forControlEvents:UIControlEventTouchUpInside];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"=========> base view touchs Began");
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     NSLog(@"=========> base view touchs Moved");
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     NSLog(@"=========> base view touchs Ended");
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     NSLog(@"=========> base view touchs Cancelled");
}
- (void)tapAction {
     NSLog(@"=========> single Tapped");
}
- (void)testBtnClicked {
     NSLog(@"=========> click testbtn");
}
//test view
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"=========> test view touchs Began");
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"=========> test view touchs Moved");
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"=========> test view touchs Ended");
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"=========> test view touchs Cancelled");
}

情景A :单击baseView,输出结果为:

=========> base view touchs Began
=========> single Tapped
=========> base view touchs Cancelled

情景B :单击testView,输出结果为:

=========> test view touchs Began
=========> single Tapped
=========> test view touchs Cancelled

情景C :单击testBtn, 输出结果为:

=========> click testbtn

情景D :按住testView,过5秒后或更久释放,输出结果为:

=========> test view touchs Began
=========> test view touchs Ended

(二)分析:

1、情景A和B

情景A和B,都是在单击之后,既响应了手势的tap 事件,也让响应链方法执行了。为什么两个响应都执行了呢?

看看开发文档,就应该可以理解了。

Gesture Recognizers Get the First Opportunity to Recognize a Touch.

A window delays the delivery of touch objects to the view so that the gesture recognizer can analyze the touch first. During the delay, if the gesture recognizer recognizes a touch gesture, then the window never delivers the touch object to the view, and also cancels any touch objects it previously sent to the view that were part of that recognized sequence.

Google翻译:

手势识别器获得识别触摸的第一个机会。

一个窗口延迟将触摸对象传递到视图,使得手势识别器可以首先分析触摸。 在延迟期间,如果手势识别器识别出触摸手势,则窗口不会将触摸对象传递到视图,并且还将先前发送到作为识别的序列的一部分的视图的任何触摸对象取消。

图片:

image

触摸事件首先传递到手势上,如果手势识别成功,就会取消事件的继续传递,否则,事件还是会被响应链处理。具体地,系统维持了与响应链关联的所有手势,事件首先发给这些手势,然后再发给响应链。

这样可以解释情景A和B了。

首先,我们的单击事件,是有有手势识别这个大哥来优先获取。只不过,手势识别是需要一点时间的。在手势还是Possible 状态的时候,事件传递给了响应链的第一个响应对象(baseView 或者 testView)。

这样自然就去调用了,响应链UIResponder的touchsBegan:withEvent方法,之后手势识别成功了,就会去cancel之前传递到的所有响应对象,于是就会调用它们的touchsCancelled:withEvent:方法。

2、情境C

好了,情景A和B都可以解释明白了。但是,请注意,按这样的解释为什么情景C没有触发响应链的方法呢?

这里可以说是事件响应的一个特例

iOS 开发文档里这样说:

In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer. This applies only to gesture recognition that overlaps the default action for a control, which includes:

A single finger single tap on a UIButton, UISwitch, UISegmentedControl, UIStepper,and UIPageControl.A single finger swipe on the knob of a UISlider, in a direction parallel to the slider.A single finger pan gesture on the knob of a UISwitch, in a direction parallel to the switch.

Google 翻译为:

在iOS 6.0及更高版本中,默认控制操作可防止重叠的手势识别器行为。 例如,按钮的默认操作是单击。 如果您有一个单击手势识别器附加到按钮的父视图,并且用户点击按钮,则按钮的动作方法接收触摸事件而不是手势识别器。 这仅适用于与控件的默认操作重叠的手势识别,其中包括:

单个手指单击UIButton,UISwitch,UISegmentedControl,UIStepper和UIPageControl.

单个手指在UISlider的旋钮上滑动,在平行于滑块的方向上。在UISwitch的旋钮上的单个手指平移手势 与开关平行的方向。

所以呢,在情境C,里面testBtn的 默认action,获取了事件响应,不会把事件传递给父视图baseView,自然就不会触发,baseView的tap 事件了。

3、情境D

在情景D中,由于长按住testView不释放,tap手势就会识别失败,因为长按就已经不是单击事件了。手势识别失败之后,就可以继续正常传递给testView处理。

所以,只有响应链的方法触发了。

4、实际开发遇到的问题解决

基本的开发目标,不让父视图的手势识别干扰子视图UIView的点击事件响应或者说响应链的正常传递。

一般都会是重写UIGestureRecognizerDelegate中的- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch方法。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch  
{  
     // 若为UITableViewCellContentView(即点击了tableViewCell),
    if ([NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"]) {  
    // cell 不需要响应 父视图的手势,保证didselect 可以正常
        return NO;  
    }  
    //默认都需要响应
    return  YES;  
}

小结

复习一下结论:

(此处只讨论单击tap事件

1、手势响应是大哥,点击事件响应链是小弟。单击手势优先于UIView的事件响应。大部分冲突,都是因为优先级没有搞清楚。

2、单击事件优先传递给手势响应大哥,如果大哥识别成功,就会直接取消事件的响应链传递。 如果大哥识别失败了,触摸事件会继续走传递链,传递给响应链小弟处理。

3、手势识别是需要时间的。
手势识别有一个状态机的变化。在possible状态的时候,单击事件也可能已经传递给响应链小弟了

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

推荐阅读更多精彩内容

  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 5,987评论 4 26
  • 好奇触摸事件是如何从屏幕转移到APP内的?困惑于Cell怎么突然不能点击了?纠结于如何实现这个奇葩响应需求?亦或是...
    Lotheve阅读 56,579评论 51 597
  • 1 touchesBegan手势 iPhone中处理触摸屏的操作,在3.2之前是主要使用的是由UIResponde...
    Kevin_Junbaozi阅读 1,118评论 0 1
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,293评论 8 265
  • 触摸事件的生命周期 当我们手指触碰屏幕的那一刻,一个触摸事件便产生了。经过进程间通信,触摸事件被传递到合适的应用之...
    Gintok阅读 1,327评论 0 3