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.