Custom View Controllers Transitions in Swift
This is probably one of the most underrated topics in iOS development and one of the most difficult to master as well. When you had some solid base in your dev skill set you will start looking to dig into more advanced features like animations. If you’re a regular user of the most popular apps like Instagram, Facebook, Airbnb, Spotify, etc., you probably start to look at them with the developer’s eyes. That’s questioning how things work, what type of architecture do they use, how the animations and transitions are so smooth, and so on.
When you play with this kind of apps you’ll for sure notice that when a new view controller is presented, it rarely does with a default pushViewController animation. Instead of that, they have their custom transitions with some cool animations. It’s a key value that makes a difference when we try to engage a user with the app.
For this article, I have prepared a simple application with a couple of AC/DC albums (for simplicity I only added two). When you tap in one album it takes you to a new view with that album’s songs.
So, I want to show you how we can make this custom transition between view controllers.
So let’s start with some basic concepts first. Every time we push a new view controller, UIKit asks the UINavigationController’s delegate if there is a custom transition to use calling this function:
If the delegate it’s not implemented or if the return value is nil, then we’re going to get the default view controller’s push and pop behavior. If instead, we return a UIViewControllerAnimatedTranistion object, UIKit will use that object for the animations.
If you look at the UIViewControllerAnimatedTransition protocol you’ll see that we only need to implement two functions:
In the first one, we return the duration of the transition.
In the second one is when the magic happens =). Here we have access to the from view controller (the initial view controller) and the to view controller (the transitioned view controller) as well.
The first thing we need to do is create a class that implements the UIViewControllerAnimatedTransition protocol.
We know that we need to make our transition animations inside the animateTransition function. First, we must access the context view of the transition. You can think the context view as the view that is going to appear between the from view controller’s view and the to view controller’s view while the animation is still running. This context view is just an empty view. It doesn’t have anything in it, so we need to add to it the elements that we want to animate in our transition.
In our project, we’re going to animate the following views:
- Collection view cell’s content view.
- The album cover image.
If you look at the demo you’ll notice that when we tap into one album, its content view expand and fulfill the screen meanwhile the cover image moves to the top of the view while it changes its shape from a circle to a square.
When we add some view to the containerView, we must give it the right frame to match the exact position that it has in the view controller’s view. To do this, we can use the convert(_ rect: CGRect, from coordinateSpace: UICoordinateSpace) -> CGRect function.
Last but not least, we need to copy all the properties of the views we want to animate in order to replicate the same layout that they have in the original view.
Let’s wrap up all this and start coding our animateTransition function.
- Get the initial view controller (referenced as the fromViewController) and cast it to an AlbumsViewController type.
- Get the final view controller (referenced as the toViewController) and cast it to an AlbumDetailViewController type.
- Get the current cell of the collection. In the AlbumsViewController we keep this property update every time the user scroll between the albums.
- Get the album cover image from the current cell.
- Get the header view of the AlbumDetailViewController. In this header view, we have in place the cover image. So, we need to access this view to get the final position of the image.
- We get the context container view. This is the view that we’re going to use for our transition
- We need to add the final view (AlbumDetailViewController’s view). When the animation is done, this is what is going to get left. If we don’t add it we’ll get a black screen by the end.
- Until the whole transition is complete, the view needs to stay hidden.
Now that we have our container view ready, we can start whit our animations.
For the transition that we want to make, we only need to change the view’s frame inside the animation block. Also, we set the corner radius of the album image view layer to zero, so the rounded borders that we have in the first screen, smoothly disappear in the transition.
In the completion block, we need to show our AlbumDetailController’s view and then remove the snapshots that we added for the animations. And finally, we tell the context that the transition is completed.
Now we have everything in place to start using our custom transition. But first, we must implement the navigationController’s delegate to tell UIKit that now it has to use our TransitionManager class to animate the transition instead of the default behavior.
We have a couple of options to do this. We can implement the delegate in the AlbumsViewController and return an instance of the TransitionManager.
The other option is to implement the delegate inside the TransitionManager and in the AlbumsViewController set the TransitionManager as the navigationController’s delegate.
And with that in place now you have your own custom transition. I showed a simple transition animation here, but you can animate whatever you want from one view controller to another.
This might be overwhelming at the beginning but once you fully understand the key concepts of the transitions you’ll only need to worry about the animations that you want to perform. Just picture what you want to accomplish in the transition and then think that most of the work will be done inside the func animateTransition(using transitionContext: UIViewControllerContextTransitioning) function.
In this post I covered the push operation, the pop operation is pretty similar, we'll only need to make some small adjustments to our code. Let me know if you're interested in a second part covering this.
Thanks for reading! I hope this was helpful to you. If you like, you can check the full project here. And if you have any questions, drop me a message, I’ll happy to help.