在iOS 7手势识别器的问题

我添加了几个UIView对象(例如5)到屏幕上,一个在另一个里面。 这个例如view5.superview = view4view4.superview = view3view3.superview=view2view2.superview = view1 。 对于所有这些UIView我设置uitapgesturerecognizer; 对于view1-4,我只是在callback中做NSLog(@“tap%@”,self),而对于view5,我设置了以下内容:从层次结构中删除view4,然后把相同的对象view4'放在层次结构的相同位置。 这个对象还包含view5', UITapGestureRecognizer设置UITapGestureRecognizer (实际上,我用相似的一个replace标记的一部分)。

然后我开始点击view5。 有些时候,view5一直在捕捉它的抽头,一切正常,但随后的抽头数(每当这个数字不同时),其中一个view1-4对象开始捕捉这个抽头,尽pipe我们仍然在点击view5。 整个问题具有随机性 – 有时发生在第十次发射,有时发生在第二次发射。 有时候错误的物体会在第一次敲击时开始捕捉水龙头。 当我遇到一切错误的时候,我也不知道什么东西会碰到水龙头。 视图(n + 1)的帧被设置为例如帧视图(n)的一半,而视图1的帧 – 例如(0,0 320,460)。

上述所有使用UI对象的操作都是在主线程中进行的,而且我所讲的所有关于iOS 4.3 – 6.1的工作都有很多复杂的例子。 但是iOS7使它成为某种随机的地狱。

更新:我创build了一个示例项目,以简化debugging过程。 点击不添加/删除子视图操作。 在屏幕上只有4个视图,点击应用程序logging点击的视图。 所以,你需要挖掘最小的观点(4)。 如果在日志中看到“点击4点击4点击4点…” – 这是一切正常的情况下,停止和再次运行,停止和再次运行,停止和再次运行等等。在一些运行(也许10 +成功的运行)你不会在第一行看到“点击4”,你会看到“点击1”或“点击2”或“点击3”,它会继续 – 这是坏的情况。

示例项目可以从这里下载: http : //tech.octopod.com/test/BuggySample.zip (在档案中只有33 Kb)。

更新2

我们已经向苹果发布了一个bug,当我们得到一些反馈时,我会在这里发布。 但是,任何良好的解决方法将不胜感激!

更新3

由Yuvrajsinh提供的解决scheme真的是在示例项目上工作。 不幸的是,它仍然无助于解决最初出现在主要项目中的问题。 现在的主要原因是,如果任何没有自我姿态的视图放置在可点击的内容上,它下面的随机视图元素将开始捕捉交互(而不是交互姿势集的顶部视图),你有什么想法如何解决?更新的示例可以从这里下载: http : //tech.octopod.com/test/BuggySample2.zip

由于问题只发生在iOS 7中,您可以使用新的委托方法之一来解决问题:

 – gestureRecognizer:shouldRequireFailureOfGestureRecognizer: – gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer: 

我通过执行gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer解决了这个gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer和“抓取”手势的视图的超视图,所以如果我发现超级视图的手势等于所提供的手势,我可以返回“是”。 我详细介绍了我的全分辨率: https : //stackoverflow.com/a/19659848/1147934 。

说明
iOS 7中的手势识别器的问题在于,在其子视图手势之一接收其触摸之前,超视图的手势正在接收其触摸。 这导致超视图手势识别哪个然后取消了子视图的识别器…这是(不正确的?)和多个错误已经提交给苹果公司。 有人指出,苹果不保证手势接触的顺序。 我认为很多“我们”一直依赖iOS 7中更改的实现细节。这就是为什么我们使用新的委托方法,这似乎旨在帮助我们解决这个问题。

注意:我使用自己的sublcassed识别器进行了广泛的testing,logging了所有触摸,并发现识别器失败的原因是因为超级查看手势在子视图的手势达到约5%之前正在接触触摸。 每次发生这种情况都会发生失败。 如果你有很多手势的“深”层次结构,这种情况会更频繁地发生。

新的委托方法可能会让人困惑,所以你需要仔细阅读。

我使用的方法(我已经重新命名的参数,使他们更容易理解)

– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *) otherRecognizer

如果返回“YES”,则提供的手势识别器otherRecognizer将要求thisRecognizer在可识别之前发生故障。 这就是为什么在我的回答中,我抓取superview层次结构来检查它是否包含具有otherRecognizer视图。 如果是这样,我希望otherRecognizer要求thisRecognizer失败,因为thisRecognizer器在子视图中,并且在它的thisRecognizer视图的手势被识别之前应该失败。 这将确保子视图手势其超级视图手势之前被识别。 合理?

替代
我可以绕过去,使用:

– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherRecognizer

现在我需要抓取整个子视图层次结构,检查是否存在otherRecognizer ,如果是,则返回YES 。 我不使用这种方法,因为爬行整个子视图层次结构要比检查超级视图层次结构要困难和昂贵得多。 爬行子视图层次将不得不是recursion函数,而我可以使用一个简单的while循环来检查超级视图的层次结构。 所以我推荐我概述的第一种方法。

警告!
使用gestureRecognizer:shouldReceiveTouch:要小心gestureRecognizer:shouldReceiveTouch: 问题是哪个手势首先接触到触摸(取消另一个手势)…这是一个冲突解决的问题。 如果您实现了gestureRecognizer:shouldReceiveTouch:则如果子视图手势失败,则可能会拒绝超视图的手势,因为您必须猜测何时可以识别子视图手势。 子视图手势可能因为触摸以外的原因而合法地失败,所以您将必须知道实现细节才能正确猜测。 当子视图手势失败时,你想要识别超级视图手势,但是如果在实际失败之前它确实会失败,你确实无法确定。 如果子视图手势失败,通常需要超视图手势才能识别。 这是正常的响应者链(子视图超级查看),如果你搞砸了,你可能会以意想不到的行为。

