@StateObject is a property wrapper in SwiftUI that creates and owns an ObservableObject instance throughout the entire lifecycle of a View. When a View first appears on screen, @StateObject initializes the object and stores it until the View is removed from memory. This ensures that data is not reset when the interface is rebuilt — for example, when switching themes or updating the parent View. According to Apple Developer Documentation (2025), @StateObject should be used as the primary source of truth for ObservableObject in the SwiftUI hierarchy, while child Views receive the already created object via @ObservedObject or @EnvironmentObject.
Key Takeaways
@StateObject is a property wrapper introduced in iOS 14 that allows a View to create and own an instance of a class conforming to the ObservableObject protocol. Unlike @State, which works with value types (structs), @StateObject is designed for reference types — classes that can notify SwiftUI about changes to their properties.
When a View uses @StateObject var viewModel: MyViewModel, SwiftUI automatically creates an instance of MyViewModel when the View first appears and stores it in a special framework storage. On every View update (for example, when the parent state changes), SwiftUI does not recreate the object — it uses the existing instance until the View is removed from the hierarchy.
According to Apple WWDC Session 10137 (2024), @StateObject solves the data loss problem that existed in iOS 13 when Views were rebuilt, forcing developers to create ObservableObject in the parent View and pass it through the initializer. This led to code duplication and the risk of accidentally recreating the object.
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment", action: viewModel.increment)
}
}
}
The @StateObject mechanism is based on the integration of SwiftUI with the Combine framework. When an ObservableObject marks its properties with the @Published attribute, SwiftUI automatically subscribes to changes via the publisher built into the ObservableObject protocol. When a published property changes, the object sends a signal through the objectWillChange publisher, which triggers a redraw of all Views observing this object.
SwiftUI stores the ObservableObject instance in a special storage tied to a specific View instance. This storage is created once during the first render and exists until the View is destroyed. This is why @StateObject guarantees reference stability — SwiftUI manages memory automatically, without relying on the View's initializer.
According to objc.io — Thinking in SwiftUI (2025), the internal implementation of @StateObject uses a mechanism similar to @State but for reference types: SwiftUI creates a boxing wrapper around the object and manages its lifecycle through its own allocator, optimized for frequent View hierarchy rebuilds.
The main difference between @StateObject and @ObservedObject lies in who owns the object. @StateObject creates and stores the object — it is the owner. @ObservedObject only observes the object that was created elsewhere and passed via initializer or property.
| Characteristic | @StateObject | @ObservedObject |
|---|---|---|
| Ownership | Creates and owns the object | Only observes |
| Initialization | Inside the View via init/default | External, passed via parameter |
| Lifecycle | Tied to the View's lifecycle | Not controlled by the View |
| Recreation | Not recreated on update | Can be replaced externally |
| iOS version | iOS 14+ | iOS 13+ |
The rule is simple: if the View creates the ObservableObject — use @StateObject. If the View only receives an already created object from the parent — use @ObservedObject. Violating this rule leads either to data loss (if using @ObservedObject for ownership) or to excessive object creation (if using @StateObject for observation).
@StateObject should be used in Views that are the source of truth for a specific data set. Typical scenarios include screens with their own view model, root screens of navigation stacks, and modal presentations managing their own state.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Save") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
Initializing @StateObject with parameters requires special syntax, since SwiftUI manages object creation on its own. You cannot simply pass parameters to the initializer — you need to use an escaping closure or a separate factory method.
According to Swift by Sundell (2024), the cleanest approach is to use a factory method or closure that SwiftUI will call when the object is first created. An alternative approach is to initialize the ObservableObject in the parent View and pass it via @StateObject using the standard initializer.
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
It is important to remember that the View initializer with @StateObject should use an underscore before the property name (_viewModel) to access the property wrapper itself, not its value. This is a standard Swift pattern for working with property wrappers in initializers.
The most common mistake is using @ObservedObject instead of @StateObject for a View that should own the object. In this case, each time the parent is rebuilt, the object will be recreated, leading to the loss of all accumulated data. This mistake is especially insidious in complex hierarchies with NavigationStack or TabView.
To avoid these problems, follow a simple rule: one @StateObject per source of truth. If data should be shared across multiple screens — create @StateObject once in the root View and pass it via @ObservedObject or @EnvironmentObject to child elements.
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
@ObservedObject var vm = ViewModel() // will be recreated on each update!
}
// ✅ Correct: @StateObject for owning
struct GoodView: View {
@StateObject var vm = ViewModel() // created once for View lifetime
}
Frequently Asked Questions
@State works with value types (structs, strings, numbers) and stores the value directly in SwiftUI storage. @StateObject works with reference types — classes conforming to ObservableObject. @State is suitable for simple local states, @StateObject is for complex objects with logic and published properties.
No, @StateObject is only available from iOS 14 and above. For iOS 13, use @ObservedObject and create the ObservableObject in the parent View via @State with manual lifecycle management. An alternative is to use @State with a struct instead of a class for data that does not require reference semantics.
The child View will create its own copy of the ObservableObject, completely independent from the parent's. Changes in one will not affect the other. This is almost always a mistake: use @ObservedObject to receive an object from the parent and @StateObject only to create a new object inside the View.
The object is destroyed when the View that created it is completely removed from the SwiftUI hierarchy. For a screen in NavigationStack, this happens on pop from the navigation stack. For a modal window — when it is dismissed. For TabView — when switching tabs, if the View is not cached.
Use a custom init with access to the property wrapper via underscore: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). This pattern allows passing any parameters to the ObservableObject while maintaining the guarantee of a single object creation during the View's lifetime.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also