Post

Common SwiftUI pitfalls - onChange modifier + Task

When developing in SwiftUI, it is common to encounter scenarios where asynchronous tasks need to be executed upon specific state changes. It may be intuitive to implement the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct MyView: View {

    @State private var myState: MyState = ...

    var body: some View {
        MyAwesomeView()
            .onChange(of: myState) {
                Task {
                    await doWork()
                }
            }
    }
}

However, this approach generates a new unstructured task each time myState changes, without a mechanism to cancel it. While you could manage task cancellation by storing the task in a variable and cancelling it in the subsequent onChange closure, a more streamlined solution exists:

1
2
3
4
5
6
7
8
9
10
11
struct MyView: View {

    @State private var myState: MyState = ...

    var body: some View {
        MyAwesomeView()
            .task(id: myState) {
                await doWork()
            }
    }
}

This method effectively cancels and recreates the task upon myState changes, and ensuring task cancellation when the view disappears.

Please note that the task will also trigger when the view appears.

This post is licensed under CC BY 4.0 by the author.