How to change the Push and Pop animations in a navigation based app
Asked Answered
A

25

249

I have a navigation based application and I want to change the animation of the push and pop animations. How would I do that?

Edit 2018

There have been many answers to this question and it's been quite awhile now, I have re-chosen the answer to what I believe to be the most relevant now. If there is anyone that thinks otherwise please let me know in comments

Anaplastic answered 7/2, 2010 at 2:27 Comment(6)
As of iOS 7, there is official API for this; see UINavigationControllerDelegate's custom transition animation support. There's also a WWDC 2013 Video about this.Discrepancy
I have added an answer (below) for doing this in Swift - I came across this question asking about Swift implementations so thought I'd chime in with my subsequent implementation.Pedaias
For a very good tutorial with the official (iOS 7+) API, see: bradbambara.wordpress.com/2014/04/11/…Mesothorium
@JesseRusak updated link to WWDC 2013 Video: developer.apple.com/videos/play/wwdc2013-218Triggerhappy
For 2018 ! For this extremely old QA, nowadays you use UIViewControllerAnimatedTransitioning. I put in an answer down the bottom. Happy new year!Littrell
Changed my accepted answer guys n gals. Hope this helps! GLHFAnaplastic
L
71

Modern 2022 code.

How to change the Push and Pop animations in a navigation based app...

If you are new to iOS development. For the simplest, most common animations (such as "slide over" or "one pushes the other") you have to do a huge amount of work.

1. You need a custom UIViewControllerAnimatedTransitioning

  1. You need popStyle boolean - is it popping on, or popping off?

  2. You must include transitionDuration (trivial) and the main call, animateTransition

  3. You must write the two different animations routines, one for the push, and one for the pop. Inside animateTransition, simply branch on the boolean popStyle to one the two routines

  4. The example below does a simple move-over/move-off

  5. In your animatePush and animatePop routines. You must get the "from view" and the "to view". (How to do that, is shown in the code example.)

  6. and you must addSubview for the new "to" view.

  7. and you must call completeTransition at the end of your anime

Copy-paste ..

  class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {
        
        var popStyle: Bool = false
        
        func transitionDuration(
            using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.20
        }
        
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            
            if popStyle {
                
                animatePop(using: transitionContext)
                return
            }
            
            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            
            let f = transitionContext.finalFrame(for: tz)
            
            let fOff = f.offsetBy(dx: f.width, dy: 55)
            tz.view.frame = fOff
            
            transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
            
            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    tz.view.frame = f
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
        
        func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
            
            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            
            let f = transitionContext.initialFrame(for: fz)
            let fOffPop = f.offsetBy(dx: f.width, dy: 55)
            
            transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
            
            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    fz.view.frame = fOffPop
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
    }

NOTE - at the line of code insertSubview. Notice the comment chain below. If you experience a "black flash" during one of the transitions, that's caused by another problem. It's a common programming difficulty in UIKit that one has a problem loading layers or views (for example, often one is "missing a layout" somewhere in a custom view. However as a "quick fix" as explained by @Dmytro you can also insert the "fz" view, duplicate the insertSubview loc. However take care if you do this as it shouldn't be necessary.

And then ...

2. Use it in your view controller.

Note: strangely, you only have to do this in the "first" view controller. (The one which is "underneath".)

With the one that you pop on top, do nothing. Easy.

So your class...

class SomeScreen: UIViewController {
}

becomes...

class FrontScreen: UIViewController,
        UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
    
    let simpleOver = SimpleOver()
    

    override func viewDidLoad() {
        
        super.viewDidLoad()
        navigationController?.delegate = self
    }

    func navigationController(
        _ navigationController: UINavigationController,
        animationControllerFor operation: UINavigationController.Operation,
        from fromVC: UIViewController,
        to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        simpleOver.popStyle = (operation == .pop)
        return simpleOver
    }
}

That's it.

Push and pop exactly as normal, no change. To push ...

let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
          .instantiateViewController(withIdentifier: "nextScreenStoryboardID")
          as! NextScreen
navigationController?.pushViewController(n, animated: true)

and to pop it, you can if you like just do that on the next screen:

class NextScreen: TotallyOrdinaryUIViewController {
    
    @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
        
        navigationController?.popViewController(animated: true)
    }
}

Phew.