我在你的代码中做了一些改变,我也testing了很多,问题没有生成。

在创build视图时,我为每个视图设置标签以区分它:

 View1234 *v1 = [[View1234 alloc] initWithId:@"1"]; v1.tag =1; v1.backgroundColor = [UIColor redColor]; v1.frame = CGRectMake(0, 0, 320, 460); View1234 *v2 = [[View1234 alloc] initWithId:@"2"]; v2.tag=2; v2.backgroundColor = [UIColor greenColor]; v2.frame = CGRectMake(0, 0, 160, 230); View1234 *v3 = [[View1234 alloc] initWithId:@"3"]; v3.tag=3; v3.backgroundColor = [UIColor blueColor]; v3.frame = CGRectMake(0, 0, 80, 115); View1234 *v4 = [[View1234 alloc] initWithId:@"4"]; v4.tag=4; v4.backgroundColor = [UIColor orangeColor]; v4.frame = CGRectMake(0, 0, 40, 50); 

View1234.h

 @interface View1234 : UIView <UIGestureRecognizerDelegate> { NSString *vid; } - (id) initWithId:(NSString *)vid_; @end 

以下是View1234.m完整代码

 - (id) initWithId:(NSString *)vid_ { if (self = [super init]) { vid = [vid_ retain]; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init]; tapGesture.delegate = self; tapGesture.numberOfTapsRequired = 1; tapGesture.numberOfTouchesRequired = 1; [tapGesture addTarget:self action:@selector(handleTap:)]; [self addGestureRecognizer:tapGesture]; [tapGesture release]; } return self; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{ if (touch.view==self ) { return YES; } else if ([self.subviews containsObject:touch.view] && ![touch.view isKindOfClass:[self class]]) { return YES; } return NO; } /*- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { if (self.tag==gestureRecognizer.view.tag) { return YES; } } return NO; }*/ - (void) handleTap:(UITapGestureRecognizer *)tap { NSLog(@"tap %@", vid); } - (void) dealloc { [vid release]; vid = nil; [super dealloc]; } 

更新 :为什么这个问题实际上。

当你在每个视图中使用UITapGestureRecognizer作为另一个UIView的子视图添加一个UIView ,那么在一些less见的情况下, UITapGestureRecognizer状态会以某种方式变成失败我已经debugging了50多次,并且知道了这一点 )。 因此,当任何视图的任何子视图不能处理轻击手势时,系统将把手势传递给它的超级视图来处理该手势,并且这继续。

如果你debugging,那么你会知道该gestureRecognizerShouldBegin被称为多层次的视图。 在这种特殊情况下,如果我点击view3那么gestureRecognizerShouldBegin将调用3次,因为view3在第三级视图层次结构中,因此将为view3, view2 and view1调用view3, view2 and view1

所以要解决问题,我返回YES表单gestureRecognizerShouldBegin为正确的看法和其余的NO ,所以它解决了问题。

更新2 :我在编辑的答案中的代码进行了一些更改,希望能够解决您的问题。 还要感谢@masmor,我也从他的答案中find了一些线索来解决问题。

在识别器上设置委托并实现gestureRecognizer:shouldReceiveTouch:

实现应该基本上阻止触及子视图,但根据实际的视图层次和设置,可能会有一些额外的标准。

下面的示例只是检查命中视图是否是直接子视图,以及是否有任何手势识别器,在这种情况下,允许看到触摸。

阻挡触摸应该比识别器状态摆弄更强大,因为任何不需要的视图都不可能触发识别器。 实现自定义标准的要求是一个缺点,但是我认为当在这种情况下处理一个不明原因的错误时,明确地实现行为更加健壮。

 #import "View1234.h" @interface View1234 () <UIGestureRecognizerDelegate> @end @implementation View1234 - (id) initWithId:(NSString *)vid_ { if (self = [super init]) { vid = [vid_ retain]; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init]; tapGesture.numberOfTapsRequired = 1; tapGesture.numberOfTouchesRequired = 1; [tapGesture addTarget:self action:@selector(handleTap:)]; tapGesture.delegate = self; [self addGestureRecognizer:tapGesture]; [tapGesture release]; } return self; } //- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ // // if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { // if (self.tag==gestureRecognizer.view.tag) { // return YES; // } // } // // return NO; //} - (void) handleTap:(id)tap { NSLog(@"tap %@", vid); } - (void) dealloc { [vid release]; vid = nil; [super dealloc]; } - (BOOL)shouldReceiveTouchOnView:(UIView *)hitView { NSLog(@"Should view:\n%@\nreceive touch on view:\n%@", self, hitView); // Replace this implementation with whatever you need... // Here, we simply check if the view has a gesture recognizer and // is a direct subview. BOOL res = (hitView.gestureRecognizers.count == 0 && [self.subviews containsObject:hitView]); NSLog(@"%@", res? @"YES":@"NO"); return res; } #pragma mark - Gesture Recognizer Delegate - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { UIView *hitView = [self hitTest:[touch locationInView:self.superview] withEvent:nil]; if (hitView == self) { NSLog(@"Touch not in subview"); return YES; } return [self shouldReceiveTouchOnView:hitView]; } @end 

我还没有尝试过你的项目或下面。

您应该能够使用gestureRecognizerShouldBegin:防止任何不属于视图的视图触发的视图触发。

你可以用UIView的子类来做到这一点,或者你可以在UIView (带有属性或关联对象)上创build一个类别,它添加一个标志来确定每个视图实例应该做什么 – 这将打破一些视图types,所以要小心。

如果问题是观点的顺序,这将无济于事。