Making Animations Fun Again on iOS
Writing elegant animations is usually not that elegant: boilerplate code, code repetition, managing multiple animation engines and it’s hard to keep a consistent look and feel across the entire app.
Most animations in the app are “primitive” animations:
- Fade in / out
- Translation along the axes
And they have variations: like “Basic”, “Spring”, “Decay”, …
Complex animations are composed of these primitive animations. So basically, whenever I write an animation I repeat myself. The same code already exists in some other view controller.
For example, let say I want to hide a UITableView — it would look like so:
This code, though short, repeats in many view controllers, and contains boilerplate code — I would much rather write:
Since we use a small set of durations for our animations and usually use 0.25 as a default duration, it would make sense to omit it:
In uMake this is how we write animations. It allows us to write fast and complex animations.
In this post we’ll go over the steps to make it happen:
- Simplify: make animations easy to write
- Beautify: make the syntax more beautiful
- Extend: support for constraints
- Encapsulate: change the animation engine to Facebook Pop.
- Ready: prepare to support spring animations
- Result: final code in a Github repository
Let’s start with refactoring this code:
To this code:
Since this code is functional oriented (it’s self contained), it’s now easy to refactor the function into a service:
And now hiding the tableView looks like this:
Great! That takes care of the code reuse! Using AnimationManager makes sure we keep our look and feel consistent — all the animations will look the same!
But this code is very rigid. The duration is a magic number, but not only for us the programmers — it’s also for our designers! They take the duration part very seriously. It’s all in the tiny details :-)
A designer may want to have a duration palette — a small set of animation durations that will be used repeatedly all over the app (similar to a color palette).
Let’s take the duration out as a parameter:
But this enables a developer to pass in a wrong duration! We might lose the magic.
Here is a solution:
Let’s set an enum for a small set of options: 0.25 seconds, 1 second and let’s add a variable duration in case we want to test something:
When developers would use the hide function, they would be motivated to use one of the predefined durations :-)
Now, for some view, myView, we can hide it with animation like so:
Let’s be a little bit more of a perfectionist — I would like to write it like so:
And here is how we are going to make it happen:
- We added an init to AnimationManager to hold a weak reference to a view instance.
- We removed the static modifier from the hide function and use the view as a field, not as a parameter — we lose some “functionality” there.
- Finally, we added an extension to UIView so we can call the fade out animation on every view!
So we moved from this code:
To this code:
Here is what we achieved so far:
- Removed repetition of code
- Reduced boilerplate code
- Easy API
- Motivated the developers (and the designers) to follow a consistent design and feel
Our next steps will be to upgrade AnimationManager to support the next features:
- Extend to other types like constraints and layers!
- We can add different animation variations like we mentioned at the beginning of the article like “Spring” and “Decay”. So far we described only “Basic” animations.
- In the above examples we use duration palette for animation, but this idea can be useful for other properties like timing function.
- Hides the implementation: We could have used some other animation engine, like Facebook Pop (Using this engine will allow us to interrupt animations in a very precise manner). A change in the engine will not affect the interface of AnimationManager. Once we move to iOS 10 we can choose to stop using Facebook Pop and switch back to the new UIKit API featured at WWDC 2016. This will only affect the AnimationManager file.
The above changes will allow us to achieve this final example:
OK! Let’s start to add support for constraints to our AnimationManager — first we need to refactor our code a little:
- We created a new inner struct named View and moved the init and the hide function to it. We can now add a new similar struct named Constraint to handle animations for constraints.
- We’ve made the extension’s property return the new type View instead of AnimationManager.
Now we can add a new inner struct named Constraint to AnimationManager:
This struct has a very similar design to the struct View. We added here the ability to animate the size of a given constraint. All we need to add is an extension:
And here is how we use it:
Similarly we can add support for Layers to AnimationManager. I will add that to our final example.
Different animation engine:
First let’s replace the hide function with a more generic function alpha that will animate the alpha property and could also animate showing (alpha = 1) or hiding (alpha = 0) the view:
Here I decided to show how I use a different animation engine — Facebook Pop as an example of how choosing a different animation engine doesn’t affect the use of AnimationManager.
This function also supports a delay property named beginTime, and a completion closure.
So far this is what we have:
Ready: prepare for Spring animation
So far we implemented only basic animations. I would like to specify the type of the animation like so:
So first let’s specify that all the animations we used are basic animations:
- I’ve converted all the animations to use Facebook pop — so it will be interruptible.
- Create a class BasicAnimation that represent a generic basic animation.
It has one function animation() that returns a basic animation object.
- Change struct View to have an inner class Basic that inherits from the class BasicAnimation. I moved the alpha animation to class Basic. View holds an instance of class Basic.
- Do the same thing to struct Constraint.
Now we can write this:
Similarly we can add support for Spring animations, once we’ll do that we’ll have everything we need to write the final animation!
The example code from the video above can be found in this github repository. The code has support for layers and Spring animations.
AnimationManager gives us a simpler way to write animations while hiding the animation engine behind it.
I hope you’ll give this code a try, if you have any questions or comments, please join the discussion below!