Littrell answered 3/1, 2018 at 16:38 Comment(7)
Excellent! If i want the navigation swipe back gesture support the same AnimatedTransitioning too. Any idea?Apostolic
thanks @samchiwen - indeed that's exactly what animatePush and animatePop are .. the two different directions!Littrell
When I pop navigation controller, the screen turn black. In debug view only transition view :(Eyre
@Eyre on pop animation add destination controller view to transitionContext.containerView as first elementCalculable
@Eyre if you have that problem consider you may have an issue elsewhere (like a custom control that is not being redrawn at the correct time; the given code works great all the time for years now. Take care that the "quick fix" may cause other problems. I've added a long note in the answer alerting readers to the possibility of using the quick fix! Enjoy!Littrell
@Fattie, docs states next regarding containerView. >UIKit sets this view for you and automatically adds the view of the presenting view controller to it. The animator object is responsible for adding the view of the presented view controller, and the animator object or presentation controller must use this view as the container for all other views involved in the transition.< In push case we add destination view as it will be presented. No problems here. But on pop, presenting view controller is the destination, following that we still need to add presented controller view to containerViewCalculable
Hi @DmytroAntonchenko ! What you say makes sense. I just don't know; I've never seen the problem exhibit doing it the "usual" way. (Could it relate to Note: strangely, you only have to do this in the "first" view controller. (The one which is "underneath".) With the one that you pop on top, do nothing. Easy. - IDK) Recall the presenting VC view is always there underneath. Unfortunately i just don't have to time to investigate the effects one way or the other :/ the info is there for anyone who need it!Littrell
B
278

I did the following and it works fine.. and is simple and easy to understand..

CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
//transition.subtype = kCATransitionFromTop; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[[self navigationController] popViewControllerAnimated:NO];

And the same thing for push..


Swift 3.0 version:

let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey: nil)
_ = self.navigationController?.popToRootViewController(animated: false)
Basenji answered 14/4, 2011 at 8:7 Comment(15)
+1, this really is the most sane solution. Just a minor note for future visitors: the Animated:NO part is vital. If YES is passed, the animations mix and cause funny effects.Nightcap
Best solution so far.. And for the beginners, don't forget to include QuartCore (#import <QuartzCore/QuartzCore.h>)Cavalcade
It seems that this approach has better performance, however how to make screen rotate from left to right like as the UIView animation solution with the UIViewAnimationTransitionFlipFromLeft option. I tried with all transition's types but don't see such effect.Caddell
Is there an example in which this code works? Not sure why I can't make it go.Centime
The only problem I have with this solution that the pushed viewcontroller's viewDidAppear gets called immediately after I push it without animation. Is there a way around it?Scythe
My issue with this code is each view appears to flash to gray or white as they slide in or out.Psychological
checked on iOS 7.1.2 and iOS 8.3 — this code is working fine also working fine for method setViewControllers:Poseur
Perfect but one thing more that to hide navigation bar, use this [self.navigationController setNavigationBarHidden:YES animated:NO];Lugansk
its not working when VC is pushed ..works when poppedGideon
@MukulMore For push Use: transition.type = kCATransitionPush; transition.subtype = kCATransitionFromLeft;//RightRemarkable
Does not work in case of opengl-based views usage in destination controller. Alan Zeino method does work.Cyanine
This 7 yr old answer is, honestly, just not a solution. It's one of those cases on SO where a "quick fix" that really doesn't work gets a lot of upvotes! Really with the massive changes to iOS since then, it's just not viable, unfortunately.Littrell
Again, purely for anyone googling here, this 8 year old answer does not work nowadays. I update the answer below (see "2018") every month or two so it has the latest Swift syntax. There are now a couple more answers which use the correct modern approach. It's a good example of how on SO, some answers get really, really out of date.Littrell
@Littrell For my simple case where I want to dismiss a VC once with a fade animation, this solution works fine. What do you mean with 'does not work'?Zabrine
This is the simplest solution. However it also affects other animations occurring at the same time on the view controllers. If this is not acceptable then do https://mcmap.net/q/115908/-how-to-change-the-push-and-pop-animations-in-a-navigation-based-appCerement
G
256

This is how I've always managed to complete this task.

For Push:

MainView *nextView=[[MainView alloc] init];
[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:nextView animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
[nextView release];

For Pop:

[UIView  beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
[UIView commitAnimations];

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelay:0.375];
[self.navigationController popViewControllerAnimated:NO];
[UIView commitAnimations];


I still get a lot of feedback from this so I'm going to go ahead and update it to use animation blocks which is the Apple recommended way to do animations anyway.

For Push:

MainView *nextView = [[MainView alloc] init];
[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [self.navigationController pushViewController:nextView animated:NO];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
                         }];

For Pop:

[UIView animateWithDuration:0.75
                         animations:^{
                             [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                             [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.navigationController.view cache:NO];
                         }];
[self.navigationController popViewControllerAnimated:NO];
Gnarl answered 4/5, 2011 at 20:56 Comment(8)
Thanks for this. But the pop is done automatically by the UINavigationController. How do you override that behavior so you can call your custom pop logic?Steere
@stuckj actually it does work!!! you just have to replace super by self.navigationControllerHaymaker
Is there any way to get a slide from the left instead of the default slide from the right?Marmite
The first doesn't show the new view at all. The second doesn't show animation. Very bad answer! iOS 7.Basel
@Altaveron If you keep in mind what this is doing, it is a really simple solution. Essentially you are just telling the navigation controller not to do its own animation on its view and taking care of it yourself.Gnarl
See this answer for an example of how to roll this code into a Category on UINavigationController for easier reuse: https://mcmap.net/q/115908/-how-to-change-the-push-and-pop-animations-in-a-navigation-based-appApologize
why have you gived to UIViewController subclass a name without "ViewController" part. This name is more appropriate for UIView.Persuade
Swift 3 solution please?Advocaat
L
71

Modern 2022 code.

How to change the Push and Pop animations in a navigation based app...

If you are new to iOS development. For the simplest, most common animations (such as "slide over" or "one pushes the other") you have to do a huge amount of work.

1. You need a custom UIViewControllerAnimatedTransitioning

  1. You need popStyle boolean - is it popping on, or popping off?

  2. You must include transitionDuration (trivial) and the main call, animateTransition

  3. You must write the two different animations routines, one for the push, and one for the pop. Inside animateTransition, simply branch on the boolean popStyle to one the two routines

  4. The example below does a simple move-over/move-off

  5. In your animatePush and animatePop routines. You must get the "from view" and the "to view". (How to do that, is shown in the code example.)

  6. and you must addSubview for the new "to" view.

  7. and you must call completeTransition at the end of your anime

Copy-paste ..

  class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {
        
        var popStyle: Bool = false
        
        func transitionDuration(
            using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.20
        }
        
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            
            if popStyle {
                
                animatePop(using: transitionContext)
                return
            }
            
            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            
            let f = transitionContext.finalFrame(for: tz)
            
            let fOff = f.offsetBy(dx: f.width, dy: 55)
            tz.view.frame = fOff
            
            transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
            
            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    tz.view.frame = f
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
        
        func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
            
            let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            
            let f = transitionContext.initialFrame(for: fz)
            let fOffPop = f.offsetBy(dx: f.width, dy: 55)
            
            transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
            
            UIView.animate(
                withDuration: transitionDuration(using: transitionContext),
                animations: {
                    fz.view.frame = fOffPop
            }, completion: {_ in 
                    transitionContext.completeTransition(true)
            })
        }
    }

NOTE - at the line of code insertSubview. Notice the comment chain below. If you experience a "black flash" during one of the transitions, that's caused by another problem. It's a common programming difficulty in UIKit that one has a problem loading layers or views (for example, often one is "missing a layout" somewhere in a custom view. However as a "quick fix" as explained by @Dmytro you can also insert the "fz" view, duplicate the insertSubview loc. However take care if you do this as it shouldn't be necessary.

And then ...

2. Use it in your view controller.

Note: strangely, you only have to do this in the "first" view controller. (The one which is "underneath".)

With the one that you pop on top, do nothing. Easy.

So your class...

class SomeScreen: UIViewController {
}

becomes...

class FrontScreen: UIViewController,
        UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
    
    let simpleOver = SimpleOver()
    

    override func viewDidLoad() {
        
        super.viewDidLoad()
        navigationController?.delegate = self
    }

    func navigationController(
        _ navigationController: UINavigationController,
        animationControllerFor operation: UINavigationController.Operation,
        from fromVC: UIViewController,
        to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        simpleOver.popStyle = (operation == .pop)
        return simpleOver
    }
}

That's it.

Push and pop exactly as normal, no change. To push ...

let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
          .instantiateViewController(withIdentifier: "nextScreenStoryboardID")
          as! NextScreen
navigationController?.pushViewController(n, animated: true)

and to pop it, you can if you like just do that on the next screen:

class NextScreen: TotallyOrdinaryUIViewController {
    
    @IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
        
        navigationController?.popViewController(animated: true)
    }
}

