How do I cross dissolve when pushing views on a UINavigationController in iOS 7?
Asked Answered
S

7

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?

Sly answered 8/5, 2014 at 0:3 Comment(0)
F
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).

Fimbriation answered 15/5, 2018 at 10:10 Comment(1)
It is really the simplest and the fastest solution!Simulacrum
C
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];
Catbird answered 8/5, 2014 at 0:25 Comment(2)
This doesn't do the exact same thing. It dissolves in, but it still appears like it's sliding in.Counterstroke
The sliding (pushing) animation will be disabled if you set animated to NO, so it does work as expectedHerbage
G
17

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

Godber answered 16/1, 2018 at 19:48 Comment(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 blankJordon
It's not necessary to add the fromVC!.view in the containerView. The linked article seems to confirm this statement.Reagan
C
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)
Counterstroke answered 4/5, 2017 at 3:44 Comment(4)
You also need to set animated to false in pushViewController, right?Novelistic
No, it's not. If you want to have "FadeIn" effect, you have to set animated to falseBollinger
Animated set to true is the correct way to do this =)Counterstroke
@Counterstroke nope, animated must be set to false as Mateusz mentionedRhoads
B
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];
Beforetime answered 18/9, 2018 at 5:53 Comment(0)
F
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)
}
Fireplug answered 12/7, 2019 at 4:47 Comment(1)
Great simple solution. Thank you.Gasify
T
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)
}
Tarnetgaronne answered 19/4, 2022 at 10:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.