How to Use @ObservedObject in SwiftUI
What you’ll build or solve
You’ll set up a SwiftUI view that observes an existing model instance and refreshes when that model changes.
When this approach works best
@ObservedObject works best when:
Learn Swift on Mimo
- A parent or coordinator creates the model and passes it into the view.
- You want multiple views to observe the same model instance.
- You want to keep view state lightweight and keep logic in a model class.
This is a bad idea when the view should create and own the model’s lifecycle. In that case, use @StateObject in the owning view instead.
Prerequisites
- Xcode with a SwiftUI project
- You already have an observable model instance created outside the view
- Basic knowledge of Swift classes
Example model used in this guide:
Swift
import SwiftUI
class CounterModel: ObservableObject {
@Published var count = 0
func increment() {
count += 1
}
}
Step-by-step instructions
Step 1: Observe the external model with @ObservedObject
Declare the model in your view using @ObservedObject. The view does not create the model, it observes a model passed in from elsewhere.
Swift
import SwiftUI
struct CounterView: View {
@ObservedObject var model: CounterModel
var body: some View {
VStack(spacing: 12) {
Text("Count: \(model.count)")
Button("Add") {
model.increment()
}
}
.padding()
}
}
What to look for
- The view declares
@ObservedObject var model: CounterModel. - The view reads values from
modellikemodel.count. - Updating the model triggers a view refresh as long as the property is
@Published.
Step 2: Bind UI controls to published properties when needed
When a control needs a Binding, use $model.property. This works when the property is @Published.
Swift
import SwiftUI
class ProfileModel: ObservableObject {
@Published var username = ""
@Published var notificationsOn = false
}
struct ProfileView: View {
@ObservedObject var model: ProfileModel
var body: some View {
Form {
TextField("Username", text: $model.username)
Toggle("Notifications", isOn: $model.notificationsOn)
}
}
}
What to look for
$model.usernameand$model.notificationsOncreate bindings for controls.- The view stays in sync when you type or toggle.
Examples you can copy
Example 1: Observe a passed-in model
Swift
import SwiftUI
class TimerModel: ObservableObject {
@Published var seconds = 0
func tick() {
seconds += 1
}
}
struct TimerView: View {
@ObservedObject var model: TimerModel
var body: some View {
VStack(spacing: 10) {
Text("Seconds: \(model.seconds)")
Button("Tick") { model.tick() }
}
.padding()
}
}
Example 2: Simple editable form model
Swift
import SwiftUI
class ContactModel: ObservableObject {
@Published var name = ""
@Published var email = ""
}
struct ContactFormView: View {
@ObservedObject var model: ContactModel
var body: some View {
VStack(spacing: 12) {
TextField("Name", text: $model.name)
.textFieldStyle(.roundedBorder)
TextField("Email", text: $model.email)
.textFieldStyle(.roundedBorder)
}
.padding()
}
}
Example 3: Shared settings model in multiple views
Swift
import SwiftUI
class SettingsModel: ObservableObject {
@Published var isDarkMode = false
}
struct SettingsToggleView: View {
@ObservedObject var model: SettingsModel
var body: some View {
Toggle("Dark Mode", isOn: $model.isDarkMode)
.padding()
}
}
struct StatusView: View {
@ObservedObject var model: SettingsModel
var body: some View {
Text(model.isDarkMode ? "Dark" : "Light")
}
}
Pass the same SettingsModel instance to both views to keep them aligned.
Common mistakes and how to fix them
Mistake 1: Using a struct for the model
What you might do:
Swift
import SwiftUI
struct CounterModel: ObservableObject {
@Published var count = 0
}
Why it breaks: ObservableObject is designed for reference types, so a class is the right tool.
Correct approach:
Swift
import SwiftUI
class CounterModel: ObservableObject {
@Published var count = 0
}
Mistake 2: Forgetting @Published on changing properties
What you might do:
Swift
import SwiftUI
class CounterModel: ObservableObject {
var count = 0
}
Why it breaks: SwiftUI will not be notified when count changes.
Correct approach:
Swift
import SwiftUI
class CounterModel: ObservableObject {
@Published var count = 0
}
Troubleshooting
- If the view does not update, confirm the model property is marked
@Published. - If you see
Cannot convert value of type 'String' to expected argument type 'Binding<String>', pass$model.propertyto the control. - If values reset or flicker, check that the model instance is not being recreated by the parent on every render.
- If you need the view to own the model, switch to
@StateObjectin the owning view instead of@ObservedObject.
Quick recap
- Use
@ObservedObjectwhen the model is created elsewhere and passed in. - The model should be a class that conforms to
ObservableObject. - Mark changing model properties with
@Published. - Use
$model.propertyto bind controls to published properties. - Use
@StateObjectwhen the view owns the model’s lifecycle.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot