@StateObject vs @ObservedObject
In SwiftUI, @StateObject and @ObservedObject are both used for managing reference-type data in a view, but they have different use cases.
@StateObject
- Used when creating a new instance of an
ObservableObject
inside a view. - The view owns the object, meaning SwiftUI manages its lifecycle.
- It is retained when the view is recreated, preventing the object from being reset.
class ViewModel: ObservableObject {
@Published var count = 0
}
struct MyView: View {
@StateObject private var viewModel = ViewModel() // Owned by this view
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment") { viewModel.count += 1 }
}
}
}
Use @StateObject when a view is responsible for initializing and owning an observable object.
@ObservedObject
- Used when passing an existing
ObservableObject
instance into a view. - The view does not own the object but just observes it.
- If the parent view recreates the view, the
@ObservedObject
may get reset.
struct ChildView: View {
@ObservedObject var viewModel: ViewModel // Reference from parent
var body: some View {
Text("Count: \(viewModel.count)")
}
}
struct ParentView: View {
@StateObject private var viewModel = ViewModel() // Owned by the parent
var body: some View {
ChildView(viewModel: viewModel) // Passing down
}
}
Use @ObservedObject when a view receives an observable object from elsewhere, but does not own it.
🆚 Key Differences
🔥 Common Mistake: Using @ObservedObject instead of @StateObject
If you mistakenly use @ObservedObject when initializing an object inside a view, SwiftUI may recreate it every time the view refreshes, leading to unexpected resets.
struct MyView: View {
@ObservedObject var viewModel = ViewModel() // ❌ Wrong! Should be @StateObject
var body: some View {
Text("Count: \(viewModel.count)")
}
}
✅ Corrected:
struct MyView: View {
@StateObject private var viewModel = ViewModel() // ✅ Correct
var body: some View {
Text("Count: \(viewModel.count)")
}
}
🎯 Rule of Thumb
- Use
@StateObject
when creating an observable object inside the view. - Use
@ObservedObject
when receiving an observable object from another view/wireframe (or coordinator).
🎯 Best Practice for Wireframe/Coordinator-Managed ViewModels:
📱 Real-World Example: Task Manager App (Using @StateObject & @ObservedObject)
Let’s build a simple Task Manager app where:
- The MainView owns a
TaskViewModel
(so it uses@StateObject
). - The TaskListView receives the same
TaskViewModel
and observes it (so it uses@ObservedObject
). - Users can add tasks, and the list updates in real-time.
🏗 Step 1: Create the TaskViewModel
This class will store a list of tasks and allow adding new ones.
import SwiftUI
class TaskViewModel: ObservableObject {
@Published var tasks: [String] = []
func addTask(_ task: String) {
tasks.append(task)
}
}
📜 Step 2: Create the TaskListView (Uses @ObservedObject)
This view will display the list of tasks and observe changes from TaskViewModel.
struct TaskListView: View {
@ObservedObject var viewModel: TaskViewModel // Receiving an existing object
var body: some View {
List(viewModel.tasks, id: \\.self) { task in
Text(task)
}
}
}
🏠 Step 3: Create the MainView (Uses @StateObject)
This view owns the TaskViewModel and passes it down to TaskListView.
struct MainView: View {
@StateObject private var viewModel = TaskViewModel() // Owned by this view
@State private var newTask = ""
var body: some View {
VStack {
TextField("Enter task", text: $newTask)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button("Add Task") {
viewModel.addTask(newTask)
newTask = "" // Clear input field
}
.padding()
// Pass viewModel to the TaskListView
TaskListView(viewModel: viewModel)
}
}
}
🎯 How It Works?
✅ MainView
uses @StateObject
because it creates and owns the TaskViewModel
.
✅ TaskListView
uses @ObservedObject
because it receives the TaskViewModel
from MainView
but does not own it.
🏆 Final Result
- Users type a task into the
TextField
and tap "Add Task." - The task is stored in
TaskViewModel
and displayed in the list. - Because
TaskListView
observesTaskViewModel
, it updates automatically when new tasks are added.
🚀 This ensures data persistence and correct ownership of the observable object.