Last active
November 7, 2023 01:28
-
-
Save andyyhope/e6318b1735a4fb85e6a61762ec46c8ba to your computer and use it in GitHub Desktop.
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
//: | |
//: UIView Animation Syntax Sugar | |
//: | |
//: Created by Andyy Hope on 18/08/2016. | |
//: Twitter: @andyyhope | |
//: Medium: Andyy Hope, https://medium.com/@AndyyHope | |
import UIKit | |
extension UIView { | |
class Animator { | |
typealias Completion = (Bool) -> Void | |
typealias Animations = () -> Void | |
private var animations: Animations | |
private var completion: Completion? | |
private let duration: TimeInterval | |
private let delay: TimeInterval | |
private let options: UIViewAnimationOptions | |
init(duration: TimeInterval, delay: TimeInterval = 0, options: UIViewAnimationOptions = []) { | |
class Animator { | |
typealias Completion = (Bool) -> Void | |
typealias Animations = () -> Void | |
fileprivate var animations: Animations | |
fileprivate var completion: Completion? | |
fileprivate let duration: TimeInterval | |
fileprivate let delay: TimeInterval | |
fileprivate let options: UIViewAnimationOptions | |
init(duration: TimeInterval, delay: TimeInterval = 0, options: UIViewAnimationOptions = []) { | |
self.animations = {} | |
self.completion = nil | |
self.duration = duration | |
self.delay = delay | |
self.options = options | |
} | |
func animations(_ animations: @escaping Animations) -> Self { | |
self.animations = animations | |
return self | |
} | |
func completion(_ completion: @escaping Completion) -> Self { | |
self.completion = completion | |
return self | |
} | |
func animate() { | |
UIView.animate(withDuration: duration, delay: delay, animations: animations, completion: completion) | |
} | |
} | |
final class SpringAnimator: Animator { | |
fileprivate let damping: CGFloat | |
fileprivate let velocity: CGFloat | |
init(duration: TimeInterval, delay: TimeInterval = 0, damping: CGFloat, velocity: CGFloat, options: UIViewAnimationOptions = []) { | |
self.damping = damping | |
self.velocity = velocity | |
super.init(duration: duration, delay: delay, options: options) | |
} | |
override func animate() { | |
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: options, animations: animations, completion: completion) | |
} | |
} | |
} | |
// MARK: - Example API | |
var view = UIView(frame: .zero) | |
// Regular Animations | |
UIView.Animator(duration: 0.3) | |
.animations { | |
view.frame.size.height = 100 | |
view.frame.size.width = 100 | |
} | |
.completion { finished in | |
view.backgroundColor = .black | |
} | |
.animate() | |
// Regular Animations with options | |
UIView.Animator(duration: 0.4, delay: 0.2) | |
.animations { } | |
.completion { _ in } | |
.animate() | |
UIView.Animator(duration: 0.4, options: [.autoreverse, .curveEaseIn]) | |
.animations { } | |
.completion { _ in } | |
.animate() | |
UIView.Animator(duration: 0.4, delay: 0.2, options: [.autoreverse, .curveEaseIn]) | |
.animations { } | |
.completion { _ in } | |
.animate() | |
// Spring Animator | |
UIView.SpringAnimator(duration: 0.3, delay: 0.2, damping: 0.2, velocity: 0.2, options: [.autoreverse, .curveEaseIn]) | |
.animations { } | |
.completion { _ in } | |
.animate() |
That's a much nicer interface to the existing UIView animations api, you did an awesome job.
I updated some existing code after looking at what you have done and the result is:
public extension UIView {
public class Animator {
public typealias Completion = (Bool) -> Void
public typealias Animations = () -> Void
public convenience init(duration: TimeInterval, delay: TimeInterval = 0, options: UIViewAnimationOptions = []) {
self.init(animationType: .regular(duration, delay, options))
}
public convenience init(duration: TimeInterval, delay: TimeInterval = 0, damping: CGFloat, velocity: CGFloat, options: UIViewAnimationOptions = []) {
self.init(animationType: .spring(duration, delay, damping, velocity, options))
}
public func animations(_ animations: @escaping Animations) -> Self {
self.animations = animations
return self
}
public func completion(_ completion: Completion?) -> Self {
self.completion = completion
return self
}
public func animate() {
switch self.animationType {
case let .regular(duration, delay, options):
UIView.animate(withDuration: duration, delay: delay, options: options, animations: self.animations, completion: self.completion)
case let .spring(duration, delay, dampingRatio, velocity, options):
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: dampingRatio, initialSpringVelocity: velocity, options: options, animations: self.animations, completion: self.completion)
}
}
// MARK: - Private stuff
private enum AnimationType {
case regular(TimeInterval, TimeInterval, UIViewAnimationOptions)
case spring(TimeInterval, TimeInterval, CGFloat, CGFloat, UIViewAnimationOptions)
}
private let animationType: AnimationType
private var animations: Animations = {}
private var completion: Completion? = nil
private init(animationType: AnimationType) {
self.animationType = animationType
}
}
}
I like to have the enum
instead of subclasses of the animator. This way the animator itself works like a single interface for a factory of animations. The enum
can also be extended to allow, for example, the setup of animations using CATransaction
s if you want to specify a custom timing function.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@rcritz You are right. It was probably left out for simplicity. We can just model a delay method after
animations(_ animations: @escaping Animations) -> Self
Think of it as "homework" to see if you understood the consept.