iOS Animation Tutorial: Custom View Controller Presentation Transitions

Learn how to create custom view controller presentation transitions and spice up the navigation of your iOS apps! By Fabrizio Brancati.

Leave a rating/review
Download materials
Save for later
Share
Update note: Fabrizio Brancati updated this tutorial for iOS 12, Xcode 10.2 and Swift 5. Marin Todorov wrote the original.

Whether you’re presenting the camera view controller or one of your own custom-designed modal screens, it’s important to understand how these transitions are happening.

Transitions are always called with the same UIKit method: present(_:animated:completion:). This method “gives up” the current screen to another view controller using the default presentation animation to slide the new view up to cover the current one.

The illustration below shows a “New Contact” view controller sliding up over the list of contacts:

In this iOS animation tutorial, you’ll create your own custom presentation controller transitions to replace the default one and liven up this tutorial’s project.

Getting Started

Download the project materials using the Download Materials button at the top or bottom of this tutorial.

Open the starter project and select Main.storyboard to begin the tour:

The first view controller, HomeViewController, contains the app’s recipe list. HomeViewController presents DetailsViewController whenever the user taps one of the images in the list. This view controller sports an image, a title and a description.

There’s already enough code in HomeViewController.swift and DetailsViewController.swift to support the basic app. Build and run the app to see how the app looks and feels:

Tap on one of the recipe image, and the details screen comes up via the standard vertical cover transition. That might be OK, but your recipes deserve better!

Your job is to add some custom presentation controller animations to your app to make it blossom! You’ll replace the current stock animation with one that expands the tapped recipe image to a full-screen view like so:

Roll up your sleeves, put your developer apron on and get ready for the inner workings of custom presentation controllers!

Behind the Scenes of Custom Transitions

UIKit lets you customize your view controller’s presentation via the delegate pattern. You simply make your main view controller, or another class you create specifically for that purpose, conform to UIViewControllerTransitioningDelegate.

Every time you present a new view controller, UIKit asks its delegate whether or not it should use a custom transition. Here’s what the first step of the custom transitioning dance looks like:

UIKit calls animationController(forPresented:presenting:source:) to see if it returns a UIViewControllerAnimatedTransitioning object. If that method returns nil, UIKit uses the built-in transition. If UIKit receives a UIViewControllerAnimatedTransitioning object instead, then UIKit uses that object as the animation controller for the transition.

There are a few more steps in the dance before UIKit can use the custom animation controller:

UIKit first asks your animation controller — simply known as the animator — for the transition duration in seconds, then calls animateTransition(using:) on it. This is when your custom animation gets to take center stage.

In animateTransition(using:), you have access to both the current view controller on the screen as well as the new view controller to be presented. You can fade, scale, rotate and manipulate the existing view and the new view however you like.

Now that you’ve learned a bit about how custom presentation controllers work, you can start to create your own.

Implementing Transition Delegates

Since the delegate’s task is to manage the animator object that performs the actual animations, you’ll first have to create a stub for the animator class before you can write the delegate code.

From Xcode’s main menu, select File ▸ New ▸ File… and choose the template iOS ▸ Source ▸ Cocoa Touch Class.

Name the new class PopAnimator, make sure Swift is selected, and make it a subclass of NSObject.

Open PopAnimator.swift and update the class definition to make it conform to the UIViewControllerAnimatedTransitioning protocol as follows:

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

}

You’ll see some complaints from Xcode since you haven’t implemented the required delegate methods. You can either use the quick fix that Xcode provides to generate the missing stubbed methods, or write them out yourself.

Add the following method to the class:

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

The 0 value above is just a placeholder value. You’ll replace this later with a real value as you work through the project.

Now, add the following method stub to the class:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

}

The above stub will hold your animation code, adding it should have cleared the remaining errors in Xcode.

Now that you have the basic animator class, you can move on to implementing the delegate methods on the view controller side.

Wiring up the Delegates

Open HomeViewController.swift and add the following extension to the end of the file:

// MARK: - UIViewControllerTransitioningDelegate

extension HomeViewController: UIViewControllerTransitioningDelegate {

}

This code indicates that the view controller conforms to the transitioning delegate protocol, which you’ll add here in a moment.

First, find prepare(for:sender:) in HomeViewController.swift. Near the bottom of that method, you’ll see the code that sets up the details view controller. detailsViewController is the instance of the new view controller, and you need to set HomeViewController as its transitioning delegate.

Add the following line right before setting the recipe:

detailsViewController.transitioningDelegate = self

Now UIKit will ask HomeViewController for an animator object every time you present the details view controller on the screen. However, you still haven’t implemented any of the UIViewControllerTransitioningDelegate methods, so UIKit will still use the default transition.

The next step is to actually create your animator object and return it to UIKit when requested.

Using the Animator

Add the following new property at the top of HomeViewController:

let transition = PopAnimator()

This is the instance of PopAnimator that will drive your animated view controller transitions. You only need one instance of PopAnimator since you can continue to use the same object each time you present a view controller, as the transitions are the same every time.

Now, add the first delegate method to the UIViewControllerTransitioningDelegate extension in HomeViewController:

func animationController(
  forPresented presented: UIViewController, 
  presenting: UIViewController, source: UIViewController) 
    -> UIViewControllerAnimatedTransitioning? {
  return transition
}

This method takes a few parameters that let you make an informed decision whether or not you want to return a custom animation. In this tutorial, you’ll always return your single instance of PopAnimator since you have only one presentation transition.

You’ve already added the delegate method for presenting view controllers, but how will you deal with dismissing one?

Add the following delegate method to handle this:

func animationController(forDismissed dismissed: UIViewController)
    -> UIViewControllerAnimatedTransitioning? {
  return nil
}

The method above does essentially the same thing as the previous one: You check which view controller was dismissed and decide whether to return nil and use the default animation or to return a custom transition animator and use that instead. At the moment, you return nil, as you aren’t going to implement the dismissal animation until later.

You finally have a custom animator to take care of your custom transitions. But does it work?

Build and run your app and tap one of the recipe images:

Nothing happens. Why? You have a custom animator to drive the transition, but… oh, wait, you haven’t added any code to the animator class! You’ll take care of that in the next section.