[英]Problems with gesture recognizer in iOS 7
我在屏幕上添加了几个UIView
对象(例如5个),一个在另一个内部。 例如,这是view5.superview = view4
, view4.superview = view3
, view3.superview=view2
, view2.superview = view1
。 对于所有这些UIView
我都设置了uitapgesturerecognizer; 对于view1-4,我只是在回调中执行NSLog(@“ tap%@”,self),而对于view5,请设置以下内容:从层次结构中删除view4,然后将相同的对象view4'放在层次结构的相同位置。 该目的还包含view5'为其UITapGestureRecognizer
被设置(实际上,我取代具有相似一种标记的一部分)。
然后,我开始单击view5。 一段时间后,view5会继续捕捉其拍子,一切正常,但是稍后会随机点击数次(每次此数字不同时),尽管我们仍在单击view5,但view1-4的对象之一开始捕捉此拍子。 整个问题具有随机特征-有时在第10次发射时发生,有时在第二次发射时发生。 有时,错误的物体会在第一次敲击时开始抓住敲击。 同样,当一切都出错时,我也不知道会碰到哪个物体。 视图(n + 1)的帧设置为例如帧视图(n)的一半,而视图1的帧设置为例如(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
由于该问题仅发生在iOS 7中,因此您可以使用新的委托方法之一来解决此问题:
– gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
– gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:
我通过实现gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer
解决它,并“爬行”了该手势视图的gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer
视图,因此如果我发现该超级视图的手势与所提供的手势相等,则可以返回“ YES”。 我在这里详细说明了完整的分辨率: https : //stackoverflow.com/a/19659848/1147934 。
说明
iOS 7中的手势识别器存在的问题是,超级视图的手势在其子视图手势之一接收到其触摸之前就已经接收到其触摸。 这将导致超级视图手势识别,然后取消子视图的识别器……这是(错误的?)并且Apple提交了多个错误。 有人指出,苹果公司不保证手势接收触摸的顺序。 我认为很多“我们”都依赖于iOS 7中更改的实现细节。这就是为什么我们使用新的委托方法的原因,该方法似乎旨在帮助我们解决此问题。
注意:我通过使用自己的子分类识别器进行了广泛的测试,记录了所有触摸,发现识别器失败的原因是在大约5%的情况下,子视图的手势之前,超级视图手势已经接收到触摸。 每次发生这种情况,都会发生故障。 如果您的“深层”层次结构具有很多手势,则这种情况的发生频率会更高。
新的委托方法可能会造成混淆,因此您需要仔细阅读它们。
我正在使用方法(我已将参数重命名以使其更易于理解)
– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *) otherRecognizer
。
如果返回“是”,则提供的手势识别器otherRecognizer
将要求thisRecognizer
失败才能被识别。 这就是为什么在我的回答中,我向上爬otherRecognizer
视图层次结构以检查它是否包含具有otherRecognizer
视图。 如果是这样,我希望otherRecognizer
要求thisRecognizer
失败,因为thisRecognizer
在子视图中,并且应该在识别thisRecognizer
视图的手势之前失败。 这将确保子视图手势在其超级视图手势之前被识别。 说得通?
另类
我可以反过来使用它:
– gestureRecognizer:(UIGestureRecognizer *)thisRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherRecognizer
现在,我将需要遍历整个子视图层次结构,检查其中是否otherRecognizer
,如果otherRecognizer
,则返回YES
。 我不使用此方法,因为对整个子视图层次结构进行爬网比检查超级视图层次结构要困难得多,而且成本也高得多。 爬取子视图层次结构必须是递归函数,而我可以使用简单的while
循环来检查超级视图的层次结构。 因此,我建议我概述的第一种方法。
警告!
请谨慎使用gestureRecognizer:shouldReceiveTouch:
问题是哪个手势首先接收触摸(取消另一个手势)的问题……这是解决冲突的问题。 如果要实现gestureRecognizer:shouldReceiveTouch:
你就有可能拒绝上海华盈的姿态,如果因为你必须猜测 ,当一个子视图的姿态可能被认出的子视图的姿态出现故障。 子视图手势可能会由于触摸超出范围之外的原因而合法失败,因此您必须知道实现细节才能正确猜测。 您希望在子视图手势失败时能够识别超级视图手势,但是您实际上并不确定在实际失败之前是否会失败。 如果子视图手势失败,通常您希望该超级视图手势能够识别。 这是正常的响应者链(子视图超级视图),如果您对此感到困惑,则可能会导致意外行为。
我对您的代码进行了一些更改,并且也对其进行了很多测试,并且未产生问题。
创建视图时,我为每个视图设置标记以区分它:
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);
查看1234.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,我也从他的回答中找到了一些线索来解决问题。
在识别器上设置一个委托并实现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
上创建一个类别(具有属性或关联的对象),该类别会添加一个标志以确定每个视图实例应执行的操作-这会破坏某些视图类型,因此请当心。
如果问题是视图的顺序,那将无济于事...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.