【问题标题】:Problems with gesture recognizer in iOS 7iOS 7 中手势识别器的问题
【发布时间】:2013-10-08 05:10:02
【问题描述】:

我正在向屏幕添加几个 UIView 对象(例如 5 个),一个在另一个内部。例如,view5.superview = view4view4.superview = view3view3.superview=view2view2.superview = view1。对于所有这些UIView,我设置了 uitapgesturerecognizer;对于 view1-4 我只是在回调中执行 NSLog(@"tap %@", self) ,而对于 view5 点击我设置以下内容:从层次结构中删除 view4,然后将相同的对象 view4' 放在层次结构的同一位置.该对象还包含 view5' 设置了 UITapGestureRecognizer (实际上,我用类似的部分替换了标记的一部分)。

然后我开始点击 view5。有时 view5 一直在捕捉它的点击,一切正常,但随后随机点击数次(每次这个数字不同),其中一个 view1-4 对象开始捕捉到这个点击,尽管我们仍在点击 view5。整个问题具有随机性——有时它发生在第 10 次启动时,有时发生在第二次启动时。有时错误的对象在第一次点击时开始捕捉点击。此外,当一切都出错时,我永远不知道什么物体会被点击。 view(n+1) 的框架被设置为例如框架 view(n) 的一半,而 view1 的框架 - 例如(0,0 320, 460)。

上面描述的所有 ui 对象的操作都是在主线程中进行的,我所说的一切都可以在 iOS 4.3 - 6.1 上完美运行,并带有更复杂的示例。但是 iOS7 让它成为了某种随机的地狱。

更新: 我创建了一个示例项目,以简化调试过程。没有点击添加/删除子视图操作。屏幕上只有 4 个视图,点击应用程序会记录被点击的视图。因此,您需要点击最小视图 (4)。如果您在日志中看到“tap 4 tap 4 tap 4…” - 这是一切正常时的情况,停止并再次运行,停止并再次运行,停止并再次运行,等等。并且在某些运行时(可能在 10 +成功运行)你不会在第一行看到“tap 4”,你会看到“tap 1”或“tap 2”或“tap 3”,它会继续这样 - 这些都是坏情况。

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

更新 2

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

更新 3

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

【问题讨论】:

  • 我也面临同样的问题。如果您得到解决方案,请在此处发布。我的问题是 UIPanGestureRecognizer
  • 我在使用 UITapGestureRecognizer 时遇到了类似的问题。奇怪的是,如果我用两根手指点击——其中一个在我的自定义视图上,另一个在其他视图上——它会做它的事情应该做的!然而,这不是一个很好的解决方法。
  • 向 Apple 提交雷达报告。
  • Moxy,你试过多次重启项目吗?我写了关于它 - “所以,你需要点击最小的视图(4)。如果你在日志中看到“点击 4 点击 4 点击 4...” - 这是一切正常的情况,停止并再次运行,停止并再次运行,停止并再次运行,等等。在某些运行中(可能在 10 次以上成功运行之后)您不会在第一行看到“tap 4”,您会看到“tap 1”或“tap 2”或“点击 3”,它会继续这样 - 这些是不好的情况。”
  • 顺便说一下,我们已经向 Apple 高级支持提出了请求,他们建议我们提交错误报告。 1个月前向Apple发送了Bug报告,但仍然没有回复。如果收到任何回复,我将在此线程中发布一行。

标签: ios ios7 uigesturerecognizer gesture uitapgesturerecognizer


【解决方案1】:

由于该问题仅在 iOS 7 中出现,您可以使用其中一种新的委托方法来解决该问题:

– gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
– gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:

我通过实现gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer 并“爬”上手势视图的超级视图解决了这个问题,因此如果我发现超级视图的手势等于提供的手势,我可以返回“YES”。我在这里详细说明我的完整分辨率:https://stackoverflow.com/a/19659848/1147934

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

注意:我使用自己的子分类识别器进行了广泛的测试,记录了所有的触摸,发现识别器失败的原因是超级视图手势在子视图的手势大约 5% 之前接收到触摸的案例。每次发生这种情况时,都会发生故障。如果你有很多手势的“深”层次结构,这种情况确实会更频繁地发生。

新的委托方法可能会令人困惑,因此您需要仔细阅读它们。

我正在使用该方法(我已重命名参数以使其更易于理解)

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

如果您返回“YES”,则提供的手势识别器 otherRecognizer 将要求 thisRecognizer 失败才能被识别。这就是为什么在我的回答中,我爬上超级视图层次结构以检查它是否包含具有otherRecognizer 的超级视图。如果是这样,我希望 otherRecognizer 要求 thisRecognizer 失败,因为 thisRecognizer 在子视图中,应该在它的超级视图的手势被识别之前失败。这将确保子视图手势在其父视图手势识别之前被识别。有意义吗?

