iOS 7,使用默认的interactivePopGestureRecognizer快速滑回时损坏UINavigationBar

我有一个问题,我坚持,但我不知道为什么它甚至发生; 如果我在堆栈中推送一个细节控制器,并且使用默认的左边缘interactivePopGestureRecognizer非常快速地刷回,那么我的父/根视图控制器的UINavigationBar看起来就会损坏或者什么东西,就像内置的iOS转换机制没有时间去做在细节视图消失后重置它的工作。 另外为了澄清,在这个'腐败' UINavigationBar中的一切仍然是可触摸的,我父母/根视图控制器上的一切都完美。

对于没有源代码的人来说downvoting:没有源代码! 这是一个苹果的错误!

有没有办法重置这个UINavigationBar应该是什么时,父/视图控制器的viewDidAppear方法被调用?

请注意,如果我点击左上angular的button而不是使用左边的interactivePopGestureRecognizer则不会出现此错误。

编辑:我添加了一个NSLog来检查父视图控制器上的viewDidAppear上的navigationBar的子视图计数,并且计数总是相同,损坏或不是,所以我想知道为什么popup的控制器正在肆虐与我UINavigationBar

如果你能帮助我,我将不胜感激! 谢谢。

我附上了它的样子的截图:请注意,后面的chevron不是我的父/根视图控制器的一部分,它是popup堆栈的一部分。 Testing123是父级/根视图控制器的标题,而不是popup堆栈的标题。 头部和齿轮图标是父级/根视图控制器的一部分。

编辑:我以为这样的事情可以解决这个问题,但事实certificate,这不是真的,也是不好的体验海事组织。 这不是我正在寻找的解决scheme。 我发布一个大的赏金,所以这可以正确解决! 😃。 我不能有这种奇怪的用户界面行为是在生产质量的应用程序。

 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.navigationController pushViewController:[UIViewController new] animated:NO]; [self.navigationController popToRootViewControllerAnimated:YES]; } 

在这里输入图像说明

TL; DR

我在UIViewController上做了一个类,希望能为你解决这个问题。 我实际上不能在设备上重现导航栏损坏,但我可以在模拟器上很频繁地执行此操作,而这个类别为我解决了这个问题。 希望它也能在你的设备上解决它。

问题和解决scheme

我其实不知道究竟是什么原因造成的,但是导航栏的子视图的“图层”animation似乎要么执行两次,要么没有完全完成。 无论如何,我发现你可以简单地添加一些animation到这些子视图,以强制他们回到他们应该在哪里(与正确的不透明度,颜色等)。 诀窍是使用你的视图控制器的transitionCoordinator对象并挂钩到两个事件 – 即当你抬起你的手指,交互式stream行手势识别器完成,animation的其余部分开始,然后发生的事件发生的事件当animation的非交互式一半结束时。

您可以使用transitionCoordinator上的几个方法来挂钩这些事件,特别是notifyWhenInteractionEndsUsingBlock: animateAlongsideTransition:completion: 在前者中,我们创buildnavbar子视图的所有当前animation的副本,稍微修改它们,并保存它们,以便稍后在animation的非交互式部分完成时应用它们,这些位于完成块中这两种方法中的后者。

概要

  1. 在转换的交互部分结束时收听
  2. 收集导航栏中所有视图图层的animation
  3. 稍微复制和修改这些animation(将值设置为与toValue相同的东西,将持续时间设置为零等几件事情)
  4. 在转换的非交互式部分结束时进行监听
  5. 将复制/修改的animation应用到视图的图层

