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:
importSwiftUI
classCounterModel:ObservableObject {
@Publishedvarcount=0
funcincrement() {
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.
importSwiftUI
structCounterView:View {
@ObservedObjectvarmodel:CounterModel
varbody:someView {
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.
importSwiftUI
classProfileModel:ObservableObject {
@Publishedvarusername=""
@PublishedvarnotificationsOn=false
}
structProfileView:View {
@ObservedObjectvarmodel:ProfileModel
varbody:someView {
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
importSwiftUI
classTimerModel:ObservableObject {
@Publishedvarseconds=0
functick() {
seconds+=1
}
}
structTimerView:View {
@ObservedObjectvarmodel:TimerModel
varbody:someView {
VStack(spacing:10) {
Text("Seconds: \(model.seconds)")
Button("Tick") {model.tick() }
}
.padding()
}
}
Example 2: Simple editable form model
importSwiftUI
classContactModel:ObservableObject {
@Publishedvarname=""
@Publishedvaremail=""
}
structContactFormView:View {
@ObservedObjectvarmodel:ContactModel
varbody:someView {
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
importSwiftUI
classSettingsModel:ObservableObject {
@PublishedvarisDarkMode=false
}
structSettingsToggleView:View {
@ObservedObjectvarmodel:SettingsModel
varbody:someView {
Toggle("Dark Mode",isOn: $model.isDarkMode)
.padding()
}
}
structStatusView:View {
@ObservedObjectvarmodel:SettingsModel
varbody:someView {
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:
CSS
importSwiftUI
structCounterModel:ObservableObject {
@Publishedvarcount=0
}
Why it breaks: ObservableObject is designed for reference types, so a class is the right tool.
Correct approach:
CSS
importSwiftUI
classCounterModel:ObservableObject {
@Publishedvarcount=0
}
Mistake 2: Forgetting @Published on changing properties
What you might do:
importSwiftUI
classCounterModel:ObservableObject {
varcount=0
}
Why it breaks: SwiftUI will not be notified when count changes.
Correct approach:
CSS
importSwiftUI
classCounterModel:ObservableObject {
@Publishedvarcount=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