Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for Swift 5.8 #20

Merged
merged 5 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/AnimationPlanner/AnimationPlanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct AnimationPlanner {
/// - Returns: Instance of ``RunningSequence`` to keep track of and stop animations
@discardableResult
public static func plan(
@AnimationBuilder animations builder: () -> [SequenceAnimatable]
@SequenceBuilder animations builder: () -> [SequenceAnimatable]
) -> RunningSequence {
RunningSequence(animations: builder())
.animate()
Expand All @@ -51,7 +51,7 @@ public struct AnimationPlanner {
/// - Returns: Instance of ``RunningSequence`` to keep track of and stop animations
@discardableResult
public static func group(
@AnimationBuilder animations builder: () -> [GroupAnimatable]
@GroupBuilder animations builder: () -> [GroupAnimatable]
) -> RunningSequence {
plan {
Group(animations: builder)
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnimationPlanner/Animations/AnimateSpring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ extension AnimateSpring where Springed == Animate {
}

extension AnimateSpring: SequenceAnimatable, SequenceConvertible where Contained: SequenceAnimatable {
public func asSequence() -> [SequenceAnimatable] { [self] }
public func animations() -> [SequenceAnimatable] { [self] }
}

extension AnimateSpring: Animation where Springed: Animation { }
Expand Down
31 changes: 13 additions & 18 deletions Sources/AnimationPlanner/Animations/AnimationBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
/// 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 struct SequenceBuilder {
public static func buildBlock(_ components: SequenceConvertible...) -> [SequenceAnimatable] {
components.flatMap { $0.asSequence() }
components.flatMap { $0.animations() }
}

public static func buildArray(_ components: [SequenceConvertible]) -> [SequenceAnimatable] {
components.flatMap { $0.asSequence() }
components.flatMap { $0.animations() }
}

public static func buildOptional(_ component: SequenceConvertible?) -> [SequenceAnimatable] {
component.map { $0.asSequence() } ?? []
component.map { $0.animations() } ?? []
}
public static func buildEither(first component: SequenceConvertible) -> [SequenceAnimatable] {
component.asSequence()
component.animations()
}
public static func buildEither(second component: SequenceConvertible) -> [SequenceAnimatable] {
component.asSequence()
component.animations()
}
}

extension AnimationBuilder {
@resultBuilder
public struct GroupBuilder {
public static func buildBlock(_ components: GroupConvertible...) -> [GroupAnimatable] {
components.flatMap { $0.asGroup() }
components.flatMap { $0.animations() }
}

public static func buildArray(_ components: [GroupConvertible]) -> [GroupAnimatable] {
components.flatMap { $0.asGroup() }
components.flatMap { $0.animations() }
}

public static func buildOptional(_ component: GroupConvertible?) -> [GroupAnimatable] {
component.map { $0.asGroup() } ?? []
component.map { $0.animations() } ?? []
}
public static func buildEither(first component: GroupConvertible) -> [GroupAnimatable] {
component.asGroup()
component.animations()
}
public static func buildEither(second component: GroupConvertible) -> [GroupAnimatable] {
component.asGroup()
component.animations()
}
}
2 changes: 1 addition & 1 deletion Sources/AnimationPlanner/Animations/Extra.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import UIKit

/// Perfoms the provided handler in between your actual animations.
/// Performs the provided handler in between your actual animations.
/// Typically used for setting up state before an animation or creating side-effects like haptic feedback.
public struct Extra: SequenceAnimatable, GroupAnimatable {
public let duration: TimeInterval = 0
Expand Down
2 changes: 1 addition & 1 deletion Sources/AnimationPlanner/Animations/Group.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public struct Group: SequenceAnimatable {

/// Creates a new `Group` providing a way to perform multiple animations simultaneously, meaning all animations run at the same time.
/// - Parameter animations: Add each animation from within this closure. Animations added to a group should conform to ``GroupAnimatable``.
public init(@AnimationBuilder animations builder: () -> [GroupAnimatable]) {
public init(@GroupBuilder animations builder: () -> [GroupAnimatable]) {
self.init(animations: builder())
}
}
Expand Down
129 changes: 15 additions & 114 deletions Sources/AnimationPlanner/Animations/Loop.swift
Original file line number Diff line number Diff line change
@@ -1,128 +1,29 @@
import UIKit
import Foundation

@available(
*, unavailable,
message: "Loop convenience struct has been removed following type checking changes in Swift 5.8. Use a for-loops or the sequence extension method `mapSequence()` or `mapGroup()` instead"
)
/// Loop through a sequence or for a specified repeat count to easily repeat multiple animation.
///
/// Either create a `Loop` with the default initializer where you set `repeatCount` or use the static method ``through(_:animations:)-sb44`` to loop through an existing array.
///
/// ```swift
/// Loop(for: numberOfLoops) { index in
/// Animate(duration: 0.2) {
/// view.frame.origin += 10
/// }
/// Wait(0.5)
/// }
/// ```
public struct Loop<Looped> {
/// Total duration of loop. Sum of all animations when animated in sequence, duration of longest animation when animated in a group.
public var duration: TimeInterval
/// All animations created in the loop.
public let animations: [Looped]

fileprivate init(
repeatCount: Int,
@AnimationBuilder builder: (_ index: Int) -> [Looped]
) {
self.animations = (0..<repeatCount).flatMap(builder)

if Looped.self == SequenceAnimatable.self, let animations = animations as? [SequenceAnimatable] {
duration = animations.reduce(0, { $0 + $1.duration })
} else if Looped.self == GroupAnimatable.self, let animations = animations as? [GroupAnimatable] {
duration = animations.max(by: { $0.duration < $1.duration }).map(\.duration) ?? 0
} else {
fatalError("Animations provided through Loop don’t comform to any animatable type")
}
}

fileprivate static func map<S: Swift.Sequence>(
_ sequence: S,
@AnimationBuilder with builder: (S.Element) -> [Looped]
) -> [Looped] {
sequence.flatMap(builder)
}
}

extension Loop: SequenceConvertible where Looped == SequenceAnimatable {

public func asSequence() -> [SequenceAnimatable] {
animations
}

/// Creates a new Loop that repeats for the given amount of times.
/// - Parameters:
/// - repeatCount: How many times the loop should repeat. The index of each loop is provided as a argument in the `animations` closure
/// - animations: Add each animation from within this closure. Animations added to this loop should conform to ``SequenceAnimatable``
/// - Warning: This struct is no longer available. The same functionality can be achieved by using `for`-loop or the methods `mapSequence()` and `mapGroup()` on any Swift Sequence.
public struct Loop: SequenceAnimatable, GroupAnimatable {
public var duration: TimeInterval = 0
public init(
for repeatCount: Int,
@AnimationBuilder animations builder: (_ index: Int) -> [SequenceAnimatable]
) {
self.init(repeatCount: repeatCount, builder: builder)
}

/// Loop through a sequence of values, like objects in an array or a range of numbers
/// - Parameters:
/// - sequence: Sequence to loop through, each element will be handled in the `animations` closure
/// - animations: Add each animation from within this closure. Animations added to this loop should conform to ``SequenceAnimatable``
/// - Returns: Sequence of all animations created in the `animation` closure
@SequenceBuilder animations builder: (_ index: Int) -> [SequenceAnimatable]
) { }

public static func through<S: Swift.Sequence>(
_ sequence: S,
@AnimationBuilder animations builder: (S.Element) -> [SequenceAnimatable]
@SequenceBuilder animations builder: (S.Element) -> [SequenceAnimatable]
) -> [SequenceAnimatable] {
map(sequence, with: builder)
[]
}
}

extension Loop: GroupConvertible where Looped == GroupAnimatable {

public func asGroup() -> [GroupAnimatable] {
animations
}

/// Creates a new Loop that repeats for the given amount of times.
/// - Parameters:
/// - repeatCount: How many times the loop should repeat. The index of each loop is provided as a argument in the `animations` closure
/// - animations: Add each animation from within this closure. Animations added to this loop should conform to ``GroupAnimatable``
public init(
for repeatCount: Int,
@AnimationBuilder animations builder: (_ index: Int) -> [GroupAnimatable]
) {
self.init(repeatCount: repeatCount, builder: builder)
}

/// Loop through a sequence of values, like objects in an array or a range of numbers
/// - Parameters:
/// - sequence: Sequence to loop through, each element will be handled in the `animations` closure
/// - animations: Add each animation from within this closure. Animations added to this loop should conform to ``GroupAnimatable``
/// - Returns: Group of all animations created in the `animation` closure
public static func through<S: Swift.Sequence>(
_ sequence: S,
@AnimationBuilder animations builder: (S.Element) -> [GroupAnimatable]
) -> [GroupAnimatable] {
map(sequence, with: builder)
}
}

extension Loop: PerformsAnimations where Looped == GroupAnimatable {
public func animate(delay leadingDelay: TimeInterval, completion: ((Bool) -> Void)?) {
Group(animations: animations).animate(delay: leadingDelay, completion: completion)
}
}

extension Swift.Sequence {
/// Maps values from the sequence to animations
/// - Parameter animations: Add each animation from within this closure. Animations should conform to ``GroupAnimatable``
/// - Returns: Sequence of all animations created in the `animation` closure
public func mapAnimations(
@AnimationBuilder animations builder: (Element) -> [SequenceAnimatable]
) -> [SequenceAnimatable] {
flatMap(builder)
}

/// Maps values from the sequence to animations
/// - Parameter animations: Add each animation from within this closure. Animations added to this loop should conform to ``GroupAnimatable``
/// - Returns: Group of all animations created in the `animation` closure
public func mapAnimations(
@AnimationBuilder animations builder: (Element) -> [GroupAnimatable]
@SequenceBuilder animations builder: (S.Element) -> [GroupAnimatable]
) -> [GroupAnimatable] {
flatMap(builder)
[]
}
}
29 changes: 29 additions & 0 deletions Sources/AnimationPlanner/Animations/MapSequence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

extension Swift.Sequence {
@available(*, unavailable, renamed: "mapSequence", message: "use either mapSequence or mapGroup")
public func mapAnimations(
@SequenceBuilder animations builder: (Element) -> [SequenceAnimatable]
) -> [SequenceAnimatable] {
flatMap(builder)
}
/// Maps values from the sequence to animations
/// - Parameter animations: Add each animation from within this closure. Animations should conform to ``GroupAnimatable``
/// - Returns: Sequence of all animations created in the `animation` closure
public func mapSequence(
@SequenceBuilder animations builder: (Element) -> [SequenceAnimatable]
) -> [SequenceAnimatable] {
flatMap(builder)
}
}

extension Swift.Sequence {
/// Maps values from the sequence to animations
/// - Parameter animations: Add each animation from within this closure. Animations added to this loop should conform to ``GroupAnimatable``
/// - Returns: Group of all animations created in the `animation` closure
public func mapGroup(
@GroupBuilder animations builder: (Element) -> [GroupAnimatable]
) -> [GroupAnimatable] {
flatMap(builder)
}
}
4 changes: 2 additions & 2 deletions Sources/AnimationPlanner/Animations/Sequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public struct Sequence: DelayedAnimatable {
self.runningSequence = RunningSequence(animations: animations)
}

/// Creates a new `Sequence` providing a way to perform a sequence animation from withing a group. Each animation is perform in in order, meaning each subsequent animation starts right after the previous completes.
/// Creates a new `Sequence` providing a way to perform a sequence animation from within a group. Each animation is perform in in order, meaning each subsequent animation starts right after the previous completes.
/// - Parameter animations: Add each animation from within this closure. Animations added to a sequence should conform to ``SequenceAnimatable``.
public init(@AnimationBuilder animations builder: () -> [SequenceAnimatable]) {
public init(@SequenceBuilder animations builder: () -> [SequenceAnimatable]) {
self.init(delay: 0, animations: builder())
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Creating a basic animation sequence

An example of how a typical animation sequence would look, how its created and more information on the sample app available in the repository.
An example of how a typical animation sequence would look, how its created and more information on the sample app available in the repository.

## Overview

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import QuartzCore
// from @warplings https://gist.github.com/warpling/21bef9059e47f5aad2f2955d48fd7c0c
// from @warplings https://gist.github.com/warpling/21bef9059e47f5aad2f2955d48fd7c0c
public extension CAMediaTimingFunction {

static let linear = CAMediaTimingFunction(name: .linear)
Expand Down
6 changes: 3 additions & 3 deletions Sources/AnimationPlanner/Protocols/Animatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ public protocol Animation: Animatable, PerformsAnimations {
public protocol SequenceAnimatable: Animatable, SequenceConvertible { }

extension SequenceAnimatable {
public func asSequence() -> [SequenceAnimatable] { [self] }
public func animations() -> [SequenceAnimatable] { [self] }
}

/// Animation that can be used in a ``Group`` and be performed simultaneously, meaning all animations run at the same time.
public protocol GroupAnimatable: Animatable, GroupConvertible { }

extension GroupAnimatable {
public func asGroup() -> [GroupAnimatable] { [self] }
public func animations() -> [GroupAnimatable] { [self] }
}

/// Adds delaying functionality to an animation. Delayed animations can only be added in a ``Group`` context, where each animation is performed simultaneously. Adding a delay to a sequence animation can be done by preceding it with a ``Wait`` struct.
Expand All @@ -44,7 +44,7 @@ public protocol SpringAnimatable: Animatable {
/// “To smoothly decelerate the animation without oscillation, use a value of 1. Employ a damping ratio closer to zero to increase oscillation.”
var dampingRatio: CGFloat { get }

/// Initial velocity for spring-based animation. `UIView`s documentation clearly explains it with:
/// Initial velocity for spring-based animation. `UIView`s documentation clearly explains it with:
/// “A value of 1 corresponds to the total animation distance traversed in one second. For example, if the total animation distance is 200 points and you want the start of the animation to match a view velocity of 100 pt/s, use a value of 0.5.”
var initialVelocity: CGFloat { get }
}
20 changes: 15 additions & 5 deletions Sources/AnimationPlanner/Protocols/AnimationConvertible.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
/// Provides a way to create a uniform sequence from all animations conforming to ``SequenceAnimatable``
public protocol SequenceConvertible {
func asSequence() -> [SequenceAnimatable]
func animations() -> [SequenceAnimatable]
}
extension SequenceConvertible where Self: SequenceAnimatable {
public func animations() -> [SequenceAnimatable] {
[self]
}
}

/// Provides a way to group toghether animations conforming to ``GroupAnimatable``
/// Provides a way to group together animations conforming to ``GroupAnimatable``
public protocol GroupConvertible {
func asGroup() -> [GroupAnimatable]
func animations() -> [GroupAnimatable]
}
extension GroupConvertible where Self: GroupAnimatable {
public func animations() -> [GroupAnimatable] {
[self]
}
}

extension Array: SequenceConvertible where Element == SequenceAnimatable {
public func asSequence() -> [SequenceAnimatable] { flatMap { $0.asSequence() } }
public func animations() -> [SequenceAnimatable] { flatMap { $0.animations() } }
}

extension Array: GroupConvertible where Element == GroupAnimatable {
public func asGroup() -> [GroupAnimatable] { flatMap { $0.asGroup() } }
public func animations() -> [GroupAnimatable] { flatMap { $0.animations() } }
}
2 changes: 1 addition & 1 deletion Tests/AnimationPlannerTests/BuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class BuilderTests: AnimationPlannerTests {
AnimationPlanner.plan {
Wait(delay)
Group {
zip(views, animations).mapAnimations { view, animation in
zip(views, animations).mapGroup { view, animation in
Sequence {
Wait(animation.delay)
Animate(duration: animation.duration) {
Expand Down
Loading
Loading