Binding async sequences to state variables in SwiftUI

AsyncBinding

A lightweight Swift package providing a utility for binding async sequences to state variables in SwiftUI. AsyncBinding offers an intuitive way to manage asynchronous data streams and handle errors in your SwiftUI views.

Features

  • Bind values from async sequences to a custom state variable that automatically triggers UI updates when the value changes. Bindings are created using the task(priority:_:) view modifier that ensures a binding lifetime that matches that of the modified view.
  • Handle errors thrown by async sequences. Graceful error handling is achieved by mapping the underlying async seqeunces to non-throwing sequences with Result element type.

Installation

AsyncBinding supports Swift Package Manager, which is the recommended option.

Usage

Given a ViewModel that exposes async sequences as follows:

class ViewModel {
    var name: AsyncThrowingStream<String, Error> { ... }
    var age: AsyncThrowingStream<String, Error> { ... }
    var colors: AsyncThrowingStream<String, Error> { ... }
}

You can bind those async sequences to properties in your SwiftUI View that are marked with a custom property wrapper called @AsyncBinding using a single line of code and the bind(_:) modifier:

struct Screen: View {

    @InjectedObject var viewModel: ViewModel

    @AsyncBinding var name: String = ""
    @AsyncBinding var age: String?
    @AsyncBinding var colors: [String]

    var body: some View {
        VStack {
            Text(name)
            Text(age ?? "unknown")
            Text(colors.joined(separator: ", "))
                .foregroundColor($colors.hasError ? .red : .black)
            if let error = $colors.error {
                Text(error.localizedDescription)
            }
        }
        .bind {
            viewModel.name.assign(to: $name)
            viewModel.age.assign(to: $age)
            viewModel.colors.assign(to: $colors)
        }
    }
}

Error Handling

AsyncBinding captures and stores any errors thrown by the bound async sequence. You can verify if an error occurred using the hasError property and retrieve the error through the error property of the projectedValue of an @AsyncBinding property:

Text(colors.joined(separator: ", "))
    .foregroundColor($colors.hasError ? .red : .black)
if let error = $colors.error {
    Text(error.localizedDescription)
}

AsyncBinding uses a custom sequence called AsyncBindingSequence under the hood to wrap the base sequence’s values with a Result type, thus creating a new sequence that cannot throw. This allows the base sequences to remain active even if an error was thrown. AsyncBindingSequence is inspired by AsyncMapToResultSequence from the AsyncExtensions package.

Demo Application

The package contains a demo project for a comprehensive example of how to use AsyncBinding. Note that the demo project uses XcodeGen to generate the project file. See the README.md inside the project for more info.

Contributing

Feel free to submit a pull request or open an issue.

License

AsyncBinding is made available under the MIT License. Please see the LICENSE file for more details.

GitHub

View Github