Skip to the content.

ViewScope

MIT License GitHub Release Build Status Swift Version Xcode Documentation

Overview

ViewScope is a library that lets you create structured, SwiftUI-friendly async contexts for imperative asynchronous work, such as making a network request when a user taps on a button. It allows you to scope this work to a specific node in the SwiftUI view hierarchy and prevents you from needing to rely on unstructured concurrency to manage task cancellation.

Wrap your content in a ScopedView, and use the provided scope to start tasks. When the scoped view disappears, its tasks are cancelled.

For example:

import ViewScope

struct MyScreen: View {

    var body: some View {
        ScopedView { scope in
            VStack {
                Text("This screen can produce async side effects that need to be cancelled if it ever disappears, for example if the user interacts with a deeplink or dismisses a sheet presentation with a swipe")
                Button("Make Network Request") {
                    scope.task {
                        await makeNetworkRequest()
                    }
                }
            }
        }
    }

    func makeNetworkRequest() async {
        // Await things here
    }

}

Sharing a scope across a view hierarchy

A ScopedView isn’t just for small views. Place one at the root of a screen, and every view inside it can use the same scope, either by passing along the binding provided to the content closure, or by reading it from the environment with the @CurrentScope property wrapper.

struct MyScreen: View {

    var body: some View {
        ScopedView { _ in
            VStack {
                Header()
                RefreshButton()
            }
        }
    }

}

struct RefreshButton: View {

    @CurrentScope var scope

    var body: some View {
        Button("Refresh") {
            scope.task {
                // Cancelled if `MyScreen` disappears
                await refresh()
            }
        }
    }

    func refresh() async {
        // Await things here
    }

}

Managing a scope manually

If you need more control, for example to bind a scope to a specific child view or to share one scope between several views, you can create a scope yourself with the @ViewScope property wrapper and bind it with the .viewScope(_:) modifier.

struct MyScreen: View {

    @ViewScope var myScope

    var body: some View {
        VStack {
            Text("Tasks are cancelled when this text disappears")
                .viewScope($myScope)
            Button("Make Network Request") {
                myScope.task {
                    await makeNetworkRequest()
                }
            }
        }
    }

    func makeNetworkRequest() async {
        // Await things here
    }

}

Installation

ViewScope currently distributed exclusively through the Swift Package Manager.

To add ViewScope as a dependency to an existing Swift package, add the following line of code to the dependencies parameter of your Package.swift file:

dependencies: [
    .package(url: "https://github.com/vsanthanam/ViewScope.git", from: "1.4.0")
]

To add ViewScope as a dependency to an Xcode Project:

Other distribution mechanisms like CocoaPods or Carthage may be added in the future.

Usage & Documentation

ViewScope’s documentation is built with DocC and included in the repository as a DocC archive. The latest version is hosted on GitHub Pages and is available here).

Additional installation instructions are available on the Swift Package Index

Explore the documentation for more details.

License

ViewScope is available under the MIT license. See the LICENSE file for more information.