Phew.

Littrell answered 3/1, 2018 at 16:38 Comment(7)
Excellent! If i want the navigation swipe back gesture support the same AnimatedTransitioning too. Any idea?Apostolic
thanks @samchiwen - indeed that's exactly what animatePush and animatePop are .. the two different directions!Littrell
When I pop navigation controller, the screen turn black. In debug view only transition view :(Eyre
@Eyre on pop animation add destination controller view to transitionContext.containerView as first elementCalculable
@Eyre if you have that problem consider you may have an issue elsewhere (like a custom control that is not being redrawn at the correct time; the given code works great all the time for years now. Take care that the "quick fix" may cause other problems. I've added a long note in the answer alerting readers to the possibility of using the quick fix! Enjoy!Littrell
@Fattie, docs states next regarding containerView. >UIKit sets this view for you and automatically adds the view of the presenting view controller to it. The animator object is responsible for adding the view of the presented view controller, and the animator object or presentation controller must use this view as the container for all other views involved in the transition.< In push case we add destination view as it will be presented. No problems here. But on pop, presenting view controller is the destination, following that we still need to add presented controller view to containerViewCalculable
Hi @DmytroAntonchenko ! What you say makes sense. I just don't know; I've never seen the problem exhibit doing it the "usual" way. (Could it relate to Note: strangely, you only have to do this in the "first" view controller. (The one which is "underneath".) With the one that you pop on top, do nothing. Easy. - IDK) Recall the presenting VC view is always there underneath. Unfortunately i just don't have to time to investigate the effects one way or the other :/ the info is there for anyone who need it!Littrell
A
30

for push

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;

[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:ViewControllerYouWantToPush animated:NO];

for pop

CATransition *transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
//transition.subtype = kCATransitionFromTop;

[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];
Aggie answered 23/6, 2014 at 3:41 Comment(0)
H
21

Remember that in Swift, extension are definitely your friends!

public extension UINavigationController {

    /**
     Pop current view controller to previous view controller.

     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func pop(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewControllerAnimated(false)
    }

    /**
     Push a new view controller on the view controllers's stack.

     - parameter vc:       view controller to push.
     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func push(viewController vc: UIViewController, transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: String = kCATransitionFade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        transition.type = type
        self.view.layer.addAnimation(transition, forKey: nil)
    }

}
Hintze answered 5/4, 2016 at 14:14 Comment(0)
B
19

@Magnus answer, only then for Swift (2.0)

    let transition = CATransition()
    transition.duration = 0.5
    transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    transition.type = kCATransitionPush
    transition.subtype = kCATransitionFromTop
    self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
    let writeView : WriteViewController = self.storyboard?.instantiateViewControllerWithIdentifier("WriteView") as! WriteViewController
    self.navigationController?.pushViewController(writeView, animated: false)

Some sidenotes:

You can do this as well with Segue, just implement this in prepareForSegue or shouldPerformSegueWithIdentifier. However, this will keep the default animation in it as well. To fix this you have to go to the storyboard, click the Segue, and uncheck the box 'Animates'. But this will limit your app for IOS 9.0 and above (atleast when I did it in Xcode 7).

When doing in a segue, the last two lines should be replaced with:

self.navigationController?.popViewControllerAnimated(false)

Even though I set false, it kind of ignores it.

Banker answered 14/7, 2015 at 11:41 Comment(2)
How to remove the the black color in background at the end of animation.Chessa
Not working for push view controller animation works for Pop view controllerGideon
T
11

Using private calls is a bad idea as Apple no longer approve apps that do that. Maybe you could try this:

//Init Animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.50];


[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.navigationController.view cache:YES];

//Create ViewController
MyViewController *myVC = [[MyViewController alloc] initWith...];

[self.navigationController pushViewController:myVC animated:NO];
[myVC release];

//Start Animation
[UIView commitAnimations];
Tumble answered 27/3, 2010 at 23:3 Comment(4)
It only "half" works - it won't solve the harder problem of pop animations.Adnopoz
I like this solution better, and yes it works. Using private methods will get you rejected for sure.Wayless
@Tumble which is the private api call. I didn't notice any.Waterless
@Waterless there was a discussion here a while ago around using -pushViewController:transition:forceImmediate: which would be a bad idea.Tumble
F
10

Since this is the top result on Google I thought I'd share what I think is the most sane way; which is to use the iOS 7+ transitioning API. I implemented this for iOS 10 with Swift 3.

It's pretty simple to combine this with how UINavigationController animates between two view controllers if you create a subclass of UINavigationController and return an instance of a class that conforms to the UIViewControllerAnimatedTransitioning protocol.

For example here is my UINavigationController subclass:

class NavigationController: UINavigationController {
    init() {
        super.init(nibName: nil, bundle: nil)

        delegate = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension NavigationController: UINavigationControllerDelegate {

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

}

You can see that I set the UINavigationControllerDelegate to itself, and in an extension on my subclass I implement the method in UINavigationControllerDelegate that allows you to return a custom animation controller (i.e., NavigationControllerAnimation). This custom animation controller will replace the stock animation for you.

You're probably wondering why I pass in the operation to the NavigationControllerAnimation instance via its initializer. I do this so that in NavigationControllerAnimation's implementation of the UIViewControllerAnimatedTransitioning protocol I know what the operation is (i.e., 'push' or 'pop'). This helps to know what kind of animation I should do. Most of the time, you want to perform a different animation depending on the operation.

The rest is pretty standard. Implement the two required functions in the UIViewControllerAnimatedTransitioning protocol and animate however you like:

class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {

    let operation: UINavigationControllerOperation

    init(operation: UINavigationControllerOperation) {
        self.operation = operation

        super.init()
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }

    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
        let containerView = transitionContext.containerView

        if operation == .push {
            // do your animation for push
        } else if operation == .pop {
            // do your animation for pop
        }
    }
}

It's important to remember, that for each different type of operation (i.e., 'push' or 'pop), the to and from view controllers will be different. When you are in a push operation, the to view controller will be the one being pushed. When you are in a pop operation, the to view controller will be the one that is being transitioned to, and the from view controller will be the one that's being popped.

Also, the to view controller must be added as a subview of the containerView in the transition context.

When your animation completes, you must call transitionContext.completeTransition(true). If you are doing an interactive transition, you will have to dynamically return a Bool to completeTransition(didComplete: Bool), depending on if the transition is complete at the end of the animation.

Finally (optional reading), you might want to see how I did the transition I was working on. This code is a bit more hacky and I wrote it pretty quickly so I wouldn't say it's great animation code but it still shows how to do the animation part.

Mine was a really simple transition; I wanted to mimic the same animation that UINavigationController typically does, but instead of the 'next page over the top' animation it does, I wanted to implement a 1:1 animation of the old view controller away at the same time as the new view controller appears. This has the effect of making the two view controllers seem as though they are pinned to each other.

For the push operation, that requires first setting the toViewController's view origin on the x–axis off screen, adding it as the subview of the containerView, animating it onto screen by setting that origin.x to zero. At the same time, I animate the fromViewController's view away by setting its origin.x off the screen:

toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)

containerView.addSubview(toViewController.view)

UIView.animate(withDuration: transitionDuration(using: transitionContext),
               delay: 0,
               options: [ UIViewAnimationOptions.curveEaseOut ],
               animations: {
                toViewController.view.frame = containerView.bounds
                fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
},
               completion: { (finished) in
                transitionContext.completeTransition(true)
})

The pop operation is basically the inverse. Add the toViewController as a subview of the containerView, and animate away the fromViewController to the right as you animate in the toViewController from the left:

containerView.addSubview(toViewController.view)

UIView.animate(withDuration: transitionDuration(using: transitionContext),
               delay: 0,
               options: [ UIViewAnimationOptions.curveEaseOut ],
               animations: {
                fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
                toViewController.view.frame = containerView.bounds
},
               completion: { (finished) in
                transitionContext.completeTransition(true)
})

Here's a gist with the whole swift file:

https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be

Floorwalker answered 8/1, 2017 at 0:12 Comment(2)
Great!. What I wanted to do is JUST to animate in opposite direction. I examined some other solutions, but all exhibit flashing at left and right screen. It looks like implicit alpha change animation can't be removed with them. Only this solution rectified the issue.Boucicault
@AlanZeino What if inside the same ViewController you need to have diferente Animations for different button click? So, for button1 you need a dissolve animation, for button2 you need the default transition.Fletcher
M
10

It's very simple

self.navigationController?.view.semanticContentAttribute = .forceRightToLeft
Mainstay answered 16/2, 2018 at 9:0 Comment(2)
Welcome to StackOverflow: if you post code, XML or data samples, please highlight those lines in the text editor and click on the "code samples" button ( { } ) on the editor toolbar or using Ctrl+K on your keyboard to nicely format and syntax highlight it!Aborning
This is a great way to do it!Wedgwood
A
9

Based on jordanperry answer updated for swift 4

For push UIViewController

let yourVC = self.storyboard?.instantiateViewController(withIdentifier: "yourViewController") as! yourViewController
    UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    self.navigationController?.pushViewController(terms, animated: true)
    UIView.setAnimationTransition(.flipFromRight, for: (self.navigationController?.view)!, cache: false)
})

For Pop

UIView.animate(withDuration: 0.75, animations: {() -> Void in
    UIView.setAnimationCurve(.easeInOut)
    UIView.setAnimationTransition(.flipFromLeft, for: (self.navigationController?.view)!, cache: false)
})
navigationController?.popViewController(animated: false)
Assurgent answered 26/3, 2018 at 9:2 Comment(0)
E
8

@Luca Davanzo's answer in Swift 4.2

public extension UINavigationController {

    /**
     Pop current view controller to previous view controller.

     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func pop(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.popViewController(animated: false)
    }

    /**
     Push a new view controller on the view controllers's stack.

     - parameter vc:       view controller to push.
     - parameter type:     transition animation type.
     - parameter duration: transition animation duration.
     */
    func push(viewController vc: UIViewController, transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        self.addTransition(transitionType: type, duration: duration)
        self.pushViewController(vc, animated: false)
    }

    private func addTransition(transitionType type: CATransitionType = .fade, duration: CFTimeInterval = 0.3) {
        let transition = CATransition()
        transition.duration = duration
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        transition.type = type
        self.view.layer.add(transition, forKey: nil)
    }

}
Eldrid answered 8/2, 2019 at 13:4 Comment(0)
H
7

There are UINavigationControllerDelegate and UIViewControllerAnimatedTransitioning there you can change animation for anything you want.

For example this is vertical pop animation for VC:

@objc class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return 0.5
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let containerView = transitionContext.containerView()
    let bounds = UIScreen.mainScreen().bounds
    containerView!.insertSubview(toViewController.view, belowSubview: fromViewController.view)
    toViewController.view.alpha = 0.5

    let finalFrameForVC = fromViewController.view.frame

    UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
        fromViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.height)
        toViewController.view.alpha = 1.0
        }, completion: {
            finished in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
    })
}

}

