How to run an asynchronous task when a view is shown
How to run an asynchronous task when a view is shown êŽë š
Updated for Xcode 15
New in iOS 15
SwiftUI's task()
modifier is a more powerful version of onAppear()
, allowing us to start asynchronous work as soon as the view is shown. Even better, the task will automatically be cancelled when the view is destroyed, if it has not already finished.
As the task is executed asynchronously, this is a great place to fetch some initial network data for your view. For example, if we wanted to fetch a list of messages from a server, decode it into an array of Message
structs, then show it in a list, we might write something like this:
struct Message: Decodable, Identifiable {
let id: Int
let from: String
let text: String
}
struct ContentView: View {
@State private var messages = [Message]()
var body: some View {
NavigationStack {
List(messages) { message in
VStack(alignment: .leading) {
Text(message.from)
.font(.headline)
Text(message.text)
}
}
.navigationTitle("Inbox")
}
.task {
do {
let url = URL(string: "https://hackingwithswift.com/samples/messages.json")!
let (data, _) = try await URLSession.shared.data(from: url)
messages = try JSONDecoder().decode([Message].self, from: data)
} catch {
messages = []
}
}
}
}
You can attach the task()
modifier to any view in your hierarchy, even ones that are presented as a result of a navigation push â it will only actually start its work when the view is displayed.
To demonstrate this, we could create a simple website source code viewer where users had a choice of sites to check:
struct ContentView: View {
let sites = ["Apple.com", "HackingWithSwift.com", "Swift.org"]
var body: some View {
NavigationStack {
List(sites, id: \.self) { site in
NavigationLink(site) {
SourceViewer(site: site)
}
}
.navigationTitle("View Source")
}
}
}
struct SourceViewer: View {
let site: String
@State private var sourceCode = "LoadingâŠ"
var body: some View {
ScrollView {
Text(sourceCode)
.font(.system(.body, design: .monospaced))
}
.task {
guard let url = URL(string: "https://\(site)") else {
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
sourceCode = String(decoding: data, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)
} catch {
sourceCode = "Failed to fetch site."
}
}
}
}
Both task()
and onAppear()
are able to run synchronous functions when you view is shown, so there's no particular reason to choose one or the other beyond personal taste â there's a nice balance to having both onAppear()
and onDisappear()
together.
Tips
By default, Swift tasks created using the task()
modifier will run at the highest available priority, but you can pass a custom priority into the modifier if you know the work is less important.