In SwiftUI, both .task()
and .onAppear()
are view modifiers that can be used to run some code when a view is shown. The exact moment that SwiftUI calls these methods depends on the specific view type that you apply to, but the action closures complete before the first rendered frame appears.
// iOS 13.0+, macOS 10.15+, watchOS 6.0+
func onAppear(perform action: (() -> Void)? = nil) -> some View
// iOS 15.0+, macOS 12.0+, watchOS 8.0+
func task(
priority: TaskPriority = .userInitiated,
_ action: @escaping () async -> Void
) -> some View
// iOS 15.0+, macOS 12.0+, watchOS 8.0+
func task<T>(
id value: T,
priority: TaskPriority = .userInitiated,
_ action: @escaping () async -> Void
) -> some View where T : Equatable
In general, .task()
is a more powerful and convenient modifier than .onAppear()
for running asynchronous work when a view is shown. However, it is only available in iOS 15+, so you may need to use .onAppear()
for backward compatibility. You can always use .onAppear()
with Task
to replicate what .task()
does.
There are some differences between them:
.task()
allows you to use the async/await directly to perform asynchronous work. While .onAppear()
is an older modifier that can only run synchronous code, you need to use Task
or DispatchQueue
to bridge to async code.struct ContentView: View {
@State var text: String = "Loading..."
// Async function
func asyncGetText() async -> String {
// Simulate some network request
await Task.sleep(3_000_000_000)
return "Hello, world!"
}
var body: some View {
Text(text)
.padding()
// Start a task when the view is shown
.task {
// Await the result of the async function
let result = await asyncGetText()
// Update the state with the result
text = result
}
}
}
Similar example but using .onAppear()
:
struct ContentView: View {
/* ... */
var body: some View {
Text(text)
.padding()
// Run some code when the view is shown
.onAppear {
// Start a task to call the async function
Task {
// Await the result of the async function
let result = await asyncGetText()
// Update the state with the result
text = result
}
}
}
}
.task()
will automatically cancel the task when the view is destroyed, if it has not already finished. .onAppear()
does not have this feature, so you may need to manually cancel any ongoing work in .onDisappear()
.struct ContentView: View {
/* ... */
private var task: Task<Void, Never>? // reference to the task
var body: some View {
Text(text)
.padding()
// Run some code when the view is shown
.onAppear {
// Start a task to call the async function
task = Task {
// Await the result of the async function
let result = await asyncGetText()
// Update the state with the result
text = result
}
}
// Run some code when the view is hidden
.onDisappear {
// Cancel the task if it is not finished
task?.cancel()
}
}
}
.task()
can take an id
parameter that allows you to cancel and restart the task when the value changes. For example, you can use this to listen to notifications or update data based on some state. .onAppear()
does not have this feature, so you may need to use @State
or @Binding
variables to trigger updates.struct ContentView: View {
@State var text: String = "Loading..."
@State var category: String = "Swift" // the id for the task
// Async function
func asyncGetText(category: String) async -> String {
// Simulate some network request
await Task.sleep(3_000_000_000)
return "Hello, \(category)!"
}
var body: some View {
VStack {
Text(text)
.padding()
// Start a task with an id when the view is shown
.task(id: category) {
// Await the result of the async function
let result = await asyncGetText(category: category)
// Update the state with the result
text = result
}
// A picker to change the category
Picker("Select a category", selection: $category) {
Text("Swift").tag("Swift")
Text("iOS").tag("iOS")
Text("macOS").tag("macOS")
}
.pickerStyle(.segmented)
}
}
}
.task()
can also take a priority
parameter that allows you to specify the priority of the task relative to other tasks. For example, you can use this to prioritize user-initiated tasks over background tasks. .onAppear()
does not have this feature, so you may need to use Task
, DispatchQueue
or OperationQueue
to manage priorities.struct ContentView: View {
@State var text: String = "Loading..."
@State var priority: TaskPriority = .userInitiated // the priority for the task
// Async function
func asyncGetText(priority: TaskPriority) async -> String {
// Simulate some network request
await Task.sleep(3_000_000_000)
return "Hello, \(priority)!"
}
var body: some View {
VStack {
Text(text)
.padding()
// Start a task with a priority when the view is shown
.task(priority: priority) {
// Await the result of the async function
let result = await asyncGetText(priority: priority)
// Update the state with the result
text = result
}
// A picker to change the priority
Picker("Select a priority", selection: $priority) {
Text("User Initiated").tag(TaskPriority.userInitiated)
Text("High").tag(TaskPriority.high)
Text("Medium").tag(TaskPriority.medium)
Text("Low").tag(TaskPriority.low)
Text("Background").tag(TaskPriority.background)
}
.pickerStyle(.segmented)
}
}
}