@State vs @StateObject vs @ObservedObject: The Definitive SwiftUI Comparison

SwiftUI gives you several property wrappers for holding state, and picking the wrong one is one of the most common sources of confusing bugs for developers new to the framework — a view that doesn’t update, or a value that resets unexpectedly. Here’s what each one actually does.

@State: Local, View-Owned Value Types

@State is for simple value-type state (a String, Int, Bool, or a struct) that belongs to one view and doesn’t need to be shared. SwiftUI stores the value outside the view struct itself, so it survives the view being redrawn.

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        Button("Count: (count)") {
            count += 1
        }
    }
}

@State properties should almost always be marked private — the whole point is that this state is owned by this view alone.

@StateObject: Owning a Reference-Type Model

@StateObject is for when a view needs to create and own an instance of a class conforming to ObservableObject. SwiftUI guarantees it creates the object exactly once for the lifetime of the view, even if the view is recreated due to a parent redrawing — which matters because a class you create fresh on every body evaluation would lose its state constantly.

class UserViewModel: ObservableObject {
    @Published var name = ""
}

struct ProfileView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        TextField("Name", text: $viewModel.name)
    }
}

@ObservedObject: Receiving a Model From Outside

@ObservedObject also watches an ObservableObject for changes, but it does not take ownership — it’s for when the object is created elsewhere and passed in. Using @ObservedObject for an object the view itself creates is the classic mistake: if the parent view redraws, a new instance gets created and all state on it is lost.

struct ProfileDetailView: View {
    @ObservedObject var viewModel: UserViewModel // passed in from a parent

    var body: some View {
        Text(viewModel.name)
    }
}

Choosing Between Them: One Rule

Did this view create the object, or receive it? Created it → @StateObject. Received it as a parameter → @ObservedObject. And if it’s not a class at all, just a value type like a struct, string, or number owned by this view → @State.

If you’re targeting iOS 17 or later, Apple’s newer @Observable macro replaces ObservableObject/@Published entirely and removes the @StateObject-vs-@ObservedObject distinction — see our guide on migrating to @Observable.