@StateObject & ObservableObject

Reviewed & published by Brayan K

@State is great for view-local values, but real apps share model objects across many views. That's the job of ObservableObject classes, @Published properties, and the ownership wrappers @StateObject and @ObservedObject — plus the modern @Observable macro.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Building iOS Apps with SwiftUI • Intermediate

What You'll Learn in This Lesson

1️⃣ ObservableObject & @Published

A shared model is a class that conforms to ObservableObject. Mark each property that should trigger UI updates with @Published. It must be a class because observation needs reference semantics — one instance many views share.

import SwiftUI

// An ObservableObject is a reference-type model (a class) SwiftUI can watch.
// Mark each value that should trigger UI updates with @Published.
class CartModel: ObservableObject {
    @Published var items: [String] = []
    @Published var total = 0.0

    func add(_ name: String, price: Double) {
        items.append(name)
        total += price        // each @Published change notifies observers
    }
}

2️⃣ @StateObject — Own the Model

Use @StateObject in the view that creates the model. SwiftUI keeps that single instance alive across refreshes, so its state never resets unexpectedly. Create it once, where it's born.

import SwiftUI

// @StateObject CREATES and OWNS the object. SwiftUI keeps this single
// instance alive across view refreshes — use it where the object is born.
struct CartScreen: View {
    @StateObject private var cart = CartModel()

    var body: some View {
        VStack {
            Text("Items: \(cart.items.count)")
            Text("Total: \(cart.total)")
            Button("Add coffee") {
                cart.add("Coffee", price: 3.5)   // updates refresh the UI
            }
        }
    }
}

3️⃣ @ObservedObject — Observe a Passed-In Model

@ObservedObject does not own the object — it observes one handed in from a parent. Use it in child views. The pitfall: if you create a model with @ObservedObject, a refresh can recreate it and wipe its state. Own with @StateObject, observe with @ObservedObject.

import SwiftUI

// @ObservedObject does NOT own the object — it observes one passed in.
// Use it in a CHILD that receives a model the parent already owns.
struct CartSummary: View {
    @ObservedObject var cart: CartModel   // handed in from a parent

    var body: some View {
        Text("You have \(cart.items.count) items, total \(cart.total)")
    }
}

struct ParentScreen: View {
    // The PARENT owns the model with @StateObject...
    @StateObject private var cart = CartModel()

    var body: some View {
        VStack {
            CartSummary(cart: cart)       // ...and passes it to the child
            Button("Add") { cart.add("Tea", price: 2.0) }
        }
    }
}

4️⃣ The @Observable Macro (iOS 17+)

From iOS 17, the @Observable macro removes the boilerplate. You drop ObservableObject conformance and @Published; tracked properties are observed automatically. In views you then own the model with plain @State.

import SwiftUI
import Observation

// iOS 17+ : the @Observable macro removes the ObservableObject boilerplate.
// No 'ObservableObject', no '@Published' — tracked properties are observed.
@Observable
class TimerModel {
    var seconds = 0
    func tick() { seconds += 1 }
}

struct TimerView: View {
    // With @Observable you OWN the model using plain @State.
    @State private var model = TimerModel()

    var body: some View {
        VStack {
            Text("Seconds: \(model.seconds)")
            Button("Tick") { model.tick() }
        }
    }
}

Your turn. Fill in the blanks to make an observable model and own it in a view.

// 🎯 YOUR TURN — fill in each ___ then preview in Xcode.
import SwiftUI

// 1) Make this class observable and publish its 'name'.
class UserModel: ___ {            // 👉 the protocol for an observable model
    ___ var name = "Guest"        // 👉 the wrapper that announces changes
}

struct ProfileScreen: View {
    // 2) CREATE and OWN the model in this view.
    ___ private var userModel = UserModel()   // 👉 wrapper that owns the object

    var body: some View {
        Text("Hello, \(userModel.name)")
    }
}

Pro Tips

Common Errors (and the fix)

Mini-Challenge: Score Tracker

Build a GameModel: ObservableObject with a @Published var score = 0 and a scorePoint() method. Own it in a parent with @StateObject, and create a child ScoreBadge that takes the model via @ObservedObject and shows the live score. Then rewrite it using the iOS 17 @Observable macro.

