Navigation bar gets adjusted after calling completeTransition: in custom transition

My goal is to provide zooming modal transition from for the user from a view similar as springboard icons zoom in when launching apps.

The presented view controller zooms in correctly, but the navigation bar has wrong position under the status bar. This position gets corrected after calling [transitionContext completeTransition:finished];. How can I make it correct from the beginning of the transition?

  • How to zoom a UIScrollView inside of a UICollectionViewCell?
  • iOS Change alpha of UIButton when there is an EventTouchDown
  • How to wrap text around attachments using iOS7 Text Kit?
  • Why assign nil to IBOutlets in viewDidUnload?
  • Presenting a view controller modally from an action sheet's delegate in iOS8 - iOS10
  • How do copy for UILabel?
  • This is a screen recording of the bug: http://youtu.be/7LKU4lzb-uw (the glitch is in the 6th second of the recording)

    The UIViewControllerAnimatedTransitioning code:

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        UIView *container = [transitionContext containerView];
    
        CGPoint viewCenter = self.view.center;
        CGSize viewSize = self.view.frame.size;
        CGSize controllerSize = toViewController.view.frame.size;
    
        CGFloat controllerFromX = viewCenter.x - (controllerSize.width / 2);
        CGFloat controllerFromY = viewCenter.y - (controllerSize.height / 2);
    
        CGAffineTransform transform = CGAffineTransformMakeTranslation(controllerFromX, controllerFromY);
        transform = CGAffineTransformScale(transform, viewSize.width / controllerSize.width, viewSize.height / controllerSize.height);
    
        if (self.reverse) {
            [container insertSubview:toViewController.view belowSubview:fromViewController.view];
        } else {
            toViewController.view.transform = transform;
            [container addSubview:toViewController.view];
        }
    
        [UIView animateKeyframesWithDuration:ZoomTransitioningDuration 
                                       delay:0 
                                     options:0 
                                  animations:^{
                    if (self.reverse) {
                        fromViewController.view.alpha = 0.0f;
                        fromViewController.view.transform = transform;
                    } else {
                        toViewController.view.transform = CGAffineTransformIdentity;
                    }
            } 
                                  completion:^(BOOL finished) {
                    [transitionContext completeTransition:finished];
            }];
    }
    

    4 Solutions Collect From Internet About “Navigation bar gets adjusted after calling completeTransition: in custom transition”

    The problem is that you are setting the transform before inserting the destination view controller’s view into the container.

    Switching the order should fix it:

    if (self.reverse) {
        [container insertSubview:toViewController.view belowSubview:fromViewController.view];
    } else {
        [container addSubview:toViewController.view];
        toViewController.view.transform = transform;
    }
    

    See point 4 here. Since you’ve applied a transform prior to inserting the navigation controller’s view as a subview, the layout engine doesn’t think the navigation bar is at the top edge of the window, and therefore doesn’t need to be adjusted to avoid the status bar.

    I’ve found a solution, although pretty hacky. I have to manually adjust the navigation bar frame before the animation starts:

    if (self.reverse) {
        [container insertSubview:toViewController.view belowSubview:fromViewController.view];
    } else {
        toViewController.view.transform = transform;
        [container addSubview:toViewController.view];
    
        // fix navigation bar position to prevent jump when completeTransition: is called
        if ([toViewController isKindOfClass:[UINavigationController class]]) {
            UINavigationController* navigationController = (UINavigationController*) toViewController;
            UINavigationBar* bar = navigationController.navigationBar;
            CGRect frame = bar.frame;
            bar.frame = CGRectMake(frame.origin.x, frame.origin.y + 20.0f, frame.size.width, frame.size.height);
        }
    }
    

    Add the toViewControllerView first to the ContainerView, then set the toViewControllerView transform as given below.

    [container addSubview:toViewController.view];

    toViewController.view.transform = transform;

    This will solve the problem.

    This is still a hack, based on OndÅ™ej Mirtes’ one but it works better if you have an in-call status bar and you’re on iOS8

    if([toViewController isKindOfClass:[UINavigationController class]]) { 
      UINavigationController *navCtrl = (UINavigationController *)toViewController;
      UINavigationBar *navBar = navCtrl.navigationBar;
      if(navBar.frame.origin.y == 0 && navBar.frame.size.height == 44) {
        navBar.frame = CGRectMake(0, 0, navBar.frame.size.width, fmin(44 + [UIApplication sharedApplication].statusBarFrame.size.height, 64)); 
      }
    }
    

    Remains ugly though :/