Server-provided animations in iOS apps
Hi everyone! About six months ago we launched one of Badoo«s most exciting features: Live Streaming. One of its main functionalities is that viewers can send gifts to their favourite streamers to express their appreciation. We wanted to make the gifts as fancy and as engaging as possible, so it was decided to make some of them really lively, and by this I mean animated. And to engage people even more, we, the Badoo team, planned to update those gifts and animations every few weeks.
As an iOS engineer, you might have already guessed the challenge we faced here: the need to add new animations and remove the old ones was going to require a fair amount of work from the client side. We«d need both the Android and the iOS development teams for every release — which, when combined with the amount of time App Store reviews and approval often take, would mean it might be days before each update could go live. But we solved the problem, and I«m going to explain to you how.
By this stage, we already knew how to export Adobe After Effects (AAE) animations into the format readable by our iOS app using the Lottie library. This time though, we went a bit further: we decided to create a kind of animation storage service, available via the internet. In other words, we would store all the actual animations on the server and deliver them to the client apps on demand:
Here is what the final solution looks like in the iOS simulator on the developer«s machine:
However, in this post, the example I«m going to use is a very simple animation that I created myself. It«s not as fancy as Badoo«s one, but it«s good enough to demonstrate the potential of the described approach.
The Adobe After Effects (AAE) project of animations I«m using here can be found along with other source files on github. So, after opening the AAE animation project located in _raw/animations/Fancy/Fancy.aep
, you should see a window like this:
At this point, I«m not going to go into how animations are being created in AEE, but what I«m going to explain is how to import already existing animations from AAE into an iOS app-readable format using the Bodymovin plugin.
Having made sure the plugin is installed, open it by selecting the Window/Extensions/Bodymovin option in the menu:
Now you should see the Bodymovin window where you can select the animation you wish to export, specify the output file path and then open export settings:
Having selected and opened animation settings, we can now ask Bodymovin to embed the assets into the resulting JSON file by checking the Assets / Include in json option:
Finally, the selected animation composition is exported and saved to the specified file by clicking the Render button.
Let«s assume we have moved our rendered animations JSON files onto our preferred web server via the Internet. In our case, for simplicity«s sake, I«ve uploaded them into this project«s github repository. The animations are available here:
Base URL: https://raw.githubusercontent.com/chupakabr/server-provided-animations/master/_raw/rendered-animations/
Animation-specific IDs:
clouds.json
fireworks.json
Note: Looking for an animations provider web server written in Swift? Find the solution here on github and a detailed explanation in this article.
At this point, we have a fully-functional animations-provider server, so it«s time to move onto the most exciting part: presenting the animations to our users.
At this point I strongly recommend opening our example iOS app project located at Client/ServerProvidedAnimation.xcworkspace
as it already has all the necessary boilerplate code and configurations.
Loading animations data
Given that the REST API endpoints for obtaining animation data are now up and running, it«s time to introduce the data provider protocol and add its server implementation:
import Lottie
protocol AnimationsProviderProtocol {
typealias Completion = (_ animation: LOTComposition?) -> Void
func loadAnimation(byId id: String, completion: @escaping Completion)
}
final class ServerAnimationProvider: AnimationsProviderProtocol {
private let endpoint: URL
init(endpoint: URL) {
self.endpoint = endpoint
}
func loadAnimation(byId id: String, completion: @escaping Completion) {
let path = "/\(id).json"
guard let animationUrl = URL(string: path, relativeTo: self.endpoint) else {
completion(nil)
return
}
URLSession.shared.invalidateAndCancel()
let task = URLSession.shared.dataTask(with: animationUrl) { (data, response, error) in
guard error == nil, let data = data, let json = self.parseJson(from: data) else {
completion(nil)
return
}
let animation = LOTComposition(json: json)
completion(animation)
}
task.resume()
}
private func parseJson(from data: Data?) -> [AnyHashable : Any]? {
guard let data = data else { return nil }
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [AnyHashable : Any]
return json
} catch {
return nil
}
}
}
This data provider class allows us to load animations from the server in JSON format on demand and hold them in memory for rendering on the UI. Assuming we are following the MVVM pattern, it can easily be used in the ViewModel
entity in the following way:
// ...
private let animationProvider: AnimationsProviderProtocol
private(set) var animationModel: LOTComposition?
// …
func loadAnimation(byId animationId: String) {
self.animationProvider.loadAnimation(byId: animationId) { [weak self] (animationModel) in
self?.animationModel = animationModel
}
}
// ...
The ViewModel
updates the selected animation data property when it receives a valid HTTP response from the server with a non-empty JSON object inside. This data is used by the presentation layer to schedule the animation rendering.
Presentation layer
Now we can use our ViewModel to access the animation data and present it via the UI in the «on tap» action handler attached to the button:
class ViewController: UIViewController {
// ...
@IBOutlet weak var animationContainer: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// ...
self.animationView = {
let view = LOTAnimationView(frame: self.animationContainer.bounds)
self.animationContainer.addSubview(view)
return view
}()
}
@IBAction func onPlayAnimationAction(_ sender: Any) {
self.animationView.stop()
self.animationView.sceneModel = self.viewModel.animationModel
self.animationView.play()
}
}
Basically, what we have here is a button handler which triggers an update of the LOTAnimationView instance with the most recent animation data coming from the ViewModel
.
Here«s what the final result looks like:
That«s pretty much it. Animations are now being loaded from the prepared REST API endpoint and rendered on the client on demand.
Tips and tricks:
- AAE allows the use of most asset types including raster and vector graphics;
- Bodymovin makes it possible to embed all assets into an output JSON animation file (using base64 encoding) — this means we can avoid separate assets loading on the client side;
- For the animations, you have the choice between drawing straight into the vector in AAE, or simply importing Adobe Illustrator vector graphics.
Unfortunately, it«s not yet possible to import SVG vector graphics into AAE (I«ve tried!).
More tricks and potential problems are described in this amazing article written by my colleague Radoslaw Cieciwa.
So, what does using server-provided animations give us? The most obvious benefit of this approach is the ability to decouple all stakeholders of the animations update flow. In other words, to release a fancy new animation, all designers have to do is to provide the animation«s JSON representation to the server team. And to remove one, the server team just has to remove that particular animation from the discovery service. No time wasted!
Another cool thing is that the same functionality can be implemented on all supported client platforms (iOS, Android, Web,…) without having to adjust the existing server functionality or raw animations.
That«s it for today! Thanks for reading
Resources