async/awaitThese are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.
async/awaitURLSession has asynchronous methods: let (data, response) = try await URLSession.shared.data(from: url). In SwiftUI you start that work with the .task modifier, which runs when the view appears and is cancelled automatically when the view goes away. .task(id:) restarts the work whenever a value changes. Show progress and failure explicitly: describe the state of the screen with one enum — loading, loaded, failed — rather than with several Booleans that can contradict one another.
Coming from UIKit, this replaces completion handlers, DispatchQueue.main.async, and cancelling requests by hand in viewWillDisappear.
struct TripsScreen: View {
enum LoadState { case loading, loaded([Trip]), failed(String) }
@State private var state = LoadState.loading
let url: URL
var body: some View {
Group {
switch state {
case .loading: ProgressView()
case .failed(let reason): Text(reason)
case .loaded(let trips): List(trips) { Text($0.name) }
}
}
.task { // starts on appear, cancelled on disappear
do {
let (data, _) = try await URLSession.shared.data(from: url)
state = .loaded(try JSONDecoder().decode([Trip].self, from: data))
} catch {
state = .failed("Could not load trips.")
}
}
}
}
Exercise
Fetch a list from a public API and display it, with a loading indicator, an error state that has a Retry button, and pull-to-refresh using .refreshable.
.task do that onAppear { Task { … } } does not?isLoading and error properties?.task(id:) do when the id changes?