Home

How to call APIs using async/await in SwiftUI

Async/await in Swift

Async/await is a modern Swift feature introduced in Swift 5.5 (WWDC 2021) that handles asynchronous operations more cleanly. By marking your function as async and using the await keyword when calling other asynchronous functions, you can streamline your asynchronous code.

Fetching Data Asynchronously in SwiftUI

Let's say you want to fetch recipe data from a URL whenever the view appears. Here’s how you can achieve that using async/await.

Step 1: Define your model

First, define a model to represent the recipe data.

// Recipe model
struct Recipe: Codable {
   var name: String
   var ingredients: [String]
   var instruction: String
}

Step 2: Create a Recipe fetching function

Next, define an asynchronous function to fetch the recipe from a URL.

// Asynchronous function to fetch data
func fetchRecipe() async throws -> Recipe {
   let url = URL(string: "https://recipe.com/recipes")!
   let request = URLRequest(url: url)
   let (data, _) = try await URLSession.shared.data(for: request)
   let recipe = try JSONDecoder().decode(Recipe.self, from: data)
   return recipe
}

Step 3: Execute the asynchronous function

To execute an asynchronous function in SwiftUI, you need to call the function from a context that supports asynchronous execution, such as within the onAppear modifier using Task. This creates an asynchronous context to call the await function.

// SwiftUI View
import SwiftUI

struct RecipeView: View {
    @State private var recipe: Recipe?
    @State private var isLoading = false
    
    var body: some View {
        ScrollView {
            if isLoading {
                ProgressView()
            } else {
                if let recipe = recipe {
                    Text(recipe.name)
                    ForEach(recipe.ingredients.indices, id: \.self) { index in
                        VStack(alignment: .leading, spacing: 8) {
                            Text(recipe.ingredients[index])
                        }
                    }
                    Text(recipe.instruction)
                } else {
                    Text("No recipe loaded")
                }
            }
        }
        .onAppear {
            Task {
                isLoading = true
                do {
                    recipe = try await fetchRecipe()
                } catch {
                    print("Error fetching recipe: \(error)")
                }
                isLoading = false
            }
        }
    }
}

Explanation

  • Async/await in Swift: The async keyword marks a function as asynchronous, while the await keyword is used to call asynchronous functions. This makes the code easier to read and maintain compared to traditional completion handler-based asynchronous code.
  • SwiftUI and Asynchronous Tasks: In SwiftUI, you can use the Task initializer within the onAppear modifier to create an asynchronous context. This allows you to call await functions safely when the view appears.
  • Error Handling: Using do-catch blocks within the Task helps manage any errors that might occur during the data fetching process.

This example 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 code that is more robust and maintainable, simplifying complex tasks like networking and making them more straightforward and enjoyable to implement.