In this tutorial, I would like to show you how you can use Custom Transitions to implement beautiful animated navigations in iOS. The gif below will explain what we plan to achieve. transition-gif So we are going to begin with a Starter project which you can download here. This consists of two View Controllers. The first one is the Timeline View and the other is a Navigation View. This simulates the scenario of having to open up a Menu/Navigation from a View with your data. This designs are taken from our Mega App Design Kit. If you like how these screens look, you should click here to check it out. There are loads more screens included in the Kit.

Create A Transition between two View Controllers

Open up the project and navigate to the Storyboard. You will see two View Controllers. WE want to create the transition between both. Ctrl+Drag from the TimelineViewController to the NavigationViewController to create a segue. Choose the “Custom” option. image-1 This creates a segue we can now call in code to perform the transition. Click on the actual segue in the storyboard to bring up the attributes of the segue. In the identifier box, type in “presentNav” image-2 So, let’s make this call. Open up the TimelineViewController.swift file, and add the following method.

1
2
3
@IBAction func presentNavigation(sender: AnyObject?){
performSegueWithIdentifier("presentNav", sender: self)
}

This gives us an entry to call the segue. We will customize it in a minute. Now, we would like to hook up the menu button in the navigation bar so calls this method when tapped. Right-Click on the TimelineViewController in the storyboard and Ctrl-Drag from the menu button item to the presentNavigaton method, choose Touch-up inside as the interaction method. Now you should be able to run the app. If you do exactly that, then tap on the menu button, you will see the Navigation View popping up in a normal modal transition. What we want to achieve is a much more interesting transition. So let’s get to that.

Creating A Better Transition

Add a new file to the project. Call it TransitionOperator and add the following piece of code which I will explain in a little bit.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
import UIKit
 
class TransitionOperator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate{
 
var snapshot : UIView!
 
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.5
}
 
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
 
}
 
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
 
return self
}
 
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
 
return self
}
}

This is the class that will manage the animation going on during the transition. The class derives from UIViewControllerAnimatedTransitioning and UIViewControllerTransitioningDelegate to handle the methods necessary. The transition duration is set to 0.5 which is the duration in secs for how long the transition will take to complete. In this case, half a second. The two methods animationControllerForPresentedController and animationControllerForDismissedController specifies our new TransitionOperator class and the controller responsible for the animations. The snapshot view will be needed to show a snapshot of current view controller during the animation. Now it’s time to work on the animateTransition method, this is where all the magic happens, Add the following bit of code to replace the animateTransition method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let fromView = fromViewController!.view
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let toView = toViewController!.view
 
let size = toView.frame.size
var offSetTransform = CGAffineTransformMakeTranslation(size.width - 120, 0)
offSetTransform = CGAffineTransformScale(offSetTransform, 0.6, 0.6)
 
snapshot = fromView.snapshotViewAfterScreenUpdates(true)
 
container.addSubview(toView)
container.addSubview(snapshot)
 
let duration = self.transitionDuration(transitionContext)
 
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: nil, animations: {
 
self.snapshot.transform = offSetTransform
 
}, completion: { finished in
 
transitionContext.completeTransition(true)
})
 
}

This bit of code does the following, 1) Get the views from the context. These will be the TimeLineViewController and the NavigationViewController. 2) Create a transform object by translating and reverse-scaling the item to the pushed. 3) Create a snapshot of the current Timeline View, to display in the context container. 4) Add the snapshot and the NavigationViewController’s view (toView) to the container context and then perform the animations. Time to hook up this class into our transition. Go back to TimelineViewController and add a variable to the top of the class and initialize it.

1
var transitionOperator = TransitionOperator()

Then add the prepareforsegue method below to the same file

1
2
3
4
5
6
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
 
let toViewController = segue.destinationViewController as UIViewController
self.modalPresentationStyle = UIModalPresentationStyle.Custom
toViewController.transitioningDelegate = self.transitionOperator
}

This makes sure we use the transition operator as our new Transitioning Delegate. Now if you run the app in the simulator, tap on the menu icon, you will get a nice retreating animation to show the Navigation. Creating the Reverse Animation Tapping on any item in the table will bring back the TimelineViewController but the animation is not correct. Let’s do some refactoring before we move on. Add a new instance variable in the TransitionOperator class.

1
2
3
4
5
6
class TransitionOperator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate{
 
var snapshot : UIView!
var isPresenting : Bool = true
 
.....

Then change the name of the animateTransition method presentNavigation and now we will have a new animateTransition method that calls it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
if isPresenting{
presentNavigation(transitionContext)
}
}
 
//This is the old animate transition method
func presentNavigation(transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let fromView = fromViewController!.view
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let toView = toViewController!.view
 
....
}

