iOS 7 中手势识别的问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19126391/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:56:20  来源:igfitidea点击:

Problems with gesture recognizer in iOS 7

iosios7uigesturerecognizergestureuitapgesturerecognizer

提问by Kup

I'm adding several UIViewobjects (e.g. 5) to the screen, one inside the other. This, for example, view5.superview = view4, view4.superview = view3, view3.superview=view2, view2.superview = view1. For all these UIViewI set uitapgesturerecognizer; for view1-4 I just do NSLog(@"tap %@", self) in callback, while for view5 tap I set the following: delete view4 from the hierarchy, then put the same object view4' at the same place of the hierarchy. This object also contains view5' for which UITapGestureRecognizeris set (practically, I replace one part of markup with similar one).

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

Then I start clicking on view5. Some time view5 keeps catching its tap and everything's OK, but random number of taps later (every time this number is different) one of the view1-4 objects starts catching this tap, though we're still clicking the view5. The whole problem has a random character - sometimes it occurs at the 10th launch, sometimes at the second. Sometimes wrong objects start catching taps at the first tap. Also I never know what object will catch a tap, when everything goes wrong. The frame for view(n+1) was set, e.g., as a half of the frame view(n), while the frame for view1 - e.g. (0,0 320, 460).

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

All operations with ui objects described above are conducted in the main thread, and everything I've told about perfectly worked on iOS 4.3 - 6.1 with much more complex examples. But the iOS7 makes out of it some kind of a random hell.

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

Update:I've created a sample project, to simplify the debug process. No add/remove subview operations on tap. Only 4 views on screen, on tap the app logs what view was tapped. So, you need to tap on smallest view (4). If you see "tap 4 tap 4 tap 4…" in the log - this is the case when everything works fine, stop and run again, stop and run again, stop and run again, etc. And at some runs (maybe after 10+ successful runs) you won't see "tap 4" on the first line, you will see "tap 1" or "tap 2" or "tap 3", and it will continue so - these are the bad cases.

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

Sample project can be downloaded from here: http://tech.octopod.com/test/BuggySample.zip(just 33 Kb in archive).

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

Update 2

更新 2

We've posted a bug to Apple, I'll post here when we will get some feedback. However, any good workaround would be much appreciated!

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

Update 3

更新 3

Solution, provided by Yuvrajsinh is really working on the sample project. Unfortunately, it still does not help to solve the problem that occurred in the main project where it initially appeared. The main reason for now is that if any view without self gesture is laying upon the clickable content, random view element under it starts catching the interaction (instead of the top one with interaction gesture set. Do you have any ideas how it can be solved? The updated sample can be downloaded from here: http://tech.octopod.com/test/BuggySample2.zip

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

回答by Aaron Hayman

Because the problem is only occurring in iOS 7, you can use one of the new delegate methods to resolve the issue:

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

– gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
– gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:

I resolved it by implementing gestureRecognizer:shouldBeRequiredToFailByGestureRecognizerand "crawling" up the gesture's view's superview so I could return "YES" if I find the superview's gesture is equal to the one provided. I detail my full resolution here: https://stackoverflow.com/a/19659848/1147934.

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

Explanation
The problem with gesture recognizers in iOS 7 is that a superview's gesture is receivingits touches before one of its subview gestures receives its touches. This causes the superview gesture to recognize which then cancels out the sub view's recognizer... this is (incorrect?) and multiple bugs have been filed with Apple. It's been pointed out that Apple doesn't guarantee the order in which gestures receive touches. I think a lot of "us" have been relying on an implementation detail that changed in iOS 7. This is why we use the new delegate methods, which seem designed to help us address this problem.

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

Note:I did extensive testing by using my own sublcassed recognizers, logging all touches and discovered that the reason recognizers fail is because superview gestures were receiving touches before a subview's gesture was in about ~5% of the cases. Every time this happened, failure occurred. This does happen more often if you have "deep" hierarchies with lots of gestures.

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

The new delegate methods can be confusing, so you need to read them carefully.

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

I'm using the method (I've renamed the arguments to make them easier to understand)

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

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

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

