The modifier .task() has a hidden feature


You’re more of a video kind of person? I’ve got you covered! Here’s a video with the same content than this article 🍿


Advertisement

Debug HTTP/HTTPS with Proxyman like a Pro

Try Proxyman, a native macOS app that captures and displays Request/Response in beautiful UIs.

Supports iOS and Android, both devices and simulators.

Get 30% off discount for Black Friday with the code BLACKFRIDAY2025

👉 Get it now 👈


Sponsors like Proxyman really help me grow my content creation, so if you have time please make sure to check out their product: it’s a direct support to my content creation ☺️


I’m sure that you’ve already used the modifier .task { } in SwiftUI.

It’s a very convenient way to start a Task before a View gets displayed, while also having SwiftUI automatically cancel the Task if the View disappears before the Task finishes.

A typical use case for this modifier would be to perform a network request that fetches the data that we’ll then use to populate a View:

But did you know that this modifier also has a hidden feature?

There exists an overload of .task { } that takes an additional id parameter, and this new parameter can be quite powerful.

You can use any Equatable value for this parameter, and every time that a new value is set, the previous Task will be canceled and a new Task will be started:

And this can be super useful because it allows us to perform new asynchronous work every time that an event occurs, with minimal effort from the developer.

Just make sure that the behavior of cancelling the previous Task if it hadn’t completed is indeed what you’re looking for.

That’s all for this article, here’s the code if you want to experiment with it:

import SwiftUI

struct ContentView: View {
    @State private var counter = 0
    @State private var isLoading = false
    
    var body: some View {
        VStack(spacing: 30) {
            Text("Counter: \(counter)")
                .font(.title)
            
            Button("Increment Counter") {
                counter += 1
            }
            .buttonStyle(.borderedProminent)
            
            VStack {
                if isLoading {
                    ProgressView("Loading...")
                } else {
                    Text("Task completed for counter value: \(counter)")
                }
            }
            .frame(minHeight: 50)
        }
        .task(id: counter) {
            await performTask()
        }
   }
}
Next
Next

Providing a default value in a String interpolation