As you can see, we are calling the presentNavigation method (formerly called animateTransition) when we are presenting the new view. And when we dismiss the view, we want to create a new method to call. Let’s name it dismissNavigation, see it below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func dismissNavigation(transitionContext: UIViewControllerContextTransitioning) {
 
let container = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let fromView = fromViewController!.view
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let toView = toViewController!.view
 
let duration = self.transitionDuration(transitionContext)
 
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: nil, animations: {
 
self.snapshot.transform = CGAffineTransformIdentity
 
}, completion: { finished in
transitionContext.completeTransition(true)
self.snapshot.removeFromSuperview()
})
}

It is very similar to the presentNavigation method, this time we are animating the snapshot (which if you remember was made from the TimelineViewController) back to the main screen where it was. We now call this new method in our animateTransition method when we are dismissing the NavigationViewController.

1
2
3
4
5
6
7
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
if isPresenting{
presentNavigation(transitionContext)
}else{
dismissNavigation(transitionContext)
}
}

We are almost there… We just need to let the TransitionOperator know when we are in the presentation mode or in the dismissal mode. Our class conforms to the UIViewControllerTransitioningDelegate which provides these two methods.

1
2
3
4
5
6
7
8
9
10
11
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
 
self.isPresenting = true
return self
}
 
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
 
self.isPresenting = false
return self
}

This asks for the animationController in both scenarios. So when the transition delegate asks for the animationControllerForPresentedController, we set the isPresenting value to true and false when it gets asked for the animationControllerForDismissedController. Now you can run the project in the simulator/device. Tap on the menu button, and see the Navigation come into view. Tap on any item in the table, and see the TimelineController come back into view.

What’s Next?

UPDATE: In the comments, some people noted that it was an issue implementing the code to open multiple ViewControllers. I have updated that in the final Xcode project and  you can download the final project here.

Also, don’t forget to check out the Mega – App Design Kit for more amazing designs like the ones you see here. A quick homework for you:-)… We are always going back to the same View Controller, extend the project to load a new View Controller depending on what item we tap on in the Navigation list. See the project above for the update