替代方案
我可以反过来使用:

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

现在我需要遍历整个子视图层次结构,检查otherRecognizer是否在其中,如果是则返回YES。我不使用这种方法,因为爬取整个子视图层次结构比检查超级视图层次结构要困难得多且成本高。抓取子视图层次结构必须是一个递归函数,而我可以使用简单的while 循环来检查超级视图的层次结构。所以我推荐我概述的第一种方法。

警告!
小心使用gestureRecognizer:shouldReceiveTouch:。问题是哪个手势首先接收触摸的问题(取消另一个手势)......这是解决冲突的问题。如果您实现gestureRecognizer:shouldReceiveTouch:,如果子视图手势失败,您可能会拒绝超级视图的手势,因为您必须猜测何时可以识别子视图手势。子视图手势可能由于触摸超出范围以外的原因而合法地失败,因此您必须了解实现细节才能正确猜测。您希望在子视图手势失败时识别超级视图手势,但您实际上并不需要在实际失败之前确定它是否会失败。如果子视图手势失败,通常您希望超级视图手势能够识别。这是正常响应者链(子视图超级视图),如果你搞砸了,你最终可能会出现意想不到的行为。

【讨论】:

  • 那么如果我们想要激活手势,我们应该在gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer中返回YES还是NO?
  • 我将编辑我的问题并更全面地解释一下。评论足够大,我可以在这里回复。
  • 可爱...我设法错过了 cmets 的“编辑”窗口。我的意思是:评论不够我在这里回复。
  • 我看到的问题是UITableViewCell 上的平移手势识别器。每隔一段时间,平移手势就会停止在一个单元格上工作并且不会再次工作,除非应用程序重新启动(或单元格被破坏)。我尝试实现 shouldBeRequiredToFailByGestureRecognizer 并返回 YES 并没有再次看到问题,但这是一个罕见的问题,我没有机会确保问题不再发生。不过,我希望这是同一个错误,并且这种方法可以解决它。
  • 这听起来像是问题所在。只要存在视图/手势,问题就一直存在是此问题的典型“MO”。虽然我不使用平移手势,但我在遇到此问题的表格单元格上确实有自己的“拖动”手势,因此通过实现 shouldBeRequiredToFailByGestureRecognizer 得到修复。
【解决方案2】:

我已经对您的代码进行了一些更改,并且我也对其进行了很多测试,并且没有产生问题。

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

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];
}

更新:为什么会出现这个问题。

当您将UIView 添加为另一个UIView 的子视图时,每个视图中都有UITapGestureRecognizer,那么在极少数情况下UITapGestureRecognizer 状态会以某种方式变为失败我调试了 50 多次,才知道这一点)。因此,当任何视图的任何子视图都无法处理点击手势时,系统会将手势传递给它的超级视图以处理该手势,然后继续。

如果您进行调试,您会发现gestureRecognizerShouldBegin 根据视图层次结构被多次调用。在这种特殊情况下,如果我点击view3,那么gestureRecognizerShouldBegin 将调用3 次,因为view3 位于视图层次结构的第三级,因此gestureRecognizerShouldBegin 将被调用为view3, view2 and view1

所以为了解决问题,我返回YES 表单gestureRecognizerShouldBegin 以获得正确的视图,而NO 则用于其余的,所以它解决了问题。

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

【讨论】:

  • self.tag==gestureRecognizer.view.tag 永远是真的,对吧?我认为这不会解决问题
  • @KIDdAe 我已经编辑了我的答案,以解释实际导致问题的原因以及如何解决问题。我也测试了很多次。
  • 感谢更新 :),现在听起来好多了!
  • @Yuvrajsinh,感谢您的回复!你能看看主帖中的“更新3”吗?我们已经测试了您的解决方案,它适用于示例项目,但在更复杂的视图结构上失败。任何帮助将不胜感激!
【解决方案3】:

在识别器上设置一个委托并实现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

【讨论】:

  • 我发现仅仅实现委托是不够的。正如我最初建议的那样,添加一些标准来拒绝触摸确实有效。我已经更新了我的答案以在代码中演示这一点,这也修复了 BuggySample2。
【解决方案4】:

我没有尝试过您的项目或以下项目。

您应该能够使用gestureRecognizerShouldBegin: 来防止在触摸视图时触发任何不属于该视图的手势。

您可以使用UIView 的子类来做到这一点,或者您可以在UIView 上创建一个类别(带有属性或关联对象),它添加一个标志来确定每个视图实例应该做什么 - 这会破坏一些请注意查看类型。

如果问题是视图的顺序,那将无济于事......

【讨论】:

  • 你好,韦恩!谢谢你的帮助。不幸的是,建议的解决方案并不能解决问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多