Update: As of iOS 7.0, interface transitions in landscape orientation are in a dire state. Read more about it. This article focuses on portrait-only transitions.
When teaching a new programming technique, there is a spectrum ranging from practice to theory. At one end, you teach only what you need to understand to implement a feature. At the other end, you teach the reasoning behind the API necessary to implement a feature. Too practical and you risk creating cargo-cult coders. Too theoretical and you risk alienating users of your API. It’s a tough balance to strike.
Of all of the new APIs introduced in iOS 7, perhaps the most confusing was the custom UIViewController transitions API. This is due mostly because the WWDC presentation leaned heavily toward the theoretical end of the spectrum. The problem is exacerbated by the lack of sample code illustrating how to use the custom view controller presentation API.
We’re here to fix that. The API itself isn’t that confusing – it just takes some experience getting your hands dirty. Let’s dive in.
Recall that UIViewController is the main unit of composition for application logic within iOS applications. View controllers are presented to users via navigation controllers, tab bar controllers, and modally. Before iOS 7, each of these presentations had predefined animations that were not customizable. Pushes onto a navigation controller’s stack moved from right to left. Selecting a different tab didn’t provide any animation. Modal presentations used one of a few pre-defined transitions (the default was a slide-up).
What’s more is that once a transition was complete, the presenting view controller was no longer at all visible (on the iPhone, at least). This made implementing custom modal views difficult.
iOS 7 introduces a new way to use a completely custom animation when transitioning from one view controller to another, whether it be a push onto a navigation controller stack, selecting a different tab, or a plain presentation. Additionally, the API allows you to present a view controller without necessarily obscuring the presenting controller. This makes faux popovers and alert views possible for the first time using UIViewControllers. Awesome!
A custom transition can either be interactive or non-interactive. We’re going to focus on the non-interactive type first because it’s a lot easier to implement. Remember that the goal of this article isn’t to explain the API – check out the WWDC video for a great explanation – the goal here is gain practical, hands-on experience.
Here’s the interaction we’re going to create. It’s nothing special – just a view appearing from the right edge of the screen. What is special is that we’re actually presenting a view controller, even though the presenting view controller remains visible.
So how do we accomplish this? The trick here is to create a new object called the animator that will be responsible for animating the presentation (and corresponding dismissal). When you present the view controller, set the modalPresentationStyle to UIModalPresentationCustom and set yourself as the transitionDelegate. Then implement the UIViewControllerTransitioningDelegate methods to vend the animator to the system.
You can do this in one of a few ways. We’re using a Storyboard file with a modal segue to the detail view controller, so we’ll implement the prepareForSegue:sender: method.
However, you could use the traditional presentation API if you’re not using Storyboards.
This code is for presenting a view controller modally. Similar techniques work with UINavigationControllers and UITabBarControllers. In those cases, simply comform to those class’ delegate protocols and implement the corresponding methods to vend an animator. In our examples, we’re going to use plain presentation methods.
Note that if you set the modal presentation style to custom, the system expects you to provide a non-nil transitioning delegate. You’ll receive a runtime warning if you don’t.
After defining the transitioningDelegate on the presented view controller, we’ll need to implement the UIViewControllerTransitioningDelegate methods to vend the animator.
That’s really all there is to it. With only a few lines of code, we’ve invoked a custom transition to a new view controller. This is awesome because the presenting view controller is completely unaware of how the presentation will take place – there is a clear separation of concerns. This is also awesome because we can reuse the animator elsewhere in our application for the same presentation logic.
So what’s in the animator? The animator is just an NSObject subclass that conforms to the UIViewControllerAnimatedTransitioning protocol. The two required methods of this protocol define how long the animation from one view controller to the other will take, and the code to actually animate that transition.
When the transition itself happens, the animator is passed a transition context that holds information about the transition. This includes the “from” and “to” view controllers and a container view. This container view is where the animation actually takes place. You add both view controllers’ views to the container view, perform some transition animation, then tell the context that the transition has completed. It’s that simple.
Our animator takes care of both a presentation and a dismissal (the property that is set in our UIViewControllerTransitioningDelegate methods). Let’s take a look at the complete implementation.
The first method is very straightforward – how long should the transition take? The next method is a little trickier. It’s passed the transition context then, depending on whether it’s presenting or dismissing, performs an animation to present or dismiss the detail view controller.
We’re using plain ol’ UIView block-based animations here. Nothing fancy. The only tricky thing is that the “to” and “from” view controllers change depending on whether you’re presenting or dismissing. That is to say, the presenting view controller is the “from” controller when presenting and the “to” controller when dismissing.
As you can see, it’s not a lot of code to implement a custom transition. Let’s take a look at a more complicated example: an interactive transition. These are trickier for a few reasons. First, you’ll usually want a way to present an interactive transition non-interactively, as well as dismiss it non-interactively. This lets users choose how they want to present or dismiss the content in the view controller. Additionally, the interactivity is tied to a gesture recognizer. Where does the code go to respond to that recognizer?
The answer is to subclass UIPercentDrivenInteractiveTransition, make it the animator, the transitioning delegate, and the gesture recognizer target. This is going to bundle all of your transitioning logic into one place. There’s a lot to unwind here, so let’s take it one step at a time.
First, our interactor is going to be initialized with a parent view controller. This is because the interactor itself is going to be responsible for presenting the new view controller in the gesture recognizer callback method.
The interactor is the target of a screen edge pan gesture recognizer which we’ll set up in our presenting view controller’s viewDidLoad.
Our userDidPan: method looks like the following.
Quite a lot there. Don’t worry, we’re going to go through it all. The most important thing to note is that the gesture recognizer code does not implement any animation code.
When our recognizer begins, we present (or dismiss) the view controller. When the recognizer changes, we update the percent complete on self. Finally, when the recognizer finishes, we decided whether to complete or cancel the transition depending on the last direction of the gesture recognizer. It’s a lot of code, but it’s all fairly straightforward.
Notice that when we present the new view controller, we set self as the transition delegate. When prompted to vend an animator, we’ll also return self.
There are two more methods to provide an interactor for the interactive transition. These methods are called after the previous methods. We’re going to return self if we’re interactive and nil if we’re not.
The next methods are copied almost directly from the first example. We need to provide animations to non-interactively present and dismiss the view controller.
What’s really interesting is the interactor method in the UIViewControllerInteractiveTransitioning protocol. We’ll implement this method to begin our interactive transition. Then we’ll override the UIPercentDrivenInteractiveTransition methods to update our transition, then finally to complete or cancel the transition.
The startInteractiveTransition: method sets up the container view with the “to” and “from” view controllers’ views. It’s important which order you add these in so the correct view is “on top.”
Next we need to update the position of the menu view controller depending on the transition’s percent complete.
Finally, the code to complete or cancel the transition is below. It’s critically important that no matter what, completeTransition: is called on the transition context that was passed in startInteractivetransition:. We’ll call this method in the completion block of our animation.
We’ve driven this transition completely using plain, boring UIView block animations. What’d be super-cool is to use the new UIKit Dynamics to drive the animations. That’s beyond the scope of this tutorial, but you’ll find the code for it in the TLMenuDynamicInteractor. Just set the USE_UIKIT_DYNAMICS C macro to YES to use the dynamic interactor instead.
The most important thing to note about the dynamic version of the interactor is that, unlike our last post’s example of driving an attachment behaviour with a gesture recognizer, the gesture recognizer callback method does not touch the attachment behaviour directly.