And then

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    if operation == .Pop {
        return PopAnimator()
    }
    return nil;
}

Useful tutorial https://www.objc.io/issues/5-ios7/view-controller-transitions/

Hollishollister answered 30/12, 2015 at 15:5 Comment(0)
G
6

You can now use UIView.transition. Note that animated:false. This works with any transition option, pop, push, or stack replace.

if let nav = self.navigationController
{
    UIView.transition(with:nav.view, duration:0.3, options:.transitionCrossDissolve, animations: {
        _ = nav.popViewController(animated:false)
    }, completion:nil)
}
Galimatias answered 27/4, 2017 at 12:18 Comment(1)
@Fattie, this particular method only works with any of the standard animations such as flips and curls listed in developer.apple.com/documentation/uikit/uiviewanimationoptionsGalimatias
P
5

Here is how I have done the same in Swift:

For Push:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        self.navigationController!.pushViewController(nextView, animated: false)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromRight, forView: self.navigationController!.view!, cache: false)
    })

For Pop:

I actually did this a little differently to some of the responses above - but as I am new to Swift development, it might not be right. I have overridden viewWillDisappear:animated: and added the pop code in there:

    UIView.animateWithDuration(0.75, animations: { () -> Void in
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseInOut)
        UIView.setAnimationTransition(UIViewAnimationTransition.FlipFromLeft, forView: self.navigationController!.view, cache: false)
    })

    super.viewWillDisappear(animated)