If you return "YES", then the gesture recognizer provided, otherRecognizer, will require thisRecognizerto fail before it can be recognized. This is why, in my answer, I crawl up the superview hierarchy to check if it contains a superview that has the otherRecognizer. If it does, I want otherRecognizerto require thisRecognizerto fail because thisRecognizeris in a subview and shouldfail before it's superview's gesture is recognized. This will make sure that subview gestures are recognized beforetheir superview's gestures are. Make sense?

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

Alternative
I could go about it the other way around and use:

或者,
我可以反过来使用:

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

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

Now I would need to crawl through my entire subviewhierarchy, check if otherRecognizeris in it and return YESif it is. I don't use this method because crawling the entire subview hierarchy is much more difficult and expensive to do than to check a superview hierarchy. Crawling a subview hierarchy would have to be a recursive function, while I can use a simple whileloop to check a superview's hierarchy. So I recommend the first approach I outline.

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

Warning!
Be careful about using gestureRecognizer:shouldReceiveTouch:. The issue is a problem of which gesture receives touches first (canceling the other gesture out)... it's a problem of conflict resolution. If you implement gestureRecognizer:shouldReceiveTouch:, you risk rejecting a superview's gesture if the subview gesture fails because you have to guesswhen a subview gesture mightbe recognized. A subview gesture may legitimately fail for reasons other than the touches are out of bounds, so you would have to know implementation details in order to guess correctly. You wantthe superview gesture to be recognized when the subview gesture fails but you don't really have anyway to know for certain if it will fail before it actually fails. If a subview gesture fails, normally you want the superview gesture to then recognize. This is the normalresponder chain (subview superview) and if you mess with that you could end up with unexpected behavior.

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

回答by Yuvrajsinh

I have made some changes in your code and I also have tested it much and problem is not generating.

我对您的代码进行了一些更改,并且我也对其进行了大量测试,但没有产生问题。

While creating view I set tag to each view to distinguish it:

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

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

查看1234.h

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

- (id) initWithId:(NSString *)vid_;
@end

And following is whole code of View1234.m

以下是整个代码 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];
}

UPDATE: Why This problem comes actually.

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

When you add a UIViewas a subview of another UIViewwith UITapGestureRecognizerin each view, then in some rare case UITapGestureRecognizerstate becomes Failedsomehow (I have debug it more than 50 times and come to know this). So when any subview of any view is not able to handle the tap gesture then system will pass gesture to it's super view to handle that gesture, and this continues.

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

If you debug then you will come to know that gestureRecognizerShouldBeginis called multiple times as per hierarchy of view. In this particular case if I tap on view3then gestureRecognizerShouldBeginwill call 3 timesas view3is on 3rd level of view hierarchy, so gestureRecognizerShouldBeginwill be called for view3, view2 and view1.

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

So to solve problem I am returning YESform gestureRecognizerShouldBeginfor the correct view and NOfor the rest, so it solves problem.

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

UPDATE 2: I have made some change in code in my edited answer and hope will solve your problem. And also thanks to @masmor, I also found some clue from his answer to make problem solve.

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

回答by voxlet

Set a delegate on the recognizer and implement gestureRecognizer:shouldReceiveTouch:.

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

The implementation should basically block touches on subviews, but there will probably be some additional criteria according to your actual view hierarchy and setup.

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

The sample below simply checks if the hit view is a direct subview and if it has any gesture recognizers, in which case it is allowed to see the touches.

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

Blocking touches should be more robust than fiddling with recognizer states, as there is no chance for any unwanted views to fire their recognizers. The requirement of implementing custom criteria is a drawback, but again I think it's more robust to explicitly implement behavior when working around a bug of unknown cause like in this case.

阻止触摸应该比摆弄识别器状态更强大,因为任何不需要的视图都没有机会触发它们的识别器。实现自定义标准的要求是一个缺点,但我再次认为,在解决像这种情况下的未知原因的错误时,明确实现行为会更健壮。

#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

回答by Wain

I haven't tried your project or the below.

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

You should be able to use gestureRecognizerShouldBegin:to prevent any gesture which doesn't belong to the view from firing when the view is touched.

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

You could do that with a subclass of UIView, or you could create a category on UIView(with a property or associated object) which adds a flag to determine what each view instance should do - this will break some view types so beware.

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

That won't help if the problem is the order of the views...

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