/**
* BulletinBoard
* Copyright (c) 2017 - present Alexis Aubry. Licensed under the MIT license.
*/
import Foundation
import UIKit
// MARK: AnimationChain
/**
* A sequence of animations where animations are executed the one after the other.
*
* Animations are represented by `AnimationPhase` objects, that contain the code of the animation,
* its duration relative to the chain duration, their curve and their individual completion handlers.
*/
public class AnimationChain {
/// The total duration of the animation chain.
public let duration: TimeInterval
/// The initial delay before the animation chain starts.
public var initialDelay: TimeInterval = 0
/// The code to execute after animation chain is executed.
public var completionHandler: () -> Void
/// Whether the chain is being run.
public private(set) var isRunning: Bool = false
// MARK: Initialization
private var animations: [AnimationPhase] = []
private var didFinishFirstAnimation: Bool = false
/**
* Creates an animation chain with the specified duration.
*/
public init(duration: TimeInterval) {
self.duration = duration
self.completionHandler = {}
}
// MARK: - Interacting with the Chain
/**
* Add an animation at the end of the chain.
*
* You cannot add animations if the chain is running.
*
* - parameter animation: The animation phase to add.
*/
public func add(_ animation: AnimationPhase) {
precondition(!isRunning, "Cannot add an animation to the chain because it is already performing.")
animations.append(animation)
}
/**
* Starts the animation chain.
*/
public func start() {
precondition(!isRunning, "Animation chain already running.")
isRunning = true
performNextAnimation()
}
private func performNextAnimation() {
guard animations.count > 0 else {
completeGroup()
return
}
let animation = animations.removeFirst()
let duration = animation.relativeDuration * self.duration
let options = UIView.AnimationOptions(rawValue: UInt(animation.curve.rawValue << 16))
let delay: TimeInterval = didFinishFirstAnimation ? 0 : initialDelay
UIView.animate(withDuration: duration, delay: delay, options: options, animations: animation.block) { _ in
self.didFinishFirstAnimation = true
animation.completionHandler()
self.performNextAnimation()
}
}
private func completeGroup() {
isRunning = false
completionHandler()
}
}
// MARK: - AnimationPhase
/**
* A member of an `AnimationChain`, representing a single animation.
*
* Set the `block` property to a block containing the animations. Set the `completionHandler` with
* a block to execute at the end of the animation. The default values do nothing.
*/
public class AnimationPhase {
/**
* The duration of the animation, relative to the total duration of the chain.
*
* Must be between 0 and 1.
*/
public let relativeDuration: TimeInterval
/**
* The animation curve.
*/
public let curve: UIView.AnimationCurve
/**
* The animation code.
*/
public var block: () -> Void
/**
* A block to execute at the end of the animation.
*/
public var completionHandler: () -> Void
// MARK: Initialization
/**
* Creates an animtion phase object.
*
* - parameter relativeDuration: The duration of the animation, as a fraction of the total chain
* duration. Must be between 0 and 1.
* - parameter curve: The animation curve
*/
public init(relativeDuration: TimeInterval, curve: UIView.AnimationCurve) {
self.relativeDuration = relativeDuration
self.curve = curve
self.block = {}
self.completionHandler = {}
}
}