Skip to content

Instantly share code, notes, and snippets.

@dispatchMain
Last active December 30, 2018 09:29
Show Gist options
  • Save dispatchMain/0d7051ec508feca26fd67c3ca5a22f0b to your computer and use it in GitHub Desktop.
Save dispatchMain/0d7051ec508feca26fd67c3ca5a22f0b to your computer and use it in GitHub Desktop.
Declarative Animation in Swift
//: Playground - noun: a place where people can play
// This example is inspired from John Sundell's article on building declarative animation framework
// at https://www.swiftbysundell.com/posts/building-a-declarative-animation-framework-in-swift-part-1.
// I have made some modifications to be able to pass parallel and serial animations just by adding
// then in different arrays. Jump to end of file to see final syntax.
import UIKit
import PlaygroundSupport
public struct Animation {
public let duration: TimeInterval
public let closure: (UIView) -> Void
public var concurrentAnimations: [Animation]?
init(duration: TimeInterval, closure: @escaping (UIView) -> Void) {
self.duration = duration
self.closure = closure
}
init(animations: [Animation]) {
self.init(duration: 0, closure: {debugPrint($0)})
self.concurrentAnimations = animations
}
}
public extension Animation {
static func fadeIn(duration: TimeInterval = 3.3) -> Animation {
return Animation(duration: duration, closure: {$0.alpha = 1})
}
static func fadeOut(duration: TimeInterval = 3.3) -> Animation {
return Animation(duration: duration, closure: {$0.alpha = 0.0})
}
static func resize(to size: CGSize, duration: TimeInterval = 3.3) -> Animation {
return Animation(duration: duration, closure: {$0.bounds.size = size})
}
static func concurrent(_ animations: [Animation]) -> Animation {
return Animation(animations: animations)
}
}
public extension UIView {
public func animate(_ animations: [Animation]) {
guard !animations.isEmpty else {
return
}
var animations = animations
let animation = animations.removeFirst()
if let concurrentAnimations = animation.concurrentAnimations {
animate(inParallel: concurrentAnimations, completion: { _ in
self.animate(animations)
})
} else {
UIView.animate(withDuration: animation.duration, animations: {
animation.closure(self)
}) { _ in
self.animate(animations)
}
}
}
private func animate(inParallel animations: [Animation], completion: @escaping (Bool) -> Void) {
let longestAnimation = animations.map({$0.duration}).max()
for animation in animations {
UIView.animate(withDuration: animation.duration, animations: {
animation.closure(self)
})
}
if let longestAnimation = longestAnimation {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + longestAnimation, execute: {
completion(true)
})
}
}
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
view.backgroundColor = .gray
PlaygroundPage.current.liveView = view
let animationView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
animationView.backgroundColor = .red
animationView.alpha = 0
view.addSubview(animationView)
// Here we add the animations in declarative syntax
animationView.animate([.fadeIn(),
.resize(to: CGSize(width: 200, height: 200)),
.concurrent([.fadeOut(),
.resize(to: CGSize(width: 50, height: 50))]),
.concurrent([.resize(to: CGSize(width: 200, height: 200)),
.fadeIn()])])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment