Swift: UIView Animation Syntax Sugar

Because closures make ugly couples

class func animate(withDuration duration: TimeInterval,            
    animations: @escaping () -> Void,          
    completion: ((Bool) -> Void)? = nil)

Trailing Closure

UIView.animate(withDuration: 0.3, animations: {
    // Animations
}) { finished in
    // Compeleted
}
}) { finished in // yuck

Indentation for readabilty

One could also argue against the default indentation for the animation closures because they’re both at the same level as the declaration. Lately I’ve been drinking the functional programming kool-aid big time, and one thing I absolutely love about writing functional code is how we list sequences of commandes in a bullet point format:

[0, 1, 2, 4, 5, 6]
    .sorted { $0 < $1 } 
    .map { $0 * 2 }
    .forEach { print($0) }

Forcing ugly to be beautiful

UIView.animate(withDuration: 0.3,
    animations: {
        // Animations
    },
    completion: { finished in
        // Compeleted
    })

Passing closures

let animations = {
    // Animate
}let completion = { (finished: Bool) in
    // Completion
}UIView.animate(withDuration: 0.3,
               animations: animations,
               completion: completion)

The solution

As most programmers do, I forced myself into creating a solution for a relatively mundane problem under the promise to myself that it would “save time in the long run”.

UIView.Animator(duration: 0.3)
    .animations { 
        // Animations
    }
    .completion { finished in
        // Completion
    }
    .animate()

“It will save time in the long run!”

Animator

class Animator {
    typealias Animations = () -> Void
    typealias Completion = (Bool) -> Void    private var animations: Animations
    private var completion: Completion?
    private let duration: TimeInterval    init(duration: TimeInterval) {
        self.animations = {}
        self.completion = nil
        self.duration = duration
    }...
func animations(_ animations: @escaping Animations) -> Self {
    self.animations = animations
    return self
}func completion(_ completion: @escaping Completion) -> Self {
    self.completion = completion
    return self
}

Returning Self

The cool thing is that these APIs return an instance of Self, which is the true magic here. Because we’re returning Self, we’re able to create the sequence-style API.

let numbers = 
    [0, 1, 2, 4, 5, 6]  // Returns Array
    .sorted { $0 < $1 } // Returns Array
    .map { $0 * 2 }     // Returns Array
[0, 1, 2, 4, 5, 6]         // Returns Array
    .sorted { $0 < $1 }    // Returns Array
    .map { $0 * 2 }        // Returns Array
    .forEach { print($0) } // Returns Void

Animating

func animate() {
    UIView.animate(withDuration: duration, 
        animations: animations, 
        completion: completion)
}

Extending UIView

extension UIView {    class Animator { ...

Options

UIView.Animator(duration: 0.3, delay: 0, options: [.autoreverse])UIView.SpringAnimator(duration: 0.3, delay: 0.2, damping: 0.2, velocity: 0.2, options: [.autoreverse, .curveEaseIn])

Swift Programming

The Swift Programming Language

Andyy Hope

Written by

Australian iOS Engineer living in SF, Blogger, Speaker, Organiser of Playgrounds Conference, Founder of Wu-Tang Clang

Swift Programming

The Swift Programming Language