Chapters

Hide chapters

SwiftUI Cookbook

Live Edition · iOS 16.4 · Swift 5.8.1 · Xcode 14.3.1

Async/Await in SwiftUI
Written by Team Kodeco

Async/await is a modern Swift feature introduced in Swift 5.5 that simplifies asynchronous programming, making your code cleaner and more intuitive. It is especially handy in SwiftUI where you can update your UI seamlessly after performing background tasks.

Example: Joke Fetcher App

In this example, you will build a SwiftUI app that fetches a random joke from the public JokeAPI whenever the user taps a button. You’ll use async/await to handle the networking and present the jokes in a friendly interface.

Step 1: Define the Model

Create a Joke struct that conforms to Codable to map the JSON response from the API:

struct Joke: Codable {
  let setup: String
  let delivery: String
}
Step 2: Create a Joke Fetching Function

Write an asynchronous function using async/await to fetch a random joke:

func fetchJoke() async throws -> Joke {
  let url = URL(string: "https://v2.jokeapi.dev/joke/Programming?type=twopart")!
  let request = URLRequest(url: url)
  let (data, _) = try await URLSession.shared.data(for: request)
  let joke = try JSONDecoder().decode(Joke.self, from: data)
  return joke
}

Let’s break down this method:

  1. async Keyword: Declaring the function with async means that it can perform asynchronous operations. You can think of this as a task that can run in the background without blocking the rest of your code.
  2. await Usage: Inside the function, await is used before the URLSession.shared.data(for: request) call. This is the point where the function will pause and wait for the data to be fetched from the network. Once the data is ready, the function will resume and continue executing the next lines.
  3. Error Handling: The function is marked with throws, meaning it can throw an error if something goes wrong (like a network failure). This must be handled by the caller of the function, as you’ll see below.
Step 3: Build the SwiftUI View

Now, you’ll build the SwiftUI view that displays the joke and includes a button to fetch a new one.

struct ContentView: View {
  @State private var joke: Joke?
  @State private var isLoading = false

  var body: some View {
    VStack(spacing: 8) {
      if let joke = joke {
        Text(joke.setup)
          .font(.title)
        Text(joke.delivery)
          .font(.headline)
      } else {
        Text("Tap to fetch a joke!")
      }

      Button {
        Task {
          isLoading = true
          do {
            joke = try await fetchJoke()
          } catch {
            print("Failed to fetch joke: \(error)")
          }
          isLoading = false
        }
      } label: {
        if isLoading {
          ProgressView()
            .progressViewStyle(.circular)
            .padding(.horizontal)
        } else {
          Text("Fetch Joke")
        }
      }
      .disabled(isLoading)
      .buttonStyle(.borderedProminent)
      .padding()
    }
    .multilineTextAlignment(.center)
    .padding(.horizontal)
  }
}

Tap the Fetch Joke button and your preview should look like:

Use async/await to fetch a random joke from the JokeAPI.
Use async/await to fetch a random joke from the JokeAPI.

Here’s what’s happening in the view:

  1. Task Initialization: You use Task to run the asynchronous function. Inside the task, you’re free to use async/await as needed.
  2. await Usage: You call your asynchronous function fetchJoke using await. This means that the UI will wait for the joke to be fetched but without freezing the interface. It allows other interactions or animations to continue running smoothly.
  3. Error Handling: Inside the do block, you’re trying to call the asynchronous function. If it throws an error, the catch block will handle it, allowing for graceful error handling.
  4. State Updates: You use SwiftUI’s @State to keep track of the joke and the loading status. When the async function completes, it updates these state variables, causing the UI to refresh and display the new joke.

This simple app demonstrates how async/await in Swift can make asynchronous code more readable and intuitive, especially in the context of SwiftUI. By using modern Swift concurrency, developers can write more robust and maintainable code, making complex tasks like networking more straightforward and enjoyable to implement.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.