StateObject vs ObservedObject in SwiftUI

Updated Apr 19, 2024#swiftui

In SwiftUI, StateObject and ObservedObject are both property wrappers used to manage state in your views, but they have different lifecycles and use cases.

Use StateObject when the view is the source of truth for the object. It’s created by the view and should be tied to the view’s lifecycle. The object is retained by the view across redraws, preserving its state.

Use ObservedObject when the object’s lifecycle is managed outside the view, such as when it’s passed into the view. The object may be recreated when the view is redrawn, which can lead to loss of state if not handled properly.

import SwiftUI
import Combine

class Counter: ObservableObject {
    @Published var count: Int = 0
    
    func increment() {
        count += 1
    }
    
    func decrement() {
        count -= 1
        if count < 0 {
            count = 0
        }
    }
}

struct CounterView: View {
    @ObservedObject var counter: Counter
    
    var body: some View {
        VStack {
            HStack {
                Button("-") {
                    counter.decrement()
                }
                Button("+") {
                    counter.increment()
                }
            }
        }
    }
}

struct ContentView: View {
    @StateObject private var counter = Counter()
    
    var body: some View {
        VStack {
            CounterView(counter: counter)
            Text("Count: \(counter.count)")
        }
    }
}

With the introduction of the new Observation framework since iOS 17, StateObject and ObservedObject are being phased out in favor of new property wrappers that integrate more seamlessly with the updated SwiftUI data flow.

  • The new Observable() macro replaces the ObservableObject protocol and is used to add observation support to a class at compile time, simplifies the process of making an object observable by the view.
  • The updated State wrapper is now used for properties owned by the view, which was previously the role of StateObject, ensures that the state is retained across view updates.
  • New property wrapper Bindable replaces ObservedObject for properties that are passed to the view from another view or object, allows for a binding to a parent view’s state.
  • The updated Environment wrapper can be used for shared data across different parts of the app, similar to the previous EnvironmentObject.