Restore pre-iOS7 UINavigationController pushViewController animation

20,681

Solution 1

Thanks guys for the feedback. Found a solution in completely recreating the UINavigationController's behavior. When I was nearly finished I ran into Nick Lockwood's solution:

https://github.com/nicklockwood/OSNavigationController

OSNavigationController is a open source re-implementation of UINavigationController. It currently features only a subset of the functionality of UINavigationController, but the long-term aim is to replicate 100% of the features.

OSNavigationController is not really intended to be used as-is. The idea is that you can fork it and then easily customize its appearance and behaviour to suit any special requirements that your app may have. Customizing OSNavigationController is much simpler than trying to customize UINavigationController due to the fact that the code is open and you don't need to worry about private methods, undocumented behavior, or implementation changes between versions.

By overriding my UINavigationController with his code, I was able to work with background images in UINavigationcontrollers

Thanks!

Solution 2

I managed to workaround the new transition type by creating a category for UINavigationController. In my case I needed to revert it to the old transition style because I have transparent viewControllers that slide over a static background.

  • UINavigationController+Retro.h

    @interface UINavigationController (Retro)
    
    - (void)pushViewControllerRetro:(UIViewController *)viewController;
    - (void)popViewControllerRetro;
    
    @end
    
  • UINavigationController+Retro.m

    #import "UINavigationController+Retro.h"
    
    @implementation UINavigationController (Retro)
    
    - (void)pushViewControllerRetro:(UIViewController *)viewController {
        CATransition *transition = [CATransition animation];
        transition.duration = 0.25;
        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        transition.type = kCATransitionPush;
        transition.subtype = kCATransitionFromRight;
        [self.view.layer addAnimation:transition forKey:nil];
    
        [self pushViewController:viewController animated:NO];
    }
    
    - (void)popViewControllerRetro {
        CATransition *transition = [CATransition animation];
        transition.duration = 0.25;
        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        transition.type = kCATransitionPush;
        transition.subtype = kCATransitionFromLeft;
        [self.view.layer addAnimation:transition forKey:nil];
    
        [self popViewControllerAnimated:NO];
    }
    
    @end
    

Solution 3

I have the same problem with clear background colors and crappy animations, so I create custom transitioning for ViewController with new iOS7 API. All you need is simply set a delegate for your navigation controller:

// NavigationController does not retain delegate, so you should hold it.
self.navigationController.delegate = self.navigationTransitioningDelegate;

Just add this files into your project: MGNavigationTransitioningDelegate.

Solution 4

I had a problem where when UIViewController A did a pushViewController to push UIViewController B, the push animation would stop at about 25%, halt, and then slide B in the rest of the way.

This DID NOT happen on iOS 6, but as soon as I started using iOS 7 as the base SDK in XCode 5, this started happening.

The fix is that view controller B did not have a backgroundColor set on its root view (the root view is the one that is the value of viewController.view, that you typically set in loadView). Setting a backgroundColor in that root view's initializer fixed the problem.

I managed to fix this as follows:

// CASE 1: The root view for a UIViewController subclass that had a halting animation

- (id)initWithFrame:(CGRect)frame

{

     if ((self = [super initWithFrame:frame])) {

          // Do some initialization ...

          // self.backgroundColor was NOT being set

          // and animation in pushViewController was slow and stopped at 25% and paused

     }

     return self;

}

// CASE 2: HERE IS THE FIX

- (id)initWithFrame:(CGRect)frame

{

     if ((self = [super initWithFrame:frame])) {

          // Do some initialization ...

          // Set self.backgroundColor for the fix!

          // and animation in pushViewController is no longer slow and and no longer stopped at 25% and paused

          self.backgroundColor = [UIColor whiteColor]; // or some other non-clear color

     }

     return self;

}

Solution 5

First of, I'm not using Storyboard. I tried using UINavigationController+Retro. For some reason, the UINavigationController is having a hard time releasing the UIViewController at the top of the stack. Here's the solution that works for me using iOS 7 custom transition.

  1. Set delegate to self.

    navigationController.delegate = self;
    
  2. Declare this UINavigationControllerDelegate.

    - (id<UIViewControllerAnimatedTransitioning>)navigationController (UINavigationController *)navigationController 
    animationControllerForOperation:(UINavigationControllerOperation)operation
    fromViewController:(UIViewController *)fromVC 
    toViewController:(UIViewController *)toVC 
    {
        TransitionAnimator *animator = [TransitionAnimator new]; 
        animator.presenting = YES;
        return animator;
    }
    

    Note that it'll only get called when animated is set to YES. For example

    [navigationController pushViewController:currentViewController animated:YES];
    
  3. Create the animator class extending NSObject. I called mine TransitionAnimator, which was modified from TeehanLax's TLTransitionAnimator inside UIViewController-Transitions-Example.

    TransitionAnimator.h

    #import <Foundation/Foundation.h>
    @interface TransitionAnimator : NSObject <UIViewControllerAnimatedTransitioning>
    @property (nonatomic, assign, getter = isPresenting) BOOL presenting;
    @end
    

    TransitionAnimator.m

    #import "TransitionAnimator.h"
    @implementation TransitionAnimator
    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
        return 0.5f;
    }
    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
        UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];    
        if (self.presenting) {
            //ANIMATE VC ENTERING FROM THE RIGHT SIDE OF THE SCREEN
            [transitionContext.containerView addSubview:fromVC.view];
            [transitionContext.containerView addSubview:toVC.view];     
            toVC.view.frame = CGRectMake(0, 0, 2*APP_W0, APP_H0); //SET ORIGINAL POSITION toVC OFF TO THE RIGHT                
            [UIView animateWithDuration:[self transitionDuration:transitionContext] 
                animations:^{
                    fromVC.view.frame = CGRectMake(0, (-1)*APP_W0, APP_W0, APP_H0); //MOVE fromVC OFF TO THE LEFT
                    toVC.view.frame = CGRectMake(0, 0, APP_W0, APP_H0); //ANIMATE toVC IN TO OCCUPY THE SCREEN
                } completion:^(BOOL finished) {
                    [transitionContext completeTransition:YES];
                }];
        }else{
            //ANIMATE VC EXITING TO THE RIGHT SIDE OF THE SCREEN
        }
    }
    @end
    

    Use presenting flag to set the direction you want to animate or which ever condition you prefer. Here's the link to Apple reference.

Share:
20,681
Bart van den Berg
Author by

Bart van den Berg

Updated on June 05, 2020

