Which property wrapper for which data?

SwiftUI Mid-level Swift 5.9+ iOS 17+ Reviewed By Alok Singh

Pick the fix

An iOS 17 app creates a SettingsModel (@Observable) at the root, and many deep views read it. Which setup?

Options
Hint 1 of 2

Who owns the model?

Hint 2 of 2

Which wrappers belong to Observation?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Root: @State var settings = SettingsModel() plus .environment(settings); readers: @Environment(SettingsModel.self) var settings

Correct. The root owns it, and the environment shares it without passing it through every view.

Why not the others?
  • B Root: @StateObject plus .environmentObject(settings). Those are for ObservableObject. An @Observable class doesn't conform, so this doesn't compile.
  • C Pass it through every initializer. It works, but prop drilling through ten layers is noisy and fragile.
  • D static let shared = SettingsModel(). A singleton hides the dependency and makes previews and tests harder.

30-second answer

Ask who owns the data. The view that creates it owns it with @State (values or an @Observable model). A child that must write a parent's value takes @Binding. A child that needs bindings into an @Observable model uses @Bindable. Shared app-wide models go in .environment(model) and are read with @Environment(Model.self).

Practise saying it · 60 s. No recording, just a timer

Explain how you pick a SwiftUI property wrapper.

Say your answer out loud, then check which of these points you covered:

  • Start from ownership
  • @State owns, @Binding writes a parent value
  • @Bindable for bindings into a model
  • Environment for shared models

Deep dive

@Observable is a macro, not a property wrapper. It makes a class observable property by property.

@StateObject, @ObservedObject and @EnvironmentObject belong to ObservableObject. Keep them for iOS 16 support or legacy code.

@AppStorage and @SceneStorage persist small values to UserDefaults or scene state.

Plain let properties are enough when a child only reads an @Observable model.

NeedObservation (iOS 17+)ObservableObject (legacy)
View owns a model@State@StateObject
Child reads a modelplain let@ObservedObject
Child needs bindings@Bindable@ObservedObject + $
Shared via environment.environment / @Environment(T.self).environmentObject / @EnvironmentObject

Common mistakes

  • Using @StateObject with an @Observable class.
  • Passing models through every initializer instead of using the environment for app-wide state.
  • Using a global singleton, which hides dependencies and makes previews and tests hard.

Senior take & follow-ups

Seniors answer with ownership first, then wrappers, and note the Observation versus ObservableObject split.

Follow-up questions

Think of your answer first, then open the hint.

  1. What does @Environment(Model.self) do if no model was injected?

    Show hint

    It traps at runtime; inject it in previews too.

  2. When is a plain let enough?

    Show hint

    When the child only reads the model.

Suggest a fix if something here is wrong or out of date.