Observation in SwiftUI: Ownership After iOS 17
The confusion
SwiftUI’s new Observation APIs make a lot of code feel cleaner.
@Observable removes boilerplate, @Bindable makes child editing easier, and @State can now hold reference types.
That simplicity creates a new kind of confusion. Developers see fewer wrappers and start assuming the architecture got simpler too. Then questions return in a different form:
- Who owns this model?
- Who is allowed to mutate it?
- Which view is responsible for creating it?
The syntax changed. The ownership problem did not.
What SwiftUI is actually doing
Observation tells SwiftUI when data was read and when that data changes. That lets the framework invalidate views more precisely than the older publisher-based approach.
What it does not do is decide responsibility. Observation can make a model easier to share, but sharing is not the same thing as owning. That distinction matters most in screens that mix local editing, reusable subviews, and cross-feature services.
In practice, iOS 17 gives you better tools for expressing intent:
@Statecan own an observable model at the screen boundary@Bindablecan expose mutation to children@Environmentcan inject contextual dependencies
The architecture still has to be designed by you.
The mental model
Observation changes how SwiftUI tracks updates, not who owns the data.
If a screen creates the model, that screen owns it. If a child needs to edit it, that child should borrow editing access. If a dependency is contextual, inject it explicitly instead of burying ownership inside a view tree.
That means the real question is no longer “should this be ObservableObject or @Observable?”
The better question is:
Where does this data live, and who is responsible for it over time?
A small proof
import SwiftUI
import Observation
@Observable
final class ProfileDraft {
var displayName = "Pascal"
var email = "pascal@example.com"
}
struct ProfileScreen: View {
@State private var draft = ProfileDraft()
var body: some View {
ProfileEditor(draft: draft)
}
}
struct ProfileEditor: View {
@Bindable var draft: ProfileDraft
var body: some View {
Form {
TextField("Display name", text: $draft.displayName)
TextField("Email", text: $draft.email)
}
}
}
This shows three separate responsibilities.
ProfileScreenowns the draft instance.ProfileEditorgets editable access.- SwiftUI redraws only what depends on the changed properties.
Now compare that to a settings screen where a parent owns the model, but a nested row edits one field. The shape of the code changes, but the ownership rule stays identical.
Why this matters in real apps
The biggest win is readability. When ownership stays at the boundary, reusable views become easier to extract because they do not have to decide lifecycle.
It also helps during refactors. If you move a child view into a separate file, the model wiring still makes sense: the parent owns, the child binds, the environment injects.
This is especially useful in feature modules where a screen might own draft state, while analytics, formatting, or feature flags come from ambient dependencies. Those are different jobs and should look different in code.
The result is less accidental coupling:
- screens own their local lifecycle
- editors receive focused mutation access
- services stay injectable and testable
Where this model breaks down
@Observable is not always the right answer.
Some models still fit better as @StateObject, especially when you need explicit object lifetime management or have older code paths that already depend on Combine-style observation.
And not every child should receive a binding surface.
If a subview only needs to display data, passing read-only values is usually clearer.
There is also a boundary between shared editing and global application state. Observation can blur that line if every view gets direct mutation access to everything. When that happens, the app becomes easy to update but hard to reason about.
So the rule is not “make everything observable.” The rule is “keep ownership obvious, even when observation gets easier.”
One sentence to remember
Observation helps SwiftUI notice change, but ownership still decides architecture.
Next steps
If you want to keep that boundary sharp, these are the best follow-ups: