24

If I simply call the push method with:

[self.navigationController pushViewController:viewController animated:YES];

then it uses a push animation. How do I change it to use a cross dissolve animation, like I can with a modal segue?

user3261697
  • 381
  • 1
  • 2
  • 6

7 Answers7

31

I use an extension for easy reuse:

extension UINavigationController {
    func fadeTo(_ viewController: UIViewController) {
        let transition: CATransition = CATransition()
        transition.duration = 0.3
        transition.type = CATransitionType.fade
        view.layer.add(transition, forKey: nil)
        pushViewController(viewController, animated: false)
    }
}

Notice how animated is false; when you set it to true, you still see the standard 'push' animation (right to left).

Jan Erik Schlorf
  • 2,050
  • 21
  • 36
25

You can use a CATransition as demonstrated in this answer:

CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.type = kCATransitionFade;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:viewController animated:NO];
Community
  • 1
  • 1
mspensieri
  • 3,501
  • 15
  • 18
  • 1
    This doesn't do the exact same thing. It dissolves in, but it still appears like it's sliding in. – BennyTheNerd May 04 '17 at 03:45
  • 4
    The sliding (pushing) animation will be disabled if you set animated to NO, so it does work as expected – David Sep 12 '17 at 09:14
16

The UINavigationControllerDelegate protocol has a method that can return a custom UIViewControllerAnimatedTransitioning object which will control the animation between the two view controllers involved in the transition.

Create an Animator class to control the cross-dissolve transition:

class Animator: NSObject, UIViewControllerAnimatedTransitioning {
    
    let animationDuration = 0.25
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return animationDuration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        toVC?.view.alpha = 0.0
        let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
        transitionContext.containerView.addSubview(fromVC!.view)
        transitionContext.containerView.addSubview(toVC!.view)
    
        UIView.animate(withDuration: animationDuration, animations: {
            toVC?.view.alpha = 1.0
        }) { (completed) in
            fromVC?.view.removeFromSuperview()
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

And provide it in your UINavigationControllerDelegate:

func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return Animator()
}

Here is a more in-depth tutorial: https://web.archive.org/web/20191204115047/http://blog.rinatkhanov.me/ios/transitions.html

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
GingerBreadMane
  • 2,630
  • 1
  • 30
  • 29
  • 2
    thanks for a highlevel (non coregraphics) method. fromVC?.view.removeFromSuperview() was screwing me up though. i got rid of that: upon transition back the presenter's view was predictably blank – Anton Tropashko Mar 20 '18 at 13:18
  • It's not necessary to add the `fromVC!.view` in the `containerView`. The linked article seems to confirm this statement. – Iulian Onofrei Apr 07 '21 at 15:42
9

SWIFT 3, 4.2 -- as of October, 2019

let transition = CATransition()
transition.duration = 0.5
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey:nil)
BennyTheNerd
  • 3,930
  • 1
  • 21
  • 16
3

You can set Push viewcontroller like this.

        CATransition* transition = [CATransition animation];
        transition.duration = 0.4;
        transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
        transition.type = kCATransitionFade;
        [self.navigationController.view.layer addAnimation:transition forKey:@"kCATransition"];
        [self.navigationController pushViewController:readerViewController animated:false];

You can set Pop viewcontroller like this.

         CATransition* transition = [CATransition animation];
         transition.duration = 0.4;
         transition.timingFunction = [CAMediaTimingFunction 
         functionWithName:kCAMediaTimingFunctionEaseOut];
         transition.type = kCATransitionFade;
         [self.navigationController.view.layer addAnimation:transition 
          forKey:@"kCATransition"];
         [self.navigationController popViewControllerAnimated:false];
kalpesh
  • 1,285
  • 1
  • 17
  • 30
3

Update Swift 5

Using prepare(for segue:) instead of pushViewController:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // other preparations

    let transition: CATransition = CATransition()
    transition.duration = 0.3
    transition.type = CATransitionType.fade
    navigationController?.view.layer.add(transition, forKey: nil)
}
Olympiloutre
  • 2,268
  • 3
  • 28
  • 38
0

Following michaels answer you can use UINavigationController extension.. something like this

/// Create an animatied fade push with default duration time.
/// - Parameters:
///   - controller: Which controller are we pushing...?
///   - animationDuration: What's the animation duration - has a default
func pushViewControllerWithFade(_ controller: UIViewController, animationDuration: TimeInterval = .Animation.defaultAnimationTime) {
    let transition: CATransition = .init()
    transition.duration = animationDuration
    transition.type = .fade
    pushViewController(controller, withTransition: transition)
}

/// Custom transition animation for pushing view contorller
/// - Parameters:
///   - controller: Which controller are we presenting now?
///   - transition: The transition for the push animation
func pushViewController(_ controller: UIViewController, withTransition transition: CATransition) {
    view.layer.add(transition, forKey: nil)
    pushViewController(controller, animated: false)
}
Tziki
  • 311
  • 1
  • 8