Working Asynchronously in SwiftUI task

This time, let’s take a look at task, a new modifier for SwiftUI, which is new in iOS 15. task make it more appropriate to run asynchronous tasks that were relatively unsuitable for onAppear.

Introduction

In SwiftUI, when a network request is needed, most of it is done in onAppear, which is the time when the View is created. Now, it seems that tasks that require asynchronous operations such as network requests should be performed in task rather than onAppear. However, as the task was introduced in iOS 15, it cannot be used in versions below iOS 15.

Definition

// Adds a task to perform before this view appears or when a specified value changes.
task(id:priority:_:)

The form and definition of a task are as follows. In simple terms, a task adds an asynchronous operation to be performed before the view appears or when a certain value changes. Here, the certain value means the value entered in parameter id.

Declaration & Return

The declaration and return values of the task are as follows. id, priority, and action are optionally entered as parameters.

func task<T>(
id value: T,
priority: TaskPriority = .userInitiated,
_ action: @escaping () async -> Void
) -> some View where T : Equatable

Here, id is a value of type conforming to Equatable, and priority is a value of type TaskPriority. You can put code to be executed as in onAppear in action, but the key is that it is code that performs an asynchronous operation.

The return value is a view that executes an asynchronous action before the view appears or restarts the task when the id value changes.

Advantages

The reason the task needs onAppear is because it has some good points. The nice thing about task is that they have a lifetime that matches the lifetime of the view added as a modifier. In other words, if the view disappears before the task put into the task is completed, SwiftUI will cancel or redo the task in the task.

Examples

Let’s look at an example using task. First of all, there is a structure called Message as follows.

struct Message: Decodable, Identifiable {
let id: Int
let text: String
}

And there is a function called loadMessages that does an asynchronous operation to load messages like this. When executing functions such as loadMessages, you can execute them at a much more appropriate timing if you run them in a task.

struct ContentView: View {
@State private var messages = [Message]()

var body: some View {
List(messages) { message in
VStack {
Text(message.text)
}
}
.task {
await loadMessages()
}
}

func loadMessages() async {
do {
let url = URL(string: "https://example.com/messages.json")!
let (data, _) = try await URLSession.shared.data(from: url)
messages = try JSONDecoder().decode([Message].self, from: data)
} catch {
messages = [
Message(id: 0, text: "Please try again later.")
]
}
}
}

Extras

The effect of using task becomes more prominent when using id values. Let’s say we put an Equatable compliant id value in a task. At this time, when the id value changes, SwiftUI automatically cancels the previous task and creates a new task with the new value. For example, once again consider that we have a Message structure like this:

struct Message: Decodable, Identifiable {
let id: Int
let text: String
}

Unlike the previous example, a variable called selectedBox has been added. This is being used in .task(id: selectedBox). You can see that data is fetched from a different url according to the value of selectedBox and the operation is re-executed.

struct ContentView: View {
@State private var messages = [Message]()
@State private var selectedBox = "Inbox"

let messageBoxes = ["Inbox", "Sent"]

var body: some View {
NavigationView {
List(messages) { message in
VStack {
Text(message.text)
}
}
.task(id: selectedBox) {
await fetchData()
}

}
}

func fetchData() async {
do {
let url = URL(string: "https://example.com/\(selectedBox.lowercased()).json")!
let (data, _) = try await URLSession.shared.data(from: url)
messages = try JSONDecoder().decode([Message].self, from: data)
} catch {
messages = [
Message(id: 0, text: "Please try again later.")
]
}
}
}

Additionally, the task modifier also has a priority parameter that allows you to set the priority of the task. For example, .task(priority: .low) will create a lower priority task.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store