@Observable model keeps resetting

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

Spot the bug, then pick the fix 2 steps

In this app, tapping Count works, but tapping Refresh sets the count back to 0. Which line causes it, and what is the fix?

Step 1 of 2: Find the bug

In this app, tapping Count works. Tapping Refresh sets the count back to 0. Tap the line that causes it.

  1. @Observable final class CounterModel {
  2. var count = 0
  3. }
  4. ​
  5. struct CounterScreen: View {
  6. @State private var tick = 0
  7. var body: some View {
  8. VStack {
  9. Button("Refresh \(tick)") { tick += 1 }
  10. CounterCard()
  11. }
  12. }
  13. }
  14. ​
  15. struct CounterCard: View {
  16. var model = CounterModel()
  17. var body: some View {
  18. Button("Count: \(model.count)") { model.count += 1 }
  19. }
  20. }
Hint 1 of 2

What happens to CounterCard when tick changes?

Hint 2 of 2

Which property wrapper tells SwiftUI to keep state across view re-creation?

Step 2 of 2: Pick the fix

Line 16 makes a new model every time CounterCard is recreated. Which change fixes it?

Options
Hint 1 of 1

Ownership is the issue, not observation.

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

Show answer

Step 1: Find the bug

  • Answer: line 16 var model = CounterModel()

CounterCard is a value that SwiftUI recreates every time CounterScreen.body runs. A plain stored property gets a brand-new CounterModel each time, so the count starts over. Nothing tells SwiftUI to keep it.

Step 2: Pick the fix

Answer: A @State private var model = CounterModel()

Correct. With @Observable, the owning view keeps the model in @State, and SwiftUI preserves it for the lifetime of that view's identity.

The view that creates an @Observable model owns it with @State, so SwiftUI keeps the same instance while the view keeps its identity.

Why not the others?
  • B @StateObject var model = CounterModel(). @StateObject requires ObservableObject. An @Observable class does not conform, so this does not compile.
  • C @Bindable var model = CounterModel(). @Bindable only gives you bindings to an object you are handed. It does not own or preserve the object, so the reset still happens.
  • D Make CounterModel a struct. The @Observable macro only applies to classes.

30-second answer

The child view is recreated whenever its parent's body runs, so a model stored in a plain property is recreated too. The view that creates an @Observable model must own it with @State. Children receive it as a plain property, or with @Bindable when they need bindings.

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

Who should own an @Observable model, and how do child views get it?

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

  • The creating view owns it with @State
  • Children get it as a plain property, or via @Environment(Model.self)
  • @Bindable when a child needs $model.property bindings
  • Mentions the @StateObject / ObservableObject equivalent for iOS 16 and earlier

Deep dive

SwiftUI views are short-lived values. Their stored properties are rebuilt on every init, while SwiftUI-managed storage (@State) survives as long as the view keeps the same identity.

The initial value in @State private var model = CounterModel() is evaluated on each init but discarded after the first one. If the model is expensive to create, pass it in from the owner or create it lazily in .task.

State is also lost when identity changes, for example when an if/else switches branches or .id(...) changes. That is a separate bug with the same symptom.

Common mistakes

  • Using @StateObject with an @Observable class.
  • Treating @Bindable as ownership.
  • Fixing it by moving the model into a global singleton.

Senior take & follow-ups

Describe it as an ownership and identity problem, not an observation problem. Separating 'who owns the state' from 'who observes it' is the signal interviewers look for.

Follow-up questions

Think of your answer first, then open the hint.

  1. The model is correctly in @State but still resets when the user switches tabs and back. Why might that be?

    Show hint

    Check whether the view's identity changes: a conditional branch or an .id(...) modifier.

  2. How do you inject the same model into a deep subtree?

    Show hint

    .environment(model) on an ancestor and @Environment(CounterModel.self) in the descendant.

From the author

Catalog - UI

Made by the author: browse UIKit and SwiftUI components with live demos, and copy their code snippets into your project.

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