Kolos65 / AsyncBinding

Binding async sequences to state variables in SwiftUI.
MIT License
10 stars 1 forks source link

Add the possibility to animate the State changes inside an AsyncBinding #2

Open kristofkalai opened 8 months ago

kristofkalai commented 8 months ago

Problem

Currently AsyncBinding cannot be used if the State change needs to be animated.

Feature Request

To broaden the usage of AsyncBinding, animation of the state change definitely needs to be supported. We kindly request the implementation of this feature.

Value to the community

This library presents a promising approach to connect the async world with the SwiftUI framework. Therefore, enabling animations for updating an AsyncBinding value could be the next step in its evolution.

Example

Let's say we have a basic View, that shows a simple Color:

struct RectangleView: View {
    let color: Color

    var body: some View {
        Rectangle()
            .fill(color)
            .frame(width: 100, height: 100)
    }
}

In SwiftUI, this is a basic approach to animate the color of the View:

struct StateView: View {
    @State private var color: Color = .red

    var body: some View {
        RectangleView(color: color)
            .onAppear {
                withAnimation(.linear(duration: 5).delay(1)) {
                    color = .blue
                }
            }
    }
}

However, if I want to implement such a way that the colors are coming from somewhere else, like this:

struct AsyncBindingView: View {
    @AsyncBinding private var color: Color = .red

    @StateObject private var viewModel = ViewModel()

    var body: some View {
        RectangleView(color: color)
            .bind {
                viewModel.colorSequence.assign(to: $color)
            }
    }
}

Then it works, but only without animation. Animations cannot be enabled currently.

final class ViewModel: ObservableObject {
    private let colorSubject = AsyncCurrentValueSubject(Color.red)
    var colorSequence: AnyAsyncSequence<Color> {
        colorSubject.eraseToAnyAsyncSequence()
    }

    init() {
        // Doesn't work:
        // withAnimation(.linear(duration: 5).delay(1)) {
        //     colorSubject.send(.blue)
        // }

        // Doesn't work:
        // colorSubject.send(.blue)
    }
}

I understand the limitation's technical side, but if we can, it would be great to add animation support for this library.

Kolos65 commented 8 months ago

You can you use implicit animations to animate changes. With your example:

struct AsyncBindingView: View {
    @AsyncBinding private var color: Color = .red
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        RectangleView(color: color)
            .bind {
                viewModel.colorSequence.assign(to: $color)
            }
            .animation(.default, value: color)
            .animation(.default, value: $color.hasError)
    }
}
Kolos65 commented 8 months ago

Does having support for explicit animations inside the binding block have any advantages?

kristofkalai commented 8 months ago

Can implicit animation support reacting to the completion of the animation? If not, then explicit animations can be the first step to this direction (although I'm not sure the correct way of handling this, i.e. handling animation per state update or by providing a value at initialization)