Pedaias answered 20/1, 2015 at 10:52 Comment(0)
S
4

I recently was trying to do something similar. I decided I didn't like the sliding animation of the UINavigationController, but I also didn't want to do the animations that UIView gives you like curl or anything like that. I wanted to do a cross fade between the views when I push or pop.

The problem there involves the fact that the view is literally removing the view or popping one over the top of the current one, so a fade doesn't work. The solution I came to involved taking my new view and adding it as a subview to the current top view on the UIViewController's stack. I add it with an alpha of 0, then do a crossfade. When the animation sequence finishes, I push the view onto the stack without animating it. I then go back to the old topView and clean up stuff that I had changed.

Its a little more complicated than that, because you have the navigationItems you have to adjust to make the transition look correct. Also, if you do any rotation, you then have to adjust frame sizes as you add the views as subviews so they show up correctly on screen. Here is some of the code I used. I subclassed the UINavigationController and overrode the push and the pop methods.

-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
      UIViewController *currentViewController = [self.viewControllers lastObject];
      //if we don't have a current controller, we just do a normal push
      if(currentViewController == nil)
      {
         [super pushViewController:viewController animated:animated];
         return;
      }
      //if no animation was requested, we can skip the cross fade
      if(!animation)
      {
         [super pushViewController:viewController animated:NO];
         return;
      }
      //start the cross fade.  This is a tricky thing.  We basically add the new view
//as a subview of the current view, and do a cross fade through alpha values.
//then we push the new view on the stack without animating it, so it seemlessly is there.
//Finally we remove the new view that was added as a subview to the current view.

viewController.view.alpha = 0.0;
//we need to hold onto this value, we'll be releasing it later
    NSString *title = [currentViewController.title retain];

//add the view as a subview of the current view
[currentViewController.view addSubview:viewController.view];
[currentViewController.view bringSubviewToFront:viewController.view];
UIBarButtonItem *rButtonItem = currentViewController.navigationItem.rightBarButtonItem;
UIBarButtonItem *lButtonItem = currentViewController.navigationItem.leftBarButtonItem;

NSArray *array = nil;