🎉 Lesson Complete!

Practice quiz

What is ObservableObject?

  • A SwiftUI view
  • A protocol for reference-type model classes that publish changes
  • A property wrapper
  • A struct modifier

Answer: A protocol for reference-type model classes that publish changes. ObservableObject is a protocol adopted by a class so SwiftUI can observe its published changes.

Why must an ObservableObject be a class, not a struct?

  • Structs cannot have functions
  • It needs reference semantics so views share one instance
  • Classes are faster
  • The protocol forbids structs

Answer: It needs reference semantics so views share one instance. Shared, observable model state needs reference semantics — a single instance many views reference.

What does the @Published property wrapper do?

  • Stores data on disk
  • Announces changes so observing views refresh
  • Makes a property private
  • Marks a constant

Answer: Announces changes so observing views refresh. @Published emits a change notification whenever the property's value changes, refreshing observers.

Which wrapper CREATES and OWNS an observable object?

  • @ObservedObject
  • @Binding
  • @State
  • @StateObject

Answer: @StateObject. @StateObject creates and owns the object, keeping it alive across view re-creations.

Which wrapper observes an object passed in from elsewhere?

  • @StateObject
  • @ObservedObject
  • @State
  • @Published

Answer: @ObservedObject. @ObservedObject does NOT own the object; it observes an instance handed to it.

What is the danger of using @ObservedObject to create an object inline?

  • It is illegal Swift
  • The object may be recreated and lose its state on a refresh
  • It runs too slowly
  • It cannot publish changes

Answer: The object may be recreated and lose its state on a refresh. @ObservedObject doesn't own the object, so a view refresh can recreate it and discard its state; use @StateObject to own it.

Where should you create a view model with @StateObject?

  • In the view that owns it, once
  • In every child view
  • In the App delegate only
  • Never create it

Answer: In the view that owns it, once. Create it with @StateObject in the owning view so SwiftUI keeps the single instance alive.

What does the @Observable macro (iOS 17+) replace?

  • The View protocol
  • Manually conforming to ObservableObject and marking @Published
  • The @State wrapper
  • NavigationStack

Answer: Manually conforming to ObservableObject and marking @Published. @Observable lets you drop ObservableObject conformance and @Published; tracked properties are observed automatically.

With the @Observable macro, how do you own a model in a view?

  • @StateObject
  • @State
  • @Published
  • @Binding

Answer: @State. With @Observable, you use plain @State to own the model instance in the view.

When several views need the same shared model, you should...

  • Copy it into each view
  • Use @State in each
  • Reference one instance via @StateObject/@ObservedObject
  • Avoid sharing entirely

Answer: Reference one instance via @StateObject/@ObservedObject. Share a single reference-type instance so all views see and react to the same data.

Continue this course

Frequently asked questions

What is ObservableObject?

ObservableObject is a protocol you adopt on a class to make it an observable model. SwiftUI watches such an object and refreshes any view that depends on it when it changes. Because it must be a class (a reference type), many views can share one instance — a single source of truth for shared, app-level data.

What does @Published do?

@Published is a property wrapper used inside an ObservableObject. When the wrapped property changes, it emits a notification through the object's objectWillChange publisher, which tells SwiftUI to refresh observing views. Only properties marked @Published trigger UI updates this way.

What is the difference between @StateObject and @ObservedObject?

@StateObject creates and OWNS the observable object — SwiftUI keeps that single instance alive across view re-creations, so you use it where the object is first created. @ObservedObject does NOT own the object; it observes an instance passed in from a parent. Owning the wrong way (e.g. creating an object with @ObservedObject) risks the object being recreated and losing its state on a refresh.

Why must an ObservableObject be a class?

Observation relies on reference semantics. A class instance has identity, so multiple views can hold a reference to the same object and all see the same updates. Structs are value types and get copied, which would break shared observation. That's why ObservableObject is for classes.

What is the @Observable macro in iOS 17?

The @Observable macro (in the Observation framework) removes the boilerplate: you no longer conform to ObservableObject or mark properties @Published. Just annotate the class with @Observable, and its stored properties are tracked automatically. In views you then own the model with plain @State instead of @StateObject.