25 comments

  1. Vaugen Wakeling

    Hi Tope,

    This is a great effect and very well explained thank for taking the time to make a tutorial for us 😉

    Keep up the good work everyone appreciates it.

  2. Josh H.

    Nice tutorial! Does this only work for transitioning from VC to VC? My starting VC is inside a UINavigationController and wish to reveal a UITableViewController.

    • richarddwalsh

      @Josh H.

      I modified mine to do exactly what you are referring to, instead of using custom segue in the storyboard I chose Present Modally and left all the settings as they are. And it worked.

      @Tope, one item I do see an issue with is landscape modes. The view snapshot shoots off the view.

      • Tope

        Hi Richard, thanks for the heads up. I can see the issue in landscape too. I think the animation transform needs to change and take the screen bounds into consideration.

        The place to look at is the TransitionOperator.swift file..

  3. Ivan

    Problem with this approach is that menu is on a lower hierarchy then it’s controllers.
    If we look at UITabBarController it is a root controller to all subcontrollers.

    One thing you can do is to invoke NavigationViewController from a UINavigationController and then replace it’s rootviewcontroller on menu item tap.

  4. Estelle

    Thank you so much for this tutorial. It has been very enlightening. I do have one question however. The project has a warning: Unsupported Configuration – Custom segues must have a custom class. Do you know how to write this custom class in swift? I have found Objective C answer for the question. But alas, I’m so new to Swift, I just can’t seem to translate it to Swift successfully.

    • kallithesock

      You can create some default class that mainly does nothing but a standard switch from source to destination and then define this default class in your storyboard:

      import UIKit

      //
      // A default segue class that does not do anything important.
      // It’s mainly necessary to get rid of the warning that a custom segue needs a custom segue class.
      //
      class DefaultSegue: UIStoryboardSegue {

      override func perform() {

      var sourceViewController: UIViewController = self.sourceViewController as UIViewController
      var destinationViewController: UIViewController = self.destinationViewController as UIViewController
      sourceViewController.view.addSubview(destinationViewController.view)
      destinationViewController.view.removeFromSuperview()

      // Force presentViewController() into a different runloop.
      let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC)))
      dispatch_after(time, dispatch_get_main_queue()) {
      sourceViewController.presentViewController(destinationViewController, animated: true, completion: nil)
      }
      }
      }

  5. Mark

    I get this error when i run the project straight form the box:

    2014-12-06 11:41:08.249 NavTransition[1594:60b] -[NavTransition.TimelineViewController tableView:heightForRowAtIndexPath:]: unrecognized selector sent to instance 0x176abd60
    2014-12-06 11:41:08.255 NavTransition[1594:60b] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[NavTransition.TimelineViewController tableView:heightForRowAtIndexPath:]: unrecognized selector sent to instance 0x176abd60’
    *** First throw call stack:
    (0x2e0defd3 0x38957ccf 0x2e0e2967 0x2e0e10f1 0x2e0307b8 0x30a2d5fb 0x30a2d4fd 0x30a2d39f 0x30a2cf5f 0x309d5357 0x309d5259 0x309d472d 0x309d449d 0x308fad79 0x3057862b 0x30573e3b 0x305a2cdd 0x30977183 0x309757a3 0x309749f3 0x3097497b 0x30974913 0x3096cf89 0x30901127 0x30974661 0x30974125 0x30906065 0x30903847 0x3096d35d 0x30969fcd 0x3096458b 0x30900709 0x308ff871 0x30963cc9 0x32f6caed 0x32f6c6d7 0x2e0a9ab7 0x2e0a9a53 0x2e0a8227 0x2e012f0f 0x2e012cf3 0x30962ef1 0x3095e16d 0xedeb4 0xedef0 0x38e64ab7)
    libc++abi.dylib: terminating with uncaught exception of type NSException
    (lldb)

  6. Frankie

    How do I add multiple view controllers and make the navigation take me to the appropriate view controller. Newbie question.

    • Aiden

      Frankie, did you get an answer for this or work it out? I’m a newbie and having the same problem. I managed to put some code in didSelectRowAtIndexPath to get an index from the selected cell, instantiate a view controller (a mock up I made) in code and show it, but obviously this doesn’t make use of the animations.

      The tutorial is an interesting demonstration of animation, but it isn’t very helpful to newbies as a template for creating an animated navigation experience between views. Any help would be greatly welcomed.

      • Aiden

        OK, so this might not be the best solution (I’ve only been coding Swift for 2 weeks) but this is what I did. It animates the different screens as per the example, but when a new screen item is selected it pops up with default iPhone type animation, but then will shrink / expand like it should. I don’t find this too bad as it fits the fact that it’s a whole new screen being presented.

        In NavigationViewController I add a static method to remember what the current shrunk page is, along with a guard in the didSelectRowAtIndexPath() to see if we are re-showing the shrunk view, or opening a new one:

        struct MenuSelection {
        static var curIndex = 0
        }

        func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        // How to select the nav row and open desired scene
        let row = indexPath.row
        println(“row \(row)”)
        println(“MyVariables.curIndex \(MenuSelection.curIndex)”)

        if (row == MenuSelection.curIndex) {
        dismissViewControllerAnimated(true, completion: nil)
        } else {
        var navScreenIdentifer = “DefaultViewController”
        if (row == 0) {
        navScreenIdentifer = “DefaultViewController”
        } else if (row == 1) {
        navScreenIdentifer = “OtherViewController”
        } else if (row == 2) {
        navScreenIdentifer = “AnotherViewController”
        }

        let vc : AnyObject! = self.storyboard?.instantiateViewControllerWithIdentifier(navScreenIdentifer)
        self.showViewController(vc as UIViewController, sender: vc)
        MenuSelection.curIndex = row
        }
        }

        Then for each of the other view controllers I just add the custom seques the same way as described in this tutorial. Create the custom link, add the presentNavigation() method link, name the seque link to the same as in the presentNavigation() method. The only final thing is that if your new pages link to other pages than back to the Navigation table, then you need to name those seques also, and have another guard in the prepareForSeque() methods also. See below:

        @IBAction func presentNavigation(sender: AnyObject?){
        performSegueWithIdentifier(“presentNav”, sender: self)
        }

        override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let link = segue.identifier

        if (link == “showArena”) {
        /*
        You can just leave default seque processing
        */
        } else if (link == “presentNav”) {
        // Navigation seque!??!
        let toViewController = segue.destinationViewController as UIViewController
        self.modalPresentationStyle = UIModalPresentationStyle.Custom
        toViewController.transitioningDelegate = self.transitionOperator
        }
        }

        Hope this helps, and if anyone more proficient in Swift can enhance this I would love to hear.

        Cheers

        • David

          I’m trying AIDEN’s solution, being new to Swift as Well, I’m getting this error:

          NavTransition[41029:1682618] Warning: Attempt to present on whose view is not in the window hierarchy!

          I made a new ViewController File called VolunteerViewController and a new ViewController in the StoryBoard called VolunteerViewController and have tried connecting it to Both the NavigationViewController and the TimelineViewController and I get the same error.

          Any ideas on what I’m doing wrong?

  7. Pingback: Issue with Slide-Out Menu Navigation In Swift | Questions and Answers Resource

  8. Abhishek

    Hi,
    Thanks a lot for this great tutorial, It helped me a lot.
    However, I have a requirement where my app’s first page is a tab bar, and on the first tab I need to show this sliding menu.
    Can you please help as how this could be done?

    Thanks!

  9. Pingback: How to animate a left sidebar as similar to flipkart in ios? - BlogoSfera

  10. iphone 6 case neo hybrid

    What’s up, just wanted to tell you, I liked this post.
    It was funny. Keep on posting!