//if we have a right bar button, we need to add it to the array, if not, we will crash when we try and assign it
//so leave it out of the array we are creating to pass as the context.  I always have a left bar button, so I'm not checking to see if it is nil. Its a little sloppy, but you may want to be checking for the left BarButtonItem as well.
if(rButtonItem != nil)
    array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,rButtonItem,nil];
else {
    array = [[NSArray alloc] initWithObjects:currentViewController,viewController,title,lButtonItem,nil];
}

//remove the right bar button for our transition
[currentViewController.navigationItem setRightBarButtonItem:nil animated:YES];
//remove the left bar button and create a backbarbutton looking item
//[currentViewController.navigationItem setLeftBarButtonItem:nil animated:NO];

//set the back button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:title style:kButtonStyle target:self action:@selector(goBack)];
[currentViewController.navigationItem setLeftBarButtonItem:backButton animated:YES];
[viewController.navigationItem setLeftBarButtonItem:backButton animated:NO];
[backButton release];

[currentViewController setTitle:viewController.title];

[UIView beginAnimations:@"push view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePushDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[viewController.view setAlpha: 1.0];
[UIView commitAnimations];
}

-(void)animationForCrossFadePushDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{

UIViewController *c = [(NSArray*)context objectAtIndex:0];
UIViewController *n = [(NSArray*)context objectAtIndex:1];
NSString *title     = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:3];
UIBarButtonItem *r = nil;
//not all views have a right bar button, if we look for it and it isn't in the context,
//we'll crash out and not complete the method, but the program won't crash.
//So, we need to check if it is there and skip it if it isn't.
if([(NSArray *)context count] == 5)
    r = [(NSArray *)context objectAtIndex:4];

//Take the new view away from being a subview of the current view so when we go back to it
//it won't be there anymore.
[[[c.view subviews] lastObject] removeFromSuperview];
[c setTitle:title];
[title release];
//set the search button
[c.navigationItem setLeftBarButtonItem:l animated:NO];
//set the next button
if(r != nil)
    [c.navigationItem setRightBarButtonItem:r animated:NO];


[super pushViewController:n animated:NO];

 }

As I mention in the code, I always have a left bar button item, so I don't check to see if it is nil before putting it in the array that I pass as the context for the animation delegate. If you do this, you may want to make that check.

The problem I found was that if you crash at all in the delegate method, it won't crash the program. It just stops the delegate from completing but you don't get any kind of warning.
So since I was doing my cleanup in that delegate routine, it was causing some weird visual behavior since it wasn't finishing the cleanup.

The back button I create calls a "goBack" method, and that method just calls the pop routine.

-(void)goBack
{ 
     [self popViewControllerAnimated:YES];
}

Also, here is my pop routine.

-(UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    //get the count for the number of viewControllers on the stack
int viewCount = [[self viewControllers] count];
//get the top view controller on the stack
UIViewController *topViewController = [self.viewControllers objectAtIndex:viewCount - 1];
//get the next viewController after the top one (this will be the new top one)
UIViewController *newTopViewController = [self.viewControllers objectAtIndex:viewCount - 2];

//if no animation was requested, we can skip the cross fade
if(!animated)
{
    [super popViewControllerAnimated:NO];
            return topViewController;
}



//start of the cross fade pop.  A bit tricky.  We need to add the new top controller
//as a subview of the curent view controler with an alpha of 0.  We then do a cross fade.
//After that we pop the view controller off the stack without animating it.
//Then the cleanup happens: if the view that was popped is not released, then we
//need to remove the subview we added and change some titles back.
newTopViewController.view.alpha = 0.0;
[topViewController.view addSubview:newTopViewController.view];
[topViewController.view bringSubviewToFront:newTopViewController.view];
NSString *title = [topViewController.title retain];
UIBarButtonItem *lButtonItem = topViewController.navigationItem.leftBarButtonItem;
UIBarButtonItem *rButtonItem = topViewController.navigationItem.rightBarButtonItem;

//set the new buttons on top of the current controller from the new top controller
if(newTopViewController.navigationItem.leftBarButtonItem != nil)
{
    [topViewController.navigationItem setLeftBarButtonItem:newTopViewController.navigationItem.leftBarButtonItem animated:YES];
}
if(newTopViewController.navigationItem.rightBarButtonItem != nil)
{
    [topViewController.navigationItem setRightBarButtonItem:newTopViewController.navigationItem.rightBarButtonItem animated:YES];
}

[topViewController setTitle:newTopViewController.title];
//[topViewController.navigationItem.leftBarButtonItem setTitle:newTopViewController.navigationItem.leftBarButtonItem.title];

NSArray *array = nil;
if(rButtonItem != nil)
    array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,rButtonItem,nil];
else {
    array = [[NSArray alloc] initWithObjects:topViewController,title,lButtonItem,nil];
}


[UIView beginAnimations:@"pop view" context:array];
[UIView setAnimationDidStopSelector:@selector(animationForCrossFadePopDidStop:finished:context:)];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.80];
[newTopViewController.view setAlpha: 1.0];
[UIView commitAnimations];
return topViewController;

 }

 -(void)animationForCrossFadePopDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
 {

UIViewController *c = [(NSArray *)context objectAtIndex:0];
//UIViewController *n = [(NSArray *)context objectAtIndex:1];
NSString *title = [(NSArray *)context objectAtIndex:1];
UIBarButtonItem *l = [(NSArray *)context objectAtIndex:2];
UIBarButtonItem *r = nil;



//Not all views have a right bar button.  If we look for one that isn't there
// we'll crash out and not complete this method, but the program will continue.
//So we need to check if it is therea nd skip it if it isn't.
if([(NSArray *)context count] == 4)
    r = [(NSArray *)context objectAtIndex:3];

//pop the current view from the stack without animation
[super popViewControllerAnimated:NO];

//if what was the current veiw controller is not nil, then lets correct the changes
//we made to it.
if(c != nil)
{
    //remove the subview we added for the transition
    [[c.view.subviews lastObject] removeFromSuperview];
    //reset the title we changed
    c.title = title;
    [title release];
    //replace the left bar button that we changed
    [c.navigationItem setLeftBarButtonItem:l animated:NO];
    //if we were passed a right bar button item, replace that one as well
    if(r != nil)
        [c.navigationItem setRightBarButtonItem:r animated:NO];
    else {
        [c.navigationItem setRightBarButtonItem:nil animated:NO];
    }


 }
}