这里是UIViewController类别的代码:

 @interface UIViewController (FixNavigationBarCorruption) - (void)fixNavigationBarCorruption; @end @implementation UIViewController (FixNavigationBarCorruption) /** * Fixes a problem where the navigation bar sometimes becomes corrupt * when transitioning using an interactive transition. * * Call this method in your view controller's viewWillAppear: method */ - (void)fixNavigationBarCorruption { // Get our transition coordinator id<UIViewControllerTransitionCoordinator> coordinator = self.transitionCoordinator; // If we have a transition coordinator and it was initially interactive when it started, // we can attempt to fix the issue with the nav bar corruption. if ([coordinator initiallyInteractive]) { // Use a map table so we can map from each view to its animations NSMapTable *mapTable = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:0]; // This gets run when your finger lifts up while dragging with the interactivePopGestureRecognizer [coordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) { // Loop through our nav controller's nav bar's subviews for (UIView *view in self.navigationController.navigationBar.subviews) { NSArray *animationKeys = view.layer.animationKeys; NSMutableArray *anims = [NSMutableArray array]; // Gather this view's animations for (NSString *animationKey in animationKeys) { CABasicAnimation *anim = (id)[view.layer animationForKey:animationKey]; // In case any other kind of animation somehow gets added to this view, don't bother with it if ([anim isKindOfClass:[CABasicAnimation class]]) { // Make a pseudo-hard copy of each animation. // We have to make a copy because we cannot modify an existing animation. CABasicAnimation *animCopy = [CABasicAnimation animationWithKeyPath:anim.keyPath]; // CABasicAnimation properties // Make sure fromValue and toValue are the same, and that they are equal to the layer's final resting value animCopy.fromValue = [view.layer valueForKeyPath:anim.keyPath]; animCopy.toValue = [view.layer valueForKeyPath:anim.keyPath]; animCopy.byValue = anim.byValue; // CAPropertyAnimation properties animCopy.additive = anim.additive; animCopy.cumulative = anim.cumulative; animCopy.valueFunction = anim.valueFunction; // CAAnimation properties animCopy.timingFunction = anim.timingFunction; animCopy.delegate = anim.delegate; animCopy.removedOnCompletion = anim.removedOnCompletion; // CAMediaTiming properties animCopy.speed = anim.speed; animCopy.repeatCount = anim.repeatCount; animCopy.repeatDuration = anim.repeatDuration; animCopy.autoreverses = anim.autoreverses; animCopy.fillMode = anim.fillMode; // We want our new animations to be instantaneous, so set the duration to zero. // Also set both the begin time and time offset to 0. animCopy.duration = 0; animCopy.beginTime = 0; animCopy.timeOffset = 0; [anims addObject:animCopy]; } } // Associate the gathered animations with each respective view [mapTable setObject:anims forKey:view]; } }]; // The completion block here gets run after the view controller transition animation completes (or fails) [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) { // Iterate over the mapTable's keys (views) for (UIView *view in mapTable.keyEnumerator) { // Get the modified animations for this view that we made when the interactive portion of the transition finished NSArray *anims = [mapTable objectForKey:view]; // ... and add them back to the view's layer for (CABasicAnimation *anim in anims) { [view.layer addAnimation:anim forKey:anim.keyPath]; } } }]; } } @end 

然后在你的视图控制器的viewWillAppear:方法中调用这个方法(在你的testing项目的情况下,它将是ViewController类):

 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self fixNavigationBarCorruption]; } 

在用debugging控制台Instruments和Reveal调查这个问题一段时间之后,我发现了以下内容:

1)在模拟器上,如果使用configuration文件/自动化模板并添加以下脚本,则可以每次重新创build该错误:

 var target = UIATarget.localTarget(); var appWindow = target.frontMostApp().mainWindow(); appWindow.buttons()[0].tap(); target.delay(1); target.flickFromTo({x:2, y: 100}, {x:160, y: 100}); 

2)在真实的设备上(iPhone 5s,iOS 7.1),这个脚本永远不会导致错误。 我尝试了各种滑动坐标和延迟的选项。

3)UINavigationBar包括:

 _UINavigationBarBackground (doesn't seem to be related to the bug) _UIBackdropView _UIBackgropEffectView UIView UIImageView UINavigationItemView UILabel (visible in the bug) _UINavigationBarBackIndicatorView (visible in the bug) 

4)当发生错误时,UILabel看起来是半透明的,而且位置错误,但是UILabel的实际属性是正确的(正常情况下,alpha:1和frame)。 此外_UINavigationBarBackIndicatorView看起来不符合实际的属性 – 它是可见的,虽然它是alpha是0。

从这我得出结论,这是一个模拟器的错误,你甚至不能从代码中发现错误。

所以@ troop231 – 你100%肯定这也发生在设备上?

关键概念

推动视图控制器时禁用手势识别器,并在视图出现时启用。

一个常见的解决scheme:子类

您可以UINavigationControllerUIViewController以防止损坏。

MyNavigationController:UINavigationController

 - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { [super pushViewController:viewController animated:animated]; self.interactivePopGestureRecognizer.enabled = NO; // disable } 

MyViewController:UIViewController

 - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; self.navigationController.interactivePopGestureRecognizer.enabled = YES; // enable } 

问题:太烦人了

  • 需要使用MyNavigationControllerMyViewController而不是UINavigationControllerUIViewController
  • 需要UICollectionViewController UITableViewControllerUICollectionViewController等等。

更好的解决scheme:方法Swizzling

这可以通过调配UINavigationControllerUIViewController方法来完成。 想知道方法swizzling,请访问这里 。

下面的例子使用JRSwizzle ,使方法调整容易。

UINavigationController的

 + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self jr_swizzleMethod:@selector(viewDidLoad) withMethod:@selector(hack_viewDidLoad) error:nil]; [self jr_swizzleMethod:@selector(pushViewController:animated:) withMethod:@selector(hack_pushViewController:animated:) error:nil]; }); } - (void)hack_viewDidLoad { [self hack_viewDidLoad]; self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self; } - (void)hack_pushViewController:(UIViewController *)viewController animated:(BOOL)animated { [self hack_pushViewController:viewController animated:animated]; self.interactivePopGestureRecognizer.enabled = NO; } 

的UIViewController

 + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self jr_swizzleMethod:@selector(viewDidAppear:) withMethod:@selector(hack_viewDidAppear:) error:nil]; }); } - (void)hack_viewDidAppear:(BOOL)animated { [self hack_viewDidAppear:animated]; self.navigationController.interactivePopGestureRecognizer.enabled = YES; } 

简单:使用开源

SwipeBack

SwipeBack自动执行而不需要任何代码。

使用CocoaPods ,只需将下面的一行添加到您的Podfile 。 你不需要写任何代码。 CocoaPods全局自动导入SwipeBack。

pod 'SwipeBack'

安装pod,就完成了!