Tricky UIViewController transition animations
There are a lot of articles about simple transition animations for beginners and lack of advanced ones. So I’ve decided to fix it and describe a few tricky transitions that I have recently made. But first let’s refresh our basics of UIViewController transition animations.
Basics of UIViewController transition animations
If you have a strong knowledge of the basics you can skip this part.
UIKit offers us simple mechanics for custom transitions. It consists of 2 mandatory parts and 1 optional.
It is an object that conforms to
UIViewControllerAnimatedTransitioning protocol. To create an animator you need to implement just 2 methods:
In the first one you should return duration of the transition in seconds, 0.5 for example. And in the second you need to write code of the transition. And that’s it. Variable
transitionContext contains all necessary information you need to perform transition of any complexity level.
UIViewController or container controller delegates such as
UITabBarControllerDelegate for animator injecting. These methods will be called before any related transition and you can return your animator if needed or
Interaction controller (optional)
It is an object that conforms to
UIViewControllerInteractiveTransitioning protocol. But for the majority of cases you need to make a subclass of
UIPercentDrivenInteractiveTransition. This class is provided by Apple to make our life easier. All you have to do to make your transition interactive is to bind calling of
update(percentComplete) method of interaction controller with progress of any kind of gestures such as a pan gesture via
If you want to read more about this check official Apple documentation.
Recently we have made the application Tripinsurance which made insurance process for travellers much easier and clearer. We at Rosberry care much about beautiful UI and the best UX. One of the main purposes of the app is to offer a quick and friendly way for buying a policy. On the main screen a user sees the card with text field “Select country”. By tapping on it a user initiates a policy creation process selecting a country as a start. Less words more animations!
Looks smooth and elegant and as you already understand there are two transitions — one is for opening and another is for closing. On opening the card grows up and fills all the space behind the new screen. Steps marked with numbers take their places above the text field and the second step title isn’t cut anymore. A small title of the card fades out and the list of the countries appears below the search text field. A backward animation just rolls everything back in an intuitive manner. Let’s try to look at on these animations closer to see more details, I slowed it down.
Now you can notice that the text field “expands” and becomes a new full size screen with a list of countries below. Also rounded corners of the text field increase their radius of the top left and the top right ones and decrease it till 0 of the bottom left and the bottom right. During the backward animation you can notice that the list of countries with the search field on top of it turns into the original text field by masking it.
Seems like we understand the way this transition works and now will learn how it can be done in code. But firstly I want to share with you some tips and tricks and general approach to creating transition animations so you can become a guru.
General approach to creating transition animations — tips and tricks
I believe that in the right hands animations in general can turn a good app into a great app. Transition animations can separate flows and simplify them if there are a lot of screens/steps involved. So here is the main thing you need to remember when coding transitions — animation must be spectacular, simple and flawless. I hope that the first statement is clear. The second one is really important because if transition is too fussy it will annoy a user. And the last one as for me is the most important because even if your animation is spectacular and simple but has any noticeable lack — all efforts are in vain.
Now we are ready to dive in the approach itself
- Analyse animation. You need to understand behaviour of every component involved.
- Analyse timings. Often during transition some animation components start at some specific point of time and might be much faster than the whole animation. You may even create some kind of timeline of transition on paper if it helps you.
- Snapshots. It is much easier and safer to use snapshots of views than views themselves. There are a few different techniques of snapshot creating:
1. Using method
2. Creating image of
The first is much easier and should be used most of the time. The second is suitable for some specific animations like paper folding. Using snapshots technique encapsulates transition animation from actual views hierarchy and does not affect it at all.
- Frames and coordinate systems. If you are an adept of AutoLayout then I encourage you to use good old frames for transition animations because it’s more convenient and requires less code.
- Long duration during debug. As I said before animation must be flawless and to achieve this goal you must see every frame and be sure that increasing or decreasing animation duration won’t cause bugs or glitches. I prefer to set at least 5 seconds duration or even 10 if I’m not sure that everything goes right.
- Ability to restart transition. Last but not least. If you won’t care about this from the beginning of the transition development you will waste a lot of time due to constant app relaunch just to watch your animation again. You don’t need to implement some really cool way to go back from the beginning. Сalling
viewDidAppearevent of the target view controller with some delay (or not) will do the trick.
Finally we can dive into the code and understand how this transition can be done. I will post code blocks gradually and explain every part.
I simplify some parts of the code related to views hierarchy.
This is the beginning of
animateTransition(using transitionContext:) method. Here we are trying to get access to controllers which are involved in this transition. As you can see this operation is wrapped with
guard and if at least one of our checks fails we interrupt the transition. Please, pay attention to
transitionContext.completeTransition(true) call. It allows us to fallback gracefully and complete the transition without animation. Otherwise if we use only
else branch the app will freeze and become irresponsive, and the only way we will have is to relaunch the app.
Here we add the view of the destination view controller to the
containerView and send it to the bottom of the view hierarchy. We did it on purpose because we will show it gradually and don’t need our user to see the whole view during the transition. Bear in mind that we add view of
toNavigationViewController and not the destination view controller itself. Because otherwise it will lead to unpredictable behaviour and can cause a lot of mysterious bugs for simple
pop actions of navigation controller.
Next we need to make all the required snapshots and gracefully fallback if something goes wrong. For our transition we need these snapshots:
textField— snapshot of the card’s text field
cardStepsView— snapshot of the steps view on the card
titleLabel— snapshot of the small title label on the card
destinationBackgroundView— snapshot of the background view with gradient in the destination view controller
destinationStepsView— snapshot of the steps view of the destination view controller. If you remember, the original steps on the card are cut and we want them to be uncut by the end of the animation. So we will combine them during the animation and nobody will notice that there is any kind of trick here. I bet you didn’t notice anything suspicious with the steps until reading that explanation 😉
destinationNavigationBar— snapshot of the container with navigation elements such as a back arrow
destinationControllersContainer— snapshot of the container which contains view controller with the search field and the countries list
We just end up the preparation to the animation. This is a common part of any animation. Find controllers, place views in a container and prepare all the snapshots and the views we need.
This part is pretty simple — we just hide the original card view and take it’s frame in window’s coordinate system. Let me show you
Here we are checking if the view has a super view. If it doesn’t, print warning and return an original frame. If the view is ok and takes its place in the views hierarchy then we convert its frame into the window’s coordinate system by passing
Next we prepare
destinationBackgroundView — we take its original frame and make it look like a
cardView. Also we take its original frame so we can move it back during animation.
And set all frames and properties for the rest of card’s inner views snapshots.
Now we end up preparation of the “fake card”. We have all the snapshots that allow us to imitate it and transform it safely without affecting the real card view.
Next we just prepare navigation elements so it can smoothly fade in.
Here we are tightly close to the trickiest part of the animation. We place a controller’s container of the destination view controller in the origin of the card’s text field. But before we dive in let’s just add everything into the transition’s container.
For adding views this way I use the following
Now we’re almost done our preparations before transition. All the snapshots are in place and ready to be animated. The only thing left is masking.
This part of code looks pretty simple — we just create instances of
CAShapeLayer as masks and set the
paths with rounded corners. Let’s pretend that you didn’t notice that
path is set with a function that I didn’t explain. I’ll do it later, I promise.
The animation begins! As you already know the layer’s properties should be animated with
CAAnimation. I create a helper function for it.
I won’t explain it step by step because there are a lot of articles and explanations of layers animations. But the main idea of this method to animate layer’s
path from the original value to the one we pass.
And here is our animation block. Nothing special — just setting frames and
alphas where needed and removing all the snapshots we were using on completion. The only suspicious thing here is setting
textField. I have cheated here a bit. Actually the view controllers hierarchy is really complex here and this cheating allows us not to over-complicate this already tough animation and save some time for other cool stuff we’ve made in this app. Seeing slowed animation you may notice a small glitch on the left side of the card’s text field, but with normal speed it isn’t noticeable. And as I said before there shouldn’t be any noticeable lacks 😉
I promise you to show the function which creates a path for the masks during animation. But first let’s try to use the function we have out of the box.
This is the simplest way to create a path with rounded corners for example only for top left and top right corners. It looks like what we need. Or not?
Excuse me, what?!
As it turned out
UIBezierPath(roundedRect:byRoundingCorners: cornerRadii:) function creates path which isn’t suitable for animations at all. After some experiments, researches and trying to use it from different angles I’ve realised that the only way I have is to draw the path which can be animated without glitches.
It looks a bit wordy but actually is quite simple.
Firstly we create points from 1 to 8, next we create the helper points which are marked on the diagram with letters
outer respectively. The inner points are used as the center to draw an arc between the main points like 2 and 3. The outer points are used as the connection points if this corner should not be rounded.
And that’s it!
In this article I’ve tried to describe the way I work with transitions of any complexity and hope that you find it useful. So fear not my friend! Everything is possible.
If I understand that this is an interesting theme and you want a continuation I will post other articles about transitions I’ve made. I have a lot really great stuff to share. So don’t be shy to support me and feel free to ask any questions. Hope to see you soon!