That's pretty much it. You'll need some additional code if you want to implement rotations. You'll need to set the frame size of your views that you add as subviews before you show them otherwise you'll run into issues the orientation is landscape, but the last time you saw the previous view it was portrait. So, then you add it as a sub view and fade it in but it shows up as portrait, then when we pop without animation, the same view, but the one that is in the stack, now is landscape. The whole thing looks a little funky. Everyone's implementation of rotation is a little different so I didn't include my code for that here.

Hope it helps some people. I've looked all over for something like this and couldn't find anything. I don't think this is the perfect answer, but it is working real well for me at this point.

Scutage answered 25/3, 2011 at 18:19 Comment(2)
Whilst admirable, this is honestly not the solution now 7 yrs later!Littrell
You are right. This answer was from 2011. It worked back then, but things have changed a lot since then. =)Scutage
A
3

Using iJordan's answer as inspiration, why not simply create a Category on UINavigationController to use throughout your app instead of copying/pasting this animation code all over the place?

UINavigationController+Animation.h

@interface UINavigationController (Animation)

- (void) pushViewControllerWithFlip:(UIViewController*) controller;

- (void) popViewControllerWithFlip;

@end

UINavigationController+Animation.m

@implementation UINavigationController (Animation)

- (void) pushViewControllerWithFlip:(UIViewController *) controller
{
    [UIView animateWithDuration:0.50
                     animations:^{
                         [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                         [self pushViewController:controller animated:NO];
                         [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
                     }];
}

- (void) popViewControllerWithFlip
{
    [UIView animateWithDuration:0.5
                     animations:^{
                         [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
                         [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];
                     }];

    [self popViewControllerAnimated:NO];
}

@end

Then simply import the UINavigationController+Animation.h file and call it normally:

[self.navigationController pushViewControllerWithFlip:[[NewViewController alloc] init]];

[self.navigationController popViewControllerWithFlip];
Apologize answered 11/11, 2013 at 16:16 Comment(3)
Clever. But why not add push/pop methods which take an argument of UIViewAnimationTransition rather than hardcode to flipFromRight ?Bacchus
@Bacchus these are convenience methods - this way, the implementor doesn't need to remember which UIViewAnimationTransition value to pass in for each specific animation type, they just call the method with the "english" name of what they want to accomplish.Apologize
@Bacchus also, your suggestion is definitely valid - if I were still using objective-c and needed to support many transition styles (definitely not recommended as many different transition styles will confuse users) I would have 1 method that takes the UIViewAnimationTransition type, then several convenience methods to make developing easier.Apologize
S
2

While all the answers here are great and most work very well, there is a slightly simpler method which achieves the same effect...

For Push:

  NextViewController *nextViewController = [[NextViewController alloc] init];

  // Shift the view to take the status bar into account 
  CGRect frame = nextViewController.view.frame;
  frame.origin.y -= 20;
  frame.size.height += 20;
  nextViewController.view.frame = frame;

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextViewController.view duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight completion:^(BOOL finished) {
    [self.navigationController pushViewController:nextViewController animated:NO];
  }];

For Pop:

  int numViewControllers = self.navigationController.viewControllers.count;
  UIView *nextView = [[self.navigationController.viewControllers objectAtIndex:numViewControllers - 2] view];

  [UIView transitionFromView:self.navigationController.topViewController.view toView:nextView duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
    [self.navigationController popViewControllerAnimated:NO];
  }];}
Soddy answered 8/2, 2013 at 19:31 Comment(1)
This will crash when poping to root view controller.Dorcas
R
2

Have a look at ADTransitionController, a drop in replacement for UINavigationController with custom transition animations (its API matches the API of UINavigationController) that we created at Applidium.

You can use different pre-defined animations for push and pop actions such as Swipe, Fade, Cube, Carrousel, Zoom and so on.

Residuary answered 17/7, 2013 at 13:58 Comment(0)
T
1

I am not aware of any way you can change the transition animation publicly.

If the "back" button is not necessary you should use modal view controllers to have the "push from bottom" / "flip" / "fade" / (≥3.2)"page curl" transitions.


On the private side, the method -pushViewController:animated: calls the undocumented method -pushViewController:transition:forceImmediate:, so e.g. if you want a flip-from-left-to-right transition, you can use

[navCtrler pushViewController:ctrler transition:10 forceImmediate:NO];

You can't change the "pop" transition this way, however.

Thigmotaxis answered 7/2, 2010 at 7:4 Comment(0)
P
1

See my answer to this question for a way to do it in far fewer lines of code. This method allows you to animate a pseudo-"Push" of a new view controller any way you like, and when the animation is done it sets up the Navigation Controller just as if you had used the standard Push method. My example lets you animate either a slide-in from the left or from the right. Code repeated here for convenience:

