iOS 7中手势识别器的问题

Kup*_*Kup 25 gesture uigesturerecognizer ios uitapgesturerecognizer ios7

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

然后我开始点击view5.有些时候view5继续捕捉它的点击,一切都没问题,但随后的点击随机数(每次这个数字不同),其中一个view1-4对象开始捕捉这个点击,虽然我们仍然点击view5.整个问题具有随机特征 - 有时它发生在第10次发射,有时发生在第二次.有时错误的物体会在第一次敲击时开始捕捉水龙头.当一切都出错时,我也永远不知道哪个物体会碰到水龙头.用于视图的帧(n + 1)被设置为例如帧视图(n)的一半,而用于view1的帧 - 例如(0,0 320,460).

上面描述的ui对象的所有操作都是在主线程中进行的,而我所讲述的所有内容都完全适用于iOS 4.3 - 6.1,并且有更复杂的例子.但是iOS7让它变成了一种随意的地狱.

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

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

更新2

我们已经向Apple发布了一个错误,我会在这里发布一些反馈意见.但是,任何好的解决方法都将非常感谢!

更新3

由Yuvrajsinh提供的解决方案真正致力于示例项目.不幸的是,它仍然没有帮助解决最初出现的主项目中出现的问题.现在的主要原因是,如果没有自我手势的任何视图都放在可点击内容上,则其下的随机视图元素开始捕捉交互(而不是具有交互手势集的顶部交互.你有任何想法如何解决它?更新的样本可以从这里下载:http://tech.octopod.com/test/BuggySample2.zip

Aar*_*man 19

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

– gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
– gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:
Run Code Online (Sandbox Code Playgroud)

我通过实现gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer和"爬行"手势视图的超视图来解决它,所以如果我发现超视图的手势等于所提供的手势,我可以返回"是".我在这里详细说明我的完整分辨率:https://stackoverflow.com/a/19659848/1147934.

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

注意:我通过使用我自己的sublcassed识别器进行了大量测试,记录所有触摸并发现原因识别器失败的原因是superview手势在子视图的手势约占约5%的情况之前接收到触摸.每次发生这种情况,都会发生失败.如果您拥有带有大量手势的"深层次"层次结构,则会更频繁地发生这种情况.

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

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

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

如果您返回"是",则提供的手势识别器otherRecognizer将需要thisRecognizer在才能被识别之前失败.这就是为什么在我的回答中,我爬上superview层次结构来检查它是否包含具有的超级视图otherRecognizer.如果是的话,我想otherRecognizer要求thisRecognizer失败,因为thisRecognizer是在一个子视图,并应该是上海华盈的手势被识别之前失败.这将确保超级视图的手势之前识别子视图手势.合理?

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

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

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

警告!
使用时要小心gestureRecognizer:shouldReceiveTouch:.问题是哪个手势首先接收触摸(取消另一个手势)的问题......这是冲突解决的问题.如果实现gestureRecognizer:shouldReceiveTouch:,你就有可能拒绝上海华盈的姿态,如果因为你的子视图手势失败当一个子视图的姿态可能会被识别.子视图手势可能由于触摸超出界限而合法地失败,因此您必须知道实现细节才能正确猜测.您希望在子视图手势失败时识别超级视图手势,但是您确实无法确切知道它是否会在实际失败之前失败.如果子视图手势失败,通常您希望超视图手势随后识别.这是正常的响应者链(子视图超级视图),如果你搞砸了,你最终会出现意想不到的行为.


Yuv*_*inh 12

我已经对你的代码做了一些更改,我也测试了很多,问题没有产生.

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

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);
Run Code Online (Sandbox Code Playgroud)

View1234.h

@interface View1234 : UIView <UIGestureRecognizerDelegate> {
    NSString *vid;
}

- (id) initWithId:(NSString *)vid_;
@end
Run Code Online (Sandbox Code Playgroud)

以下是整个代码 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];
}
Run Code Online (Sandbox Code Playgroud)

更新:为什么实际出现此问题.

当您添加UIView为与其他子视图UIViewUITapGestureRecognizer每个视图,然后在某些罕见的情况下,UITapGestureRecognizer状态变为失败不知何故(我已经调试它超过50倍,来知道这个).因此,当任何视图的任何子视图都无法处理轻击手势时,系统会将手势传递给它的超级视图以处理该手势,并且这将继续.

如果您进行调试,那么您将了解gestureRecognizerShouldBegin根据视图层次结构多次调用.在这种特殊情况下,如果我轻按view3,然后gestureRecognizerShouldBegin将调用3次作为view3是视图层次的第三级,所以gestureRecognizerShouldBegin会被调用view3, view2 and view1.

因此,为了解决问题,我将返回正确视图和其余部分的YES表单,因此它解决了问题.gestureRecognizerShouldBeginNO

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


Wai*_*ain 0

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

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

您可以使用 的子类来做到这一点UIView,或者您可以在UIView(带有属性或关联对象)上创建一个类别,该类别添加一个标志来确定每个视图实例应该执行的操作 - 这会破坏某些视图类型,因此请小心。

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