Last active
December 30, 2018 09:29
-
-
Save dispatchMain/0d7051ec508feca26fd67c3ca5a22f0b to your computer and use it in GitHub Desktop.
Declarative Animation in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: 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