使用默认的interactivePopGestureRecognizer快速向后滑动时,iOS 7损坏了UINavigationBar

klc*_*r89 39 uinavigationbar uinavigationcontroller ios ios7

我有一个问题,我坚持,但我不知道为什么它会发生; 如果我在堆栈上推送一个细节控制器,并且我使用默认的左边缘快速向后滑动interactivePopGestureRecognizer,我的父/根视图控制器UINavigationBar看起来很糟糕或类似的东西,几乎就像内置的iOS转换机制没有时间去做它的工作在细节视图消失后重置它.另外要澄清的是,这个'腐败' UINavigationBar中的所有内容仍然可以触摸,我父/根视图控制器上的所有内容都能正常工作.

对于没有源代码的人投票:没有源代码!这是一个Apple bug!

无论如何,UINavigationBar当调用父/根视图控制器的viewDidAppear方法时,是否将其重置为应该是什么?

请注意,如果我点击左上方的后退按钮而不是使用左边缘,则不会发生此错误interactivePopGestureRecognizer.

编辑:我添加了一个NSLog来检查在父/根视图控制器上viewDidAppear上的navigationBar的子视图计数,并且计数总是相同,是否损坏,所以我想知道为什么弹出的控制器会对我造成严重破坏UINavigationBar.

如果你可以帮助我,我会非常感激!谢谢.

我附上了它的外观截图:请注意,后面的V形图不是我的父/根视图控制器的一部分,它是从堆栈中弹出的部分.Testing123是父/根视图控制器的标题,而不是从堆栈弹出的标题.头部和齿轮图标是父/根视图控制器的一部分.

编辑:我认为这样的事情可以解决问题,但事实证明它没有,而且IMO也是非常糟糕的经历.这不是我正在寻找的那种解决方案.我发布了大笔奖金,所以这可以正确解决!.我只是不能在生产质量的应用程序中有这种奇怪的UI行为.

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self.navigationController pushViewController:[UIViewController new] animated:NO];
    [self.navigationController popToRootViewControllerAnimated:YES];
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

Tyl*_*ler 20

TL; DR

我做了一个类别UIViewController,希望能为你解决这个问题.我实际上无法重现设备上的导航栏损坏,但我可以在模拟器上经常这样做,这个类别解决了我的问题.希望它也可以在设备上为您解决.

问题和解决方案

我实际上并不确切知道是什么导致这种情况,但导航栏的子视图"图层"动画似乎要么执行两次,要么没有完全完成或......某事.无论如何,我发现你可以简单地为这些子视图添加一些动画,以迫使它们回到它们应该的位置(具有正确的不透明度,颜色等).诀窍是使用您的视图控制器的transitionCoordinator对象并挂钩到几个事件 - 即当您抬起手指并且交互式弹出手势识别器完成并且动画的其余部分开始时发生的事件,然后是发生的事件动画的非交互式一半结束.

你可以使用几种方法挂钩这些事件transitionCoordinator,特别是notifyWhenInteractionEndsUsingBlock:animateAlongsideTransition:completion:.在前者中,我们创建导航栏子视图层的所有当前动画的副本,稍微修改它们并保存它们,以便我们可以在动画的非交互部分完成后应用它们,这在完成块中这两种方法的后者.

摘要

  1. 在转换的交互部分结束时进行监听
  2. 在导航栏中收集所有视图图层的动画
  3. 稍微复制和修改这些动画(将fromValue设置为与toValue相同,将持续时间设置为零,以及其他一些内容)
  4. 在转换的非交互部分结束时进行监听
  5. 将复制/修改的动画应用回视图的图层

这是该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
Run Code Online (Sandbox Code Playgroud)

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

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

    [self fixNavigationBarCorruption];
}
Run Code Online (Sandbox Code Playgroud)