For example:
var body: some View {
ScrollViewReader(content: { proxy in
/* ... */
}).task {
do {
let url = URL(string: "https://example.com/data.json")!
let (data, _) = try await URLSession.shared.data(from: url)
/* ... */
} catch {
print(error)
}
}
}
What does .task
modifier do underneath? Is it for storing and restoring the state of the program after the data-request has resolved?
Key to the Swift concurrency world.
SwiftUI’s .task
modifier inherits its actor context from the surrounding function. If you call .task
inside a view’s body property, the async operation will run on the main actor because View.body
is (semi-secretly) annotated with @MainActor
. However, if you call .task
from a helper property or function that isn’t @MainActor
-annotated, the async operation will run in the cooperative thread pool.
According to Apple Docs:
Use this modifier to perform an asynchronous task with a lifetime that matches that of the modified view. If the task doesn’t finish before SwiftUI removes the view or the view changes identity, SwiftUI cancels the task.
Use the
await
keyword inside the task to wait for an asynchronous call to complete, or to wait on the values of anAsyncSequence
instance. For example, you can modify a Text view to start a task that loads content from a remote resource:
let url = URL(string: "https://example.com")!
@State private var message = "Loading..."
var body: some View {
Text(message)
.task {
do {
var receivedLines = [String]()
for try await line in url.lines {
receivedLines.append(line)
message = "Received \(receivedLines.count) lines"
}
} catch {
message = "Failed to load"
}
}
}
This example uses the lines
method to get the content stored at the specified URL as an asynchronous sequence of strings. When each new line arrives, the body of the for-await-in loop stores the line in an array of strings and updates the content of the text view to report the latest line count.