SwiftUI
ObservableObject
data binding
nested objects
app development

How to tell SwiftUI views to bind to nested ObservableObjects

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

SwiftUI redraws a view when the object that view is observing publishes objectWillChange. That works well until one observable object owns another observable object. At that point, many developers expect nested changes to "bubble up" automatically, but ObservableObject does not do that for you.

Why Nested ObservableObject State Is Tricky

Consider this model structure:

swift
1import Combine
2
3final class UserSettings: ObservableObject {
4    @Published var username = "mark"
5}
6
7final class AppState: ObservableObject {
8    @Published var settings = UserSettings()
9}

At first glance, it looks reasonable. AppState is observable, and settings is marked @Published. The catch is that @Published only emits when the settings property itself changes to a different object. Mutating settings.username does not replace the settings reference, so the parent object does not publish a new change automatically.

That is why a view observing AppState can miss updates originating inside UserSettings.

Best Pattern: Observe the Child Directly

If a subview cares about child state, pass the child object into that subview and let the subview observe it directly.

swift
1import SwiftUI
2
3final class UserSettings: ObservableObject {
4    @Published var username = "mark"
5}
6
7final class AppState: ObservableObject {
8    let settings = UserSettings()
9}
10
11struct ParentView: View {
12    @StateObject private var state = AppState()
13
14    var body: some View {
15        SettingsView(settings: state.settings)
16    }
17}
18
19struct SettingsView: View {
20    @ObservedObject var settings: UserSettings
21
22    var body: some View {
23        VStack {
24            Text("User: \(settings.username)")
25            Button("Rename") {
26                settings.username = "alice"
27            }
28        }
29    }
30}

This is the most idiomatic solution because the view observes the object that actually owns the changing property.

Forward Child Changes When the Parent Must React

Sometimes the parent view model must expose derived state or coordinate multiple nested models. In that case, forward the child's publisher into the parent's objectWillChange.

swift
1import Combine
2
3final class UserSettings: ObservableObject {
4    @Published var username = "mark"
5}
6
7final class AppState: ObservableObject {
8    let settings: UserSettings
9    private var cancellables = Set<AnyCancellable>()
10
11    init(settings: UserSettings = UserSettings()) {
12        self.settings = settings
13
14        settings.objectWillChange
15            .sink { [weak self] _ in
16                self?.objectWillChange.send()
17            }
18            .store(in: &cancellables)
19    }
20}

Now any view observing AppState will refresh when settings changes internally. This is the right approach when the parent genuinely represents a composed model rather than just a container of unrelated references.

Value Types Are Often Simpler

If the nested state is small and does not need its own subscriptions or object identity, a value type is usually cleaner than a nested observable class.

swift
1import SwiftUI
2
3struct UserSettings {
4    var username = "mark"
5}
6
7final class AppState: ObservableObject {
8    @Published var settings = UserSettings()
9}
10
11struct ContentView: View {
12    @StateObject private var state = AppState()
13
14    var body: some View {
15        VStack {
16            Text(state.settings.username)
17            Button("Rename") {
18                state.settings.username = "alice"
19            }
20        }
21    }
22}

Because settings is a struct, mutating username mutates the settings value itself, which triggers the @Published publisher on the parent.

Property Wrapper Ownership Still Matters

Nested observables often look broken when the real bug is ownership:

  • Use @StateObject when a view creates and owns the observable object.
  • Use @ObservedObject when the object is created elsewhere and injected.
  • Use @EnvironmentObject when shared state should be read across many branches.

If the parent creates AppState, it should usually own it with @StateObject. If a child view receives UserSettings from the parent, the child should use @ObservedObject.

Avoid Giant Root Models

A common architecture smell is forcing every view to depend on a giant root AppState object. That makes refresh behavior harder to predict because everything depends on everything else.

A better rule is: each view should observe the smallest object that contains the state it actually needs. This keeps redraw behavior more obvious and reduces accidental coupling.

It also makes previews and tests simpler:

swift
1import SwiftUI
2
3struct SettingsView_Previews: PreviewProvider {
4    static var previews: some View {
5        SettingsView(settings: UserSettings())
6    }
7}

The subview can be previewed independently without constructing the whole application state tree.

Common Pitfalls

One common mistake is marking the child reference @Published and assuming that inner child mutations will trigger the parent automatically. They will not unless the reference itself changes.

Another issue is forgetting to retain the Combine subscription when forwarding objectWillChange. If the cancellable is not stored, the forwarding stops immediately.

Developers also sometimes use nested observable classes where a plain struct would be easier and less error-prone. Object identity is useful, but it adds complexity that small state objects often do not need.

Finally, avoid observing the parent everywhere just because it is available. If a view needs child state, observing the child directly is usually the clearest design.

Summary

  • Nested ObservableObject changes do not automatically propagate to a parent observer.
  • The cleanest fix is usually to observe the child object directly in the subview that uses it.
  • If the parent must react to child updates, forward the child's objectWillChange.
  • Small nested state is often better modeled as a value type inside a @Published property.
  • Correct @StateObject and @ObservedObject ownership is essential for predictable SwiftUI updates.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.