Skip to content
Prepare to Grow

SwiftUI Fundamentals

Observation and Shared Model Data

Manage feature state with the Observation framework, shared models, environment injection, and clear ownership boundaries.

Intermediate6 min read2 guided activities

Before you begin

What you will understand

  • Create an observable model with @Observable.
  • Own an observable model with State.
  • Use Bindable for editable projections.
  • Share dependencies through the environment without hiding ownership.
01

Learning stage

Concept Explanation

The Observation framework lets SwiftUI track which observable properties a view reads. When one of those properties changes, SwiftUI invalidates views that formed a dependency on it. The @Observable macro synthesizes observation behavior while your model remains an ordinary Swift type with explicit properties and methods.

A view can own an observable reference by storing it in State. Child views that only read the model accept it as a normal property. A child that needs bindings to observable properties uses Bindable. This keeps ownership, reading, and editing as separate decisions.

The environment can distribute a model or service through a subtree when many descendants need it. Environment access is not a replacement for architecture: create the dependency at a clear composition root, inject it deliberately, and keep feature APIs explicit where practical.

  • @Observable: synthesizes fine-grained change tracking for a model.
  • @State model: a view identity owns the observable instance.
  • @Bindable: creates bindings to editable observable properties.
  • Environment: supplies a dependency to a view subtree.
  • Composition root: the clear place where shared dependencies are created.
02

Learning stage

Syntax & Examples

The root owns one library model; descendants read it normally and the editor uses Bindable to create a text-field binding.

LibraryFeature.swiftswift
import Observation
import SwiftUI

@Observable
final class LibraryModel {
  var searchText = ""
  var lessonTitles = ["State", "Navigation", "Animation"]

  var filteredTitles: [String] {
    guard !searchText.isEmpty else { return lessonTitles }
    return lessonTitles.filter { $0.localizedCaseInsensitiveContains(searchText) }
  }
}

struct LibraryScreen: View {
  @State private var model = LibraryModel()

  var body: some View {
    LibraryContent(model: model)
  }
}

struct LibraryContent: View {
  @Bindable var model: LibraryModel

  var body: some View {
    List(model.filteredTitles, id: .self) { Text($0) }
      .searchable(text: $model.searchText)
  }
}
One model owns feature data. The content view forms dependencies by reading filteredTitles and creates a binding through @Bindable.
03

Learning stage

Real-World Usage

Connect the SwiftUI concept to complete, maintainable application features.

  • Hold feature state that several related views read and edit.
  • Inject app-wide session or settings models at the app root.
  • Derive filtered or sorted output from observable source data.
  • Expose domain methods so views send intent instead of mutating unrelated properties.
04

Learning stage

Common Mistakes

Identify patterns that weaken ownership, identity, adaptation, accessibility, or user experience.

  • Creating an observable model inside body so it is replaced during updates.
  • Putting every app value into one global environment model.
  • Using observation to hide unclear ownership or dependency creation.
  • Making derived collections stored mutable properties that require manual synchronization.
  • Letting views directly coordinate persistence, networking, and business rules through many public fields.
05

Learning stage

Practice Exercises

Build the behavior in a small Xcode project and review it in multiple states.

Activity 1

Create an observable favorites model

Build a @Observable model with lessons, selected IDs, a derived selected count, and toggleSelection(id:). Own it in a root view and display it in two children.

Hint: Let children call a meaningful model method instead of editing the Set directly.

Activity 2

Inject shared settings

Create a small observable settings model at the app root, place it in the environment, and read it in a descendant. Add a preview that supplies the dependency.

Hint: A preview must reproduce every environment dependency required by the view.

06

Learning stage

Knowledge Check

Answer each question before revealing the explanation.

QUESTION 1

How does a SwiftUI view form an Observation dependency?

  1. A.By reading an observable property in its body
  2. B.By importing Foundation
  3. C.By subclassing UIView
  4. D.By using a global notification
Reveal answer

By reading an observable property in its body

SwiftUI tracks observable properties accessed during view evaluation and invalidates affected views when those values change.

QUESTION 2

When is @Bindable useful?

Reveal answer

When a view receives an observable model and needs bindings to one or more of its editable properties.

The model remains owned elsewhere while the child can pass projected bindings to controls.

07

Learning stage

Interview Preparation

Explain the design in terms of data flow, identity, lifecycle, platform behavior, and trade-offs.

How does modern Observation differ from simply making every property State?

Short answer: State expresses view ownership; Observation tracks changes inside a separate model that can be shared across views.

Explore the detailed answer

A State value is tied to a view identity. An @Observable model groups feature state and behavior, and SwiftUI tracks the properties each view reads. A root can own the model with State, children can receive it normally, and Bindable supplies editable projections without duplicating ownership.

Possible follow-ups

  • • When would you use the environment?
  • • Why should derived data remain computed?
08

Learning stage

Key Takeaways

Observation supports shared, fine-grained feature state while preserving normal Swift types. Create models at explicit ownership boundaries, pass them deliberately, and use environment injection only where subtree-wide access is meaningful.

  • @Observable creates trackable model properties.
  • State can own an observable instance.
  • Bindable exposes editable projections.
  • Environment dependencies need an explicit composition root.
  • Observation does not replace clear domain boundaries.