Loading and error state in SwiftUI

SwiftUI Mid-level Swift 5.9+ iOS 17+ Reviewed By Alok Singh

Spot the bug

After a network error, the spinner never stops. Tap the line that causes it.

  1. @MainActor @Observable
  2. final class LoadModel {
  3. var items: [Item] = []
  4. var isLoading = false
  5. var error: Error?
  6. func load() async {
  7. isLoading = true
  8. do {
  9. items = try await API.items()
  10. isLoading = false
  11. } catch {
  12. self.error = error
  13. }
  14. }
  15. }
Hint 1 of 1

On which path does isLoading go back to false?

Choose an answer, then open Show answer below to check it.

Show answer
  • Answer: line 10 isLoading = false

Line 10 resets the spinner only on success. On error the catch block never resets it. Put defer { isLoading = false } right after line 7, or use a single state enum.

30-second answer

Keep loading state in a @MainActor model and make it impossible to get stuck: reset the spinner with defer, or use one enum (idle, loading, loaded, failed). Show errors with the modern .alert(_:isPresented:presenting:actions:message:) API, offer Retry, and ignore CancellationError.

Practise saying it · 45 s. No recording, just a timer

Explain how you model loading and errors.

Say your answer out loud, then check which of these points you covered:

  • State enum or defer
  • Modern alert API
  • Retry action
  • Ignore CancellationError

Deep dive

An enum state removes impossible combinations like 'loading and failed'.

defer { isLoading = false } runs on every exit path.

Inline error views are often better than alerts for screen-level failures.

Map errors to user-friendly messages; log the technical details.

Modern alert Type-checked · Swift 6.4
.alert("Couldn't load", isPresented: $showError, presenting: model.error) { _ in
    Button("Retry") { Task { await model.load() } }
    Button("Cancel", role: .cancel) { }
} message: { error in
    Text(error.localizedDescription)
}

Common mistakes

  • Resetting the loading flag only on success.
  • Using the deprecated Alert(title:) initializer.
  • Showing an alert for a cancelled task.

Senior take & follow-ups

Seniors model state so bugs can't happen, rather than patching flags.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why use an enum for loading state?

    Show hint

    It makes invalid combinations unrepresentable.

  2. How do you retry?

    Show hint

    Call the same load method from the alert's action.

Suggest a fix if something here is wrong or out of date.