Point-Free episode 136 solves the problem using a Scheduler like this:
return effect
.receive(on: environment.mainQueue.animation())
It occurred to me that we could also solve it using a custom Publisher, eliminating the need to use any Scheduler:
return effect.animation()
We just need to make sure the call to the subscriber's receive(_:) method is wrapped in a withAnimation block. Here's an implementation:
extension Effect {
public func animation(_ animation: Animation? = .default) -> Self {
return BracketingPublisher(upstream: self) { action in
withAnimation(animation, action)
}.eraseToEffect()
}
}
public struct BracketingPublisher<Upstream: Publisher>: Publisher {
public typealias Output = Upstream.Output
public typealias Failure = Upstream.Failure
public var upstream: Upstream
public var bracket: (() -> Void) -> Void
public init(upstream: Upstream, bracket: @escaping (() -> Void) -> Void) {
self.upstream = upstream
self.bracket = bracket
}
public func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
let conduit = Conduit(downstream: subscriber, bracket: bracket)
upstream.receive(subscriber: conduit)
}
fileprivate class Conduit<Downstream: Subscriber>: Subscriber {
typealias Input = Downstream.Input
typealias Failure = Downstream.Failure
let downstream: Downstream
let bracket: (() -> Void) -> Void
init(downstream: Downstream, bracket: @escaping (() -> Void) -> Void) {
self.downstream = downstream
self.bracket = bracket
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
var demand: Subscribers.Demand = .none
bracket {
demand = downstream.receive(input)
}
return demand
}
func receive(completion: Subscribers.Completion<Failure>) {
downstream.receive(completion: completion)
}
}
}