Custom iOS View Controller Transitions

Cool moves — hope you’re stoked for this tutorial?

View/Download the source code for this project

Ever wanted to add some ‘pezaz’ to your View Controller transitions? Sure the builtin transitions look okay, but sometimes you want something that’s going to get your users jumping with transition joy. In this short article we’re going to do just that. Let’s do this.


The Main Players

May I introduce to you our cohort of elite players for this lesson:

UIViewControllerTransitioningDelegate

We’re going to conform to this protocol to allow us to use custom transitions: we can literally design any transition we want! Talking of which:

UIViewControllerAnimatedTransitioning

This bad boy will allow us to design totally rad and custom transitions for our View Controllers! Whatt? Yeah!


Chicken Break

Conforming to protocols? Will need to implement some required methods, right? Cluck

So what methods will be required here? Well, I found in this case that the required methods are really super-useful and logical.

For example in UIViewControllerTransitioningDelegate we need to add the following methods:

animationController(forPresented) and animationController(forDismissed) — simply really, we need something to call when the animation will present a view and when it will dismiss a view.

Similarly when conforming to UIViewControllerAnimatedTransitioning we’ll use:

transitionDuration() and animateTransition(using)- here we provide a duration for the transition (obvious) and also an actual animation.

Let us begin

Start a new project and create a new Swift file for your transition. In it we’ll inherit from NSObject to start out and then conform to AnimatedTransitiong:

class CewlAnimation: NSObject, UIViewControllerAnimatedTransitioning {
}

In here you’ll need the two methods to allow the transition to work properly:

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.0  // as long or as short as you like :-D
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Animate meh!
}

Awesome! Let’s add some animations.

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)
toView?.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
containerView.addSubview(toView!)
containerView.bringSubview(toFront: toView!)
UIView.animate(withDuration: 1.0, animations: {
toView?.transform = .identity
})
transitionContext.completeTransition(true)
}

Wow! That is a lot of code. On a basic level this will do a 1 second transition between the views where it will scale up to fill the screen from the centre.

Let’s break the code down into something chewable:

How… is this. possible

When you perform these custom transitions a super-helpful ‘thing’ comes along called transitionContext. This is like the handover manager between the two views and it gives you access to the views you’re going ‘to’ and coming ‘from’.

To get a reference to the ‘manager’ you use:

let containerView = transitionContext.containerView

This allows you to reference the view we’re moving to:

let toView = transitionContext.view(forKey: .to)

Then we animate the transition: remember, when doing animations we set where we want to go from and then animate to that state.

toView?.transform = CGAffineTransform(scaleX: 0.0, y: 0.0) // Bye bye view, you are now invisible

We then add it to the view hierarchy and place it on top of the stack:

containerView.addSubview(toView!)
containerView.bringSubview(toFront: toView!)

Now we have an invisible view on top of the stack and need to actually animate it to the screen:

UIView.animate(withDuration: 1.0, animations: {
toView?.transform = .identity
})

Cool, but don’t forget to notify the ‘manager’ that the transition is complete or it won’t EVER END!

transitionContext.completeTransition(true)

Awesome, now we just need a few views to transition between!

Go to your main ViewController class and make sure you conform to UIViewControllerTransitioningDelegate

extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
}

You’ll notice we’re returning nil here, let’s add our new animation!

class ViewController: UIViewController {
let cewlAnimation = CewlAnimation()
...

Yes! What informative names we have for that transition. Now just change the methods to return our transition rather than nil:

extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return cewlAnimation // <------- :D
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
}

Excellent! Now make a new ViewController and a class called DetailView. Add the Identifier to the storyboard as well. Make sure the new View Controller also conforms to the transitioning protocol:

class DetailController: UIViewController, UIViewControllerTransitioningDelegate {
...

Now make a button and add an action to it to transition to another ViewController:

@IBAction func loadVCInStyle(_ sender: Any) {
let detailView = storyboard!.instantiateViewController(withIdentifier: "DetailView") as! DetailController
detailView.transitioningDelegate = self
present(detailView, animated: true, completion: nil)
}

Notice we’re setting the delegate to our ViewController.

Build and run! Whoah! What a nice animation. Have fun!