iOS has always had a heavy emphasis on animations, and for good reason. Animations can not only add some extra aesthetic appeal, they facilitate a functional understanding of an interface. Animations give users useful visual cues and context. In iOS, one of the most important applications of this concept is transitioning between ‘scenes’ of an application, or in programmer speak, switching from one UIViewController to another. In this post I will explain how to accomplish custom transitions between iOS scenes using Interface Builder and objective C.
Step 1: The Simplest Set-Up
In order to demonstrate these transitions, I’m going to attempt to make the simplest possible functioning example:
The first step is to set up a navigation controller (scene A) and two scenes to transition between. Our first scene (scene B) has a button that will trigger a transition to the next scene (scene C), which has a different background color so that it will be visually distinguishable. Scene B has the following code:
Any transition between scenes will be arbitrated by a special type of controller which handles navigation (scene A in our example). This special controller is usually a UINavigationController, but you can also use a UITabBarController. The navigation controller has a delegate object that it queries to determine if it needs a custom transition. You can see in our viewDidLoad function we set our controller for scene B as the delegate for scene A’s controller.
As the delegate, our scene B controller needs to implement the UINavigationControllerDelegate protocol:
The protocol requires the delegate to return an animator object, which will inform the navigation controller on how to perform the animation. In this example, our object is the TTFadeAnimator. We will need to custom write this object for each unique type of transition that we want. Additionally, as we can see from the return type of the function above, the animator object must implement the UIViewControllerAnimatedTransitioning protocol.
The TTFadeAnimator in this example is a simple crossfade. The following code is the entire implementation:
Almost every line here is boilerplate. The two exceptions are the lines that set the view’s alpha to 0.0f, and then back to 1.0f. Let’s examine the rest of the code to see what we can learn about the way the animator is expected to function.
For reasons I will explain in the following post, the best practice here is to use only one animation block in this function. In this simplified case, we would be able to get away with having multiple animation blocks. However, we will see in a later example why multiple blocks would cause erroneous behavior in more complex transitions.
One of the more unexpected duties of the animator object is that it must manage the view hierarchy of both scene B and scene C. Our animateTransition: function is passed a UIViewControllerContextTransitioning argument that has an accessor for a container view. We are granted the freedom to do whatever we want within this container to achieve the desired transition effect, which is very powerful.
On the flip side, this container view mechanism can be error prone. Even though our navigation controller will push scene C onto the nav stack, if we neglect to add scene C’s view, it will not appear. In the above example you can see the line of code that adds the view just before the animation block. For similar reasons, you can see in the animation completion block that it is necessary to remove the old view when the transition is over.
Lastly, we need to always remember to call the completeTransition: function on the context object. This call officially ends the transition, and without it, the app will hang at the end of the animation.
So far, so good. Let’s take a look at our transition:
The code for this section is available in the ‘Step1_crossfade’ directory here.
Step 2: Using Snapshots
The previous section should be all you need to make some basic transitions. But we shouldn’t be satisfied with only the most basic features. One of the more frequently requested transitions is to have part of the screen open up to reveal more detail about a specific part of the UI. In the next example, we’ll look at what it would take to accomplish this.
The iOS snapshot API provides us with a very efficient way to capture the content of a UIView and use that content in an animation. We will need this capability to create the illusion of a view that splits open to reveal the next view inside it.
Because we want a new type of transition, we will need some new animator objects. Here is a snippet of updated code from scene B’s controller:
Here we have replaced our fade animator with two new ones: TTPushAnimator and TTPopAnimator. The delegate pattern used by the navigation controller allows our scene B controller to use any arbitrary logic we want to determine what animation should be used. In this example, we use the operation argument to detect if the transition is a push or pop operation and supply the corresponding type of animator. As you might suspect, both animators do the same type of animation, but in different ‘directions’.
One other interesting thing to note is that if this function returns nil, the navigation controller will use the default iOS push animation for the transition.
Next let’s take a look at where the meat of this example happens, the implementation of the TTPushAnimator class:
Here are the main takeaways from this function:
- We use iOS’s snapshot API to slice up the original scene and animate the parts of it around the screen
- Like all other animators, we need to:
- Manage the UIView hierarchy for the transition
- Use one, and only one, animation block
- Call the completeTransition: function to signal the end of the transition
Let’s more closely examine that first part. The snapshot API allows us to take a ‘screenshot’ of all or part of our current view and represent it as a new view. That new view can behave just like any other UIView object. Keep in mind that although the snapshot view can be thought of as an image representation of the original view, it is not a UIImageView.
Let’s take a look at the new transition:
This post only shows the code for TTPushAnimator because TTPopAnimator is so similar. A link for all the code is available below.
The code for this section is available in the ‘Step2_snapshotAPI’ directory here.