State Management: @State, @Binding & the Observation Framework

iOS Development Fundamentals

Chapter 6 · State Management: @State, @Binding & the Observation Framework

Every SwiftUI view so far has been static. This chapter covers how a view actually changes in response to real user interaction — and resolves a real, genuine puzzle Chapters 4-5 deliberately left open: if a view is a cheap struct that SwiftUI recreates constantly, how does it ever "remember" anything between those recreations?

@State: A View's Own Local Data

struct CounterView: View { @State private var count = 0 var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } } } }
Resolving the Real Puzzle
@State is a real, genuine exception to the "struct copy is fully independent" rule from Chapter 4. SwiftUI stores a @State property's own actual value outside the view struct itself, in storage the framework manages and keys to that specific view's own identity in the view tree. Every time SwiftUI recreates the CounterView struct, the new struct's own count property is reconnected to that same real, persistent storage — the struct itself is disposable, but the state it references survives.
Why private
Marking a @State property private is Apple's own real, standard convention — it belongs to this specific view alone. When a value genuinely needs to be shared with a child view, it's passed explicitly, not exposed directly, which is exactly what @Binding covers next.

The $ Prefix & @Binding

Every SwiftUI property wrapper exposes a real projected value, accessed with a $ prefix — for @State, that projected value is a real, two-way connection to the underlying data, called a Binding. Passing $count instead of count hands a child view the ability to both read and write the parent's own state, not just a copy of its current value.

struct SettingsView: View { @State private var notificationsOn = false var body: some View { ToggleRow(isOn: $notificationsOn) } } struct ToggleRow: View { @Binding var isOn: Bool var body: some View { Toggle("Enable Notifications", isOn: $isOn) } }
A Real, Common Mix-Up
ToggleRow flipping its own real Toggle switch genuinely updates SettingsView's own notificationsOn — this is the whole point of a real Binding, not a bug. Passing plain notificationsOn (no $) instead of $notificationsOn would only hand over the current value, one-way, with no way for the child to write back — and wouldn't even compile against a @Binding parameter, since the types genuinely differ (Bool vs. Binding<Bool>).

Shared Model Data: The Real Observation Framework

@State and @Binding are for one value, owned by one view, optionally shared down to its own children. Real apps also need genuinely shared, reference-type model objects — data used across several unrelated parts of the screen at once. As of iOS 17 / Swift 5.9, Apple's real, current answer is the Observation framework, replacing the older ObservableObject protocol and its required @Published property wrapper.

@Observable final class UserProfile { var name = "Sam" var age = 30 } struct ProfileEditor: View { @Bindable var profile: UserProfile var body: some View { TextField("Name", text: $profile.name) } }
ToolReal Use Case
@StateA simple, value-type property owned by one view
@BindingA two-way connection to a value owned by a parent view
@ObservableMarks a class as a real, shared model whose changes SwiftUI automatically tracks — replaces ObservableObject/@Published
@BindableLets a view create real two-way bindings ($profile.name) into an @Observable class's own properties
Less Boilerplate, Genuinely
Under the older ObservableObject approach, UserProfile's properties each needed an explicit @Published annotation to be tracked. @Observable tracks every stored property automatically — real, current Apple guidance, and one clear, direct example of the site's own Claude Code Agents courses' broader theme that a fast-moving framework's tutorials can genuinely go stale; always check which pattern a source is actually describing.

Hands-On Exercises

Exercise 1

Build a StepperView with an @State private var value = 0, showing the current value in a Text alongside two Buttons ("+" and "-") that adjust it. Explain, in your own words, why value correctly persists across taps even though SwiftUI recreates the StepperView struct on every update.

📄 View solution
Exercise 2

Build a parent view with @State private var username = "", passing a Binding<String> down to a child view UsernameField that renders a real TextField bound to it. Confirm typing in the field updates the parent's own state.

📄 View solution
Exercise 3

Explain, in your own words, why a shared piece of data used across two unrelated parts of an app's screen is a better real fit for an @Observable class than for @State, connecting your answer to structs being value types and classes being reference types (Chapter 4).

📄 View solution

Chapter 6 Quick Reference

  • @State: a view's own local value, persisted by SwiftUI outside the disposable struct itself
  • $ prefix: a property wrapper's projected value — for @State, a real, two-way Binding
  • @Binding: lets a child view read and write a value it doesn't itself own
  • @Observable (iOS 17/Swift 5.9): marks a class as a real, automatically-tracked shared model, replacing ObservableObject/@Published
  • @Bindable: creates real two-way bindings into an @Observable class's own properties from inside a view