-(void) showVC:(UIViewController *) nextVC rightToLeft:(BOOL) rightToLeft {
    [self addChildViewController:neighbor];
    CGRect offscreenFrame = self.view.frame;
    if(rightToLeft) {
        offscreenFrame.origin.x = offscreenFrame.size.width * -1.0;
    } else if(direction == MyClimbDirectionRight) {
        offscreenFrame.origin.x = offscreenFrame.size.width;
    }
    [[neighbor view] setFrame:offscreenFrame];
    [self.view addSubview:[neighbor view]];
    [neighbor didMoveToParentViewController:self];
    [UIView animateWithDuration:0.5 animations:^{
        [[neighbor view] setFrame:self.view.frame];
    } completion:^(BOOL finished){
        [neighbor willMoveToParentViewController:nil];
        [neighbor.view removeFromSuperview];
        [neighbor removeFromParentViewController];
        [[self navigationController] pushViewController:neighbor animated:NO];
        NSMutableArray *newStack = [[[self navigationController] viewControllers] mutableCopy];
        [newStack removeObjectAtIndex:1]; //self, just below top
        [[self navigationController] setViewControllers:newStack];
    }];
}
Papeterie answered 9/6, 2013 at 14:59 Comment(0)
B
0

From the sample app, check out this variation. https://github.com/mpospese/MPFoldTransition/

#pragma mark - UINavigationController(MPFoldTransition)

@implementation UINavigationController(MPFoldTransition)

//- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
- (void)pushViewController:(UIViewController *)viewController foldStyle:(MPFoldStyle)style
{
    [MPFoldTransition transitionFromViewController:[self visibleViewController] 
                                  toViewController:viewController 
                                          duration:[MPFoldTransition defaultDuration]  
                                             style:style 
                                        completion:^(BOOL finished) {
                                            [self pushViewController:viewController animated:NO];
                                        }
     ];
}

- (UIViewController *)popViewControllerWithFoldStyle:(MPFoldStyle)style
{
    UIViewController *toController = [[self viewControllers] objectAtIndex:[[self viewControllers] count] - 2];

    [MPFoldTransition transitionFromViewController:[self visibleViewController] 
                                  toViewController:toController 
                                          duration:[MPFoldTransition defaultDuration] 
                                             style:style
                                        completion:^(BOOL finished) {
                                            [self popViewControllerAnimated:NO];
                                        }
     ];

    return toController;
}
Braasch answered 13/7, 2012 at 13:8 Comment(0)
L
0

Just use:

ViewController *viewController = [[ViewController alloc] init];

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.navigationBarHidden = YES;

[self presentViewController:navController animated:YES completion: nil];
[viewController release];
[navController release];
Lunneta answered 23/4, 2013 at 15:43 Comment(0)
P
0

Realising this is an old question. I still would like to post this answer, as I had some problems popping several viewControllers with the proposed answers. My solution is to subclass UINavigationController and override all the pop and push methods.

FlippingNavigationController.h

@interface FlippingNavigationController : UINavigationController

@end

FlippingNavigationController.m:

#import "FlippingNavigationController.h"

#define FLIP_DURATION 0.5

@implementation FlippingNavigationController

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [UIView transitionWithView:self.view
                      duration:animated?FLIP_DURATION:0
                       options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromRight
                    animations:^{ [super pushViewController:viewController
                                                   animated:NO]; }
                    completion:nil];
}

- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    return [[self popToViewController:[self.viewControllers[self.viewControllers.count - 2]]
                             animated:animated] lastObject];
}

- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
    return [self popToViewController:[self.viewControllers firstObject]
                            animated:animated];
}

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    __block NSArray* viewControllers = nil;

    [UIView transitionWithView:self.view
                      duration:animated?FLIP_DURATION:0
                       options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionFlipFromLeft
                    animations:^{ viewControllers = [super popToViewController:viewController animated:NO]; }
                    completion:nil];

    return viewControllers;
}

@end
Penultimate answered 28/10, 2013 at 7:41 Comment(0)
B
0

I know this thread is old, but I thought I'd put in my two cents. You don't need to make a custom animation, there's a simple (maybe hacky) way of doing it. Instead of using push, create a new navigation controller, make the new view controller the root view controller of that nav controller, and then present the nav controller from the original nav controller. Present is easily customizable with many styles, and no need to make a custom animation.

For example:

UIViewcontroller viewControllerYouWantToPush = UIViewController()
UINavigationController newNavController = UINavigationController(root: viewControllerYouWantToView)
newNavController.navBarHidden = YES;
self.navigationController.present(newNavController)

And you can change the presentation style however you want.

Borders answered 24/6, 2019 at 20:3 Comment(1)
note: swipe to back gesture will not work if used present.Waylay
M
-1

I found a mildly recursive way to do this that works for my purposes. I have an instance variable BOOL that I use to block the normal popping animation and substitute my own non-animated pop message. The variable is initially set to NO. When the back button is tapped, the delegate method sets it to YES and sends a new non-animated pop message to the nav bar, thereby calling the same delegate method again, this time with the variable set to YES. With the variable is set to YES, the delegate method sets it to NO and returns YES to allow the non-animated pop occur. After the second delegate call returns, we end up back in the first one, where NO is returned, blocking the original animated pop! It's actually not as messy as it sounds. My shouldPopItem method looks like this:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item 
{
    if ([[navigationBar items] indexOfObject:item] == 1) 
    {
        [expandedStack restack];    
    }

    if (!progPop) 
    {
        progPop = YES;
        [navBar popNavigationItemAnimated:NO];
        return NO;
    }
    else 
    {
        progPop = NO;
        return YES;
    }
}

Works for me.

Marijn answered 13/8, 2010 at 3:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.