Skip to content

Instantly share code, notes, and snippets.

@andyyhope
Last active November 7, 2023 01:28
Show Gist options
  • Save andyyhope/e6318b1735a4fb85e6a61762ec46c8ba to your computer and use it in GitHub Desktop.
Save andyyhope/e6318b1735a4fb85e6a61762ec46c8ba to your computer and use it in GitHub Desktop.
//:
//: 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()
@wh1pch81n
Copy link

@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.

@vfn
Copy link

vfn commented Dec 11, 2016

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 CATransactions 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