-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Much cleaner implementation of animation performing * Reorganizing code structure * Automatically add completion handler in builder tests * Rename automatic builder test * Remove old-style code 👋
- Loading branch information
1 parent
67bfd64
commit 063641d
Showing
22 changed files
with
697 additions
and
1,106 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import UIKit | ||
|
||
/// Performs an animation with the provided duration in seconds. Includes properties to set `UIView.AnimationOptions` and | ||
/// even a `CAMediaTimingFunction` to apply to the interpolation of the animated values changed in the ``changes`` closure. | ||
public struct Animate: Animation, SequenceAnimatable, GroupAnimatable { | ||
public let duration: TimeInterval | ||
|
||
public internal(set) var changes: () -> Void | ||
public internal(set) var options: UIView.AnimationOptions? | ||
public internal(set) var timingFunction: CAMediaTimingFunction? | ||
|
||
/// Creates a new animation, animating the properties updated in the ``changes`` closure | ||
/// | ||
/// Only the `duration` parameter is required, all other properties can be added or modified using ``AnimationModifiers``. | ||
/// | ||
/// - Tip: AnimationPlanner provides numerous animation curves through a `CAMediaTimingFunction` extension. | ||
/// Type a period for the `timingFunction` parameter to see what is readily available. Have you tried `.quintOut` yet? | ||
/// | ||
/// - Parameters: | ||
/// - duration: Duration of animation, measured in seconds | ||
/// - timingFunction: Optional `CAMediaTimingFunction` to interpolate animated values with. | ||
/// - changes: Closure executed when the animation is performed | ||
public init( | ||
duration: TimeInterval, | ||
timingFunction: CAMediaTimingFunction? = nil, | ||
changes: @escaping () -> Void = {} | ||
) { | ||
self.duration = duration | ||
self.timingFunction = timingFunction | ||
self.changes = changes | ||
} | ||
} | ||
|
||
extension Animate: PerformsAnimations { | ||
public func animate(delay leadingDelay: TimeInterval, completion: ((Bool) -> Void)?) { | ||
let timing = timingParameters(leadingDelay: leadingDelay) | ||
let createAnimations: (((Bool) -> Void)?) -> Void = { completion in | ||
UIView.animate( | ||
withDuration: timing.duration, | ||
delay: timing.delay, | ||
options: options ?? [], | ||
animations: changes, | ||
completion: completion | ||
) | ||
} | ||
|
||
if let timingFunction = timingFunction { | ||
CATransaction.begin() | ||
CATransaction.setAnimationDuration(duration) | ||
CATransaction.setAnimationTimingFunction(timingFunction) | ||
|
||
createAnimations(completion) | ||
|
||
CATransaction.commit() | ||
} else { | ||
createAnimations(completion) | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import UIKit | ||
|
||
/// Performs an animation after a delay, only to be used in a context where other animations are run simultaneously | ||
public struct AnimateDelayed<Delayed: Animatable>: AnimationContainer, DelayedAnimatable, GroupAnimatable { | ||
|
||
public internal(set) var animation: Delayed | ||
|
||
public var duration: TimeInterval { | ||
return delay + originalDuration | ||
} | ||
|
||
public var originalDuration: TimeInterval { | ||
if let delayed = animation as? DelayedAnimatable { | ||
return delayed.originalDuration | ||
} | ||
return animation.duration | ||
} | ||
|
||
public let delay: TimeInterval | ||
|
||
internal init(delay: TimeInterval, animation: Delayed) { | ||
self.animation = animation | ||
self.delay = delay | ||
} | ||
} | ||
|
||
extension AnimateDelayed where Delayed: DelayedAnimatable { | ||
public var duration: TimeInterval { | ||
delay + animation.originalDuration | ||
} | ||
} | ||
|
||
extension AnimateDelayed where Delayed == Animate { | ||
/// Adds a delay to your animation. Can only be added in a ``Group`` context where animations should be performed simultaneously. | ||
/// - Parameters: | ||
/// - delay: Delay in seconds to add to your animation | ||
/// - duration: Duration of animation, measured in seconds | ||
/// - changes: Closure executed when the animation is performed | ||
public init( | ||
delay: TimeInterval, | ||
duration: TimeInterval, | ||
changes: @escaping () -> Void = {} | ||
) { | ||
let animation = Animate(duration: duration, changes: changes) | ||
self.init(delay: delay, animation: animation) | ||
} | ||
} | ||
|
||
extension AnimateDelayed: Animation where Delayed: Animation { } | ||
extension AnimateDelayed: SpringAnimatable where Delayed: SpringAnimatable { } | ||
|
||
extension AnimateDelayed: PerformsAnimations where Contained: PerformsAnimations { | ||
public func animate(delay leadingDelay: TimeInterval, completion: ((Bool) -> Void)?) { | ||
animation.animate(delay: delay + leadingDelay, completion: completion) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import UIKit | ||
|
||
/// Performs an animation with spring dampening applied, using the same values as UIView spring animations | ||
public struct AnimateSpring<Springed: Animation>: SpringAnimatable, AnimationContainer, GroupAnimatable { | ||
|
||
public internal(set) var animation: Springed | ||
|
||
public let dampingRatio: CGFloat | ||
public let initialVelocity: CGFloat | ||
|
||
internal init(dampingRatio: CGFloat, initialVelocity: CGFloat, animation: Springed) { | ||
self.animation = animation | ||
self.dampingRatio = dampingRatio | ||
self.initialVelocity = initialVelocity | ||
} | ||
} | ||
|
||
extension AnimateSpring where Springed == Animate { | ||
/// Creates a spring-based animation with the expected damping and velocity values. | ||
/// - Parameters: | ||
/// - damping: Value between 0 and 1, same as damping ratio used for `UIView`-based spring animations | ||
/// - initialVelocity: Relative velocity of animation, defined as full extend of animation per second | ||
/// - duration: Duration of animation, measured in seconds | ||
/// - changes: Closure executed when the animation is performed | ||
public init( | ||
duration: TimeInterval, | ||
dampingRatio: CGFloat, | ||
initialVelocity: CGFloat = 0, | ||
changes: @escaping () -> Void = {} | ||
) { | ||
let animation = Animate(duration: duration, changes: changes) | ||
self.init(dampingRatio: dampingRatio, initialVelocity: initialVelocity, animation: animation) | ||
} | ||
} | ||
|
||
extension AnimateSpring: SequenceAnimatable, SequenceConvertible where Contained: SequenceAnimatable { | ||
public func asSequence() -> [SequenceAnimatable] { [self] } | ||
} | ||
|
||
extension AnimateSpring: Animation where Springed: Animation { } | ||
extension AnimateSpring: DelayedAnimatable where Springed: DelayedAnimatable { } | ||
|
||
extension AnimateSpring: PerformsAnimations { | ||
public func animate(delay leadingDelay: TimeInterval, completion: ((Bool) -> Void)?) { | ||
let timing = timingParameters(leadingDelay: leadingDelay) | ||
UIView.animate( | ||
withDuration: timing.duration, | ||
delay: timing.delay, | ||
usingSpringWithDamping: dampingRatio, | ||
initialSpringVelocity: initialVelocity, | ||
options: animation.options ?? [], | ||
animations: animation.changes, | ||
completion: completion | ||
) | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
Sources/AnimationPlanner/Animations/AnimationBuilder.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/// Result builder through which either sequence or group animations can be created. Add `@AnimationBuilder` to a closure or method to provide your own animations. | ||
/// The result of your builder function should be an `Array` of either ``SequenceAnimatable`` or ``GroupAnimatable``. | ||
@resultBuilder | ||
public struct AnimationBuilder { } | ||
|
||
extension AnimationBuilder { | ||
public static func buildBlock(_ components: SequenceConvertible...) -> [SequenceAnimatable] { | ||
components.flatMap { $0.asSequence() } | ||
} | ||
|
||
public static func buildArray(_ components: [SequenceConvertible]) -> [SequenceAnimatable] { | ||
components.flatMap { $0.asSequence() } | ||
} | ||
|
||
public static func buildOptional(_ component: SequenceConvertible?) -> [SequenceAnimatable] { | ||
component.map { $0.asSequence() } ?? [] | ||
} | ||
public static func buildEither(first component: SequenceConvertible) -> [SequenceAnimatable] { | ||
component.asSequence() | ||
} | ||
public static func buildEither(second component: SequenceConvertible) -> [SequenceAnimatable] { | ||
component.asSequence() | ||
} | ||
} | ||
|
||
extension AnimationBuilder { | ||
public static func buildBlock(_ components: GroupConvertible...) -> [GroupAnimatable] { | ||
components.flatMap { $0.asGroup() } | ||
} | ||
|
||
public static func buildArray(_ components: [GroupConvertible]) -> [GroupAnimatable] { | ||
components.flatMap { $0.asGroup() } | ||
} | ||
|
||
public static func buildOptional(_ component: GroupConvertible?) -> [GroupAnimatable] { | ||
component.map { $0.asGroup() } ?? [] | ||
} | ||
public static func buildEither(first component: GroupConvertible) -> [GroupAnimatable] { | ||
component.asGroup() | ||
} | ||
public static func buildEither(second component: GroupConvertible) -> [GroupAnimatable] { | ||
component.asGroup() | ||
} | ||
} |
Oops, something went wrong.