Comments

  • Bart van den Berg
    Bart van den Berg almost 4 years

    So. Just started transitioning my IOS code to IOS7, and ran into a bit of problem.

    I've got a UINavigationController, which has child ViewControllers and I'm using pushViewController to display the next views. To create a parallax animation with a set of images, if customized the UINavigationController to animate a set of UIImageViews and my child ViewControllers all have a self.backgroundColor = [UIColor clearColor], transparency.

    Since iOS7, the way the UINavController animates it child vc's, is updated, by partially moving the current view controller and on top pushing the new viewcontroller, my parallax animation looks crap. I see the previous VC move a bit and then disappear. Is there any way I can restore the previous UINavigationController pushViewController animation? I can't seem to find this in the code.

    WelcomeLoginViewController* welcomeLoginViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"WelcomeLogin"];
        [self.navigationController pushViewController:welcomeLoginViewController animated:YES];    
    

    Even tried using:

        [UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [self.navigationController pushViewController:welcomeLoginViewController animated:NO];
                             [UIView setAnimationTransition:<specific_animation_form> forView:self.navigationController.view cache:NO];
                         }];
    

    Does anyone have any clue?

  • Bart van den Berg
    Bart van den Berg over 10 years
    Hi Arne, Thanks for your solution. However you animate the UINavigationController's view instead of the child views of the controller. That way, the navigation bar exhibits different behavior. Tried solving by creating a completely custom animations of both views, but still not getting the desired effect.
  • mongeta
    mongeta over 10 years
    I'm having exactly the same problem, but unfortunately my view B has clearColor, I've just have to add exactly the same image background as self.parentViewController.view.xxxxxx and now it works... thanks!
  • GenesisST
    GenesisST over 10 years
    Unless I am wrong, this seems to only work with presenting a view controller. I could not make it work when pushing a view controller to a navigation controller.
  • Bill
    Bill over 10 years
    Thanks, this works for me. However, when the user taps the Back button, it reverts to the busted animation (because the back button doesn't call popViewControllerRetro).
  • jcsahnwaldt Reinstate Monica
    jcsahnwaldt Reinstate Monica over 10 years
    Bill, you can replace the back button with leftBarButtonItem. I chose transition.duration = 0.4 because it seemed to be closer to the old speed, at least on the Xcode simulators.
  • Bill Cheswick
    Bill Cheswick over 10 years
    Yes, this exactly fixed the problem. Thanks!
  • Miros
    Miros over 10 years
    This did the trick for me - I also had a viewcontroller with transparent background being pushed onto NavigationController with transparent background and a background image. The new iOS7 transition looked like crap, I'm wondering why Apple cannot just add those new features "silently", without "brute-enforcing" changes to native UI behaviour - gives us all a lot of trouble and headaches!
  • weienw
    weienw over 10 years
    This worked wonderfully for me. I was using clearColor for my viewControllers (in order to get smooth animation underneath on UIWindow subviews), but got an annoying random stutter until implementing this.
  • uerceg
    uerceg over 10 years
    @Bill: Check my answer below.
  • iOSAppDev
    iOSAppDev over 10 years
    Thanks for this great solution... I noticed one thing , when I call pushViewControllerRetro with time interval 0.4 I can see black color while transition. Any one saw this behavior ? Also simple popViewControler:animated works for me. So still do I need to use popViewControllerRetro ? Thanks
  • CedricSoubrie
    CedricSoubrie over 10 years
    Crazy solution. Worked for me also !
  • ttotto
    ttotto about 10 years
    Great. One thing is no need to use presenting variable. UINavigationControllerOperation indicates this operation is Push or Pop.
  • Martin Herman
    Martin Herman almost 10 years
    @iOSAppDev, I'm seeing the same thing! The black and white colours... anyone a fix?
  • iOSAppDev
    iOSAppDev almost 10 years
    @Martin I didn't get any solution yet
  • Martin Herman
    Martin Herman almost 10 years
    @iOSAppDev I think we should create a new question for that... After slowing it down to around 1 sec, I've found that it is because the old VC flashes white and the new one too. Check it out. By any chance, did you find some other solution?
  • Martin Herman
    Martin Herman almost 10 years
    @iOSAppDev - I found the solution. What I and probably you did, is add a background layer to the UINavigationController [_navController.view insertSubview:_someSubview atIndex:0]; ... Now what I do is add the background view to the UIWindow as a subview instead... [self.window addSubview:_someView]; and call [self.view setBackgroundColor:[UIColor clearColor]]; in every viewDidAppear of the UIViewController being pushed. Works fabulously! :)
  • Gerardo
    Gerardo almost 10 years
    Amazing! Best solution
  • amar
    amar almost 10 years
    How did you get that? i could have tried Voodoo instead..thanks
  • jestro
    jestro about 9 years
    Works like a charm! Thank you!
  • Sagar D
    Sagar D almost 8 years
    Very useful! Thank you!
  • Tom
    Tom almost 8 years
    Unfortunately this solution breaks autolayout - when you rotate and go back the views are all broken on the previous page until you rotate again.
  • Sergey Teryokhin
    Sergey Teryokhin about 7 years
    Good solution. I made Swift 3 version of your gist: gist.github.com/steryokhin/f136bd3e0c38b0fcdbff30b311714257
  • ArtFeel
    ArtFeel about 7 years