Animator: easy trick to make UIKit animations reusable

Fighting duplicate animation code in two easy steps

Oleg Dreyman
3 min readNov 14, 2021

People sometimes come to me on the street and ask:

“Mate, what are the two most important things that define the character of a mobile app?”

And whenever that happens, I answer “oh boy, what a silly question. Everybody know that those are animations and code reusability!”.

Incredible, huh?

So in this post, I’ll try to explain how we can make those two things work very nicely together.

The Problem

Let’s say after years of experimentation, you finally came up with an absolutely perfectly tweaked spring animation for your app using nothing but default UIView.animate parameters. Let’s say, hypothetically, that the code is something like this:

UIView.animate(
withDuration: 0.4,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 1.75,
options: [.curveEaseInOut, .beginFromCurrentState, .allowUserInteraction]
) {
self.view.center.y += 50
} completion: { (isCompleted) in
// completion code
}

Well, if you absolutely nail it and you want to start using this animation everywhere in your app, you suddenly start seeing yourself copying and pasting this snippet everywhere. And we both know that copy-pasted code is no good.

Many people try to solve this by creating a centralized “constants” type, where one can store their perfectly-tuned parameters and quickly grab them when needed. Something like this:

UIView.animate(
withDuration: AnimationDefaults.defaultDuration,
delay: 0,
usingSpringWithDamping: AnimationDefaults.defaultSpringDamping,
initialSpringVelocity: AnimationDefaults.defaultSpringVelocity,
options: AnimationDefaults.defaultOptions
) {
self.view.center.y += 50
} completion: { (isCompleted) in
// completion code
}

But while this is better than copy-pasted code, it’s still somewhat the same. Cause if one day you’ll want to migrate your animations to UIViewPropertyAnimator, well… you’ll have yourself a problem.

The Solution: “Animator” struct

Instead, here’s what we came up with. First, let’s create a new type called Animator:

struct Animator {
typealias Animations = () -> ()
typealias Completion = (Bool) -> ()

let perform: (@escaping Animations, Completion?) -> ()
}

And with that, a simple extension on a UIView to mirror the usual UIView.animate as closely as possible:

extension UIView {
static func animate(with animator: Animator, animations: @escaping () -> (), completion: ((Bool) -> ())? = nil) {
animator.perform(animations, completion)
}
}

I hope by now you understand where we’re going with this. Now adding a reusable, perfectly tuned spring animation is simple:

extension Animator {
static let defaultSpring = Animator { (animations, completion) in
UIView.animate(
withDuration: 0.4,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 1.75,
options: [.curveEaseInOut, .beginFromCurrentState, .allowUserInteraction],
animations: animations,
completion: completion
)
}
}

So as you see, we’re simply wrapping our spring animation in a reusable block of code. The usage?

UIView.animate(with: .defaultSpring) {
self.view.center.y += 50
} completion: { (isCompleted) in
// completion code
}

Now it’s not just reusable, but also incredibly clean and easy to read.

With this technique, you can create as many reusable animations as you want.

One More Thing

Often, you want your methods that perform animations to have an animated: Bool parameter. Well, you probably remember how much of a pain that is with custom animations, right? Fret no more, here’s a neat little trick using the Animator:

extension Animator {
static let noAnimation = Animator { (animations, completion) in
animations()
completion?(true)
}
}

And the usage:

func moveView(animated: Bool) {
UIView.animate(with: animated ? .defaultSpring : .noAnimation) {
self.view.center.y += 50
} completion: { (isCompleted) in
// completion code
}
}

Hopefully, you find this as cool as I do.

--

--

Oleg Dreyman

iOS development know-it-all. Talk to me about Swift, coffee, photography & motorsports.