Hey everyone,
Lately, I've been working a lot with animations and I've been copying code from the Animation case study where an animation consists of multiple Effects run successively that mutate the state for each step of the animation. It works great but when you end up having lots of animation, it's kinda cumbersome to copy paste this logic for each animation. So I was thinking of an .animation
Effect that we could use instead that simply wraps the logic into another Effect.
Basically, it could look like this :
import Combine
public protocol AnimationDuration {
var duration: Double { get }
}
extension Effect where Failure == Never {
public static func animation<State, S>(
animationStates: [State],
action: @escaping (State) -> Output,
scheduler: S
) -> Effect where State: AnimationDuration, S: Scheduler {
return Effect.concatenate(
animationStates
.enumerated()
.map { index, animationState in
index == 0
? Effect(value: action(animationState))
: Effect(value: action(animationState))
.delay(for: .seconds(animationStates[index - 1].duration), scheduler: scheduler)
.eraseToEffect()
}
)
}
}
And the usage (taken from the case study):
struct AnimationsState: Equatable {
struct Animation: AnimationDuration, Equatable {
let duration: Double
let color: Color
}
var circleColor = Color.white
}
enum AnimationsAction: Equatable {
case rainbowButtonTapped
case setColor(AnimationsState.Animation)
}
struct AnimationsEnvironment {
var mainQueue: AnySchedulerOf<DispatchQueue>
}
let animationsReducer = Reducer<AnimationsState, AnimationsAction, AnimationsEnvironment> {
state, action, environment in
switch action {
case .rainbowButtonTapped:
// You need to declare each step of your animation for the animation Effect
// with a duration and a property to animate at the very least.
// You can also have different animations (linear, ease in, ease out etc.) if you want.
let animationStates = [Color.red, .blue, .green, .orange, .pink, .purple, .yellow, .white]
.map { AnimationsState.Animation(duration: 1, color: $0) }
return .animation(
animationStates: animationStates,
action: { .setColor($0) },
scheduler: environment.mainQueue
)
case let .setColor(animation):
state.circleColor = animation.color
return .none
}
Let me know what you think and if it's worth creating a PR for this.