{"id":"l-sw-3-2","courseId":"swift-essentials","lang":"en","kind":"lesson","summary":"Give each piece of data one source of truth, using @State for view-owned values, @Binding to share write access, and @Observable classes with @Bindable and the environment for app data.","html":"<p>A SwiftUI view is a struct that SwiftUI creates, reads and throws away whenever it likes. So where does “the user switched on the Large Expenses Only filter” live? Not in a normal property: the struct holding it may be gone a moment later, and from inside <code dir=\"ltr\">body</code> you can’t even change it. SwiftUI’s answer is a small set of property wrappers, each answering one question: <strong>who owns this data?</strong></p>\n<h2 id=\"sec-1\" tabindex=\"-1\">@State: data the view owns</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">ExpensesScreen.swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> SwiftUI</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ExpensesScreen</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">View </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> showLargeOnly </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">some</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> View {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        Toggle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Large expenses only\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isOn</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $showLargeOnly)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">@State</code> tells SwiftUI to keep the value in storage it manages, tied to this view’s place in the hierarchy. The struct can be recreated a hundred times and the value survives. When the value changes, SwiftUI re-reads <code dir=\"ltr\">body</code> and updates the screen.</p>\n<p>The <code dir=\"ltr\">$</code> in <code dir=\"ltr\">$showLargeOnly</code> asks for a <strong>binding</strong>: a two-way connection to the state. <code dir=\"ltr\">Toggle</code> doesn’t own the on/off value; it receives a binding so it can read the current value and write the new one when tapped. Every input control (<code dir=\"ltr\">TextField</code>, <code dir=\"ltr\">Toggle</code>, <code dir=\"ltr\">Picker</code>, <code dir=\"ltr\">DatePicker</code>, <code dir=\"ltr\">Slider</code>) works this way.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Forgetting @State</span></p>\n<p>Write <code dir=\"ltr\">var isExpanded = false</code> and then <code dir=\"ltr\">isExpanded.toggle()</code> inside a button, and the build fails with “cannot use mutating member on immutable value: ‘self’ is immutable”. The fix isn’t <code dir=\"ltr\">mutating</code>; views can’t do that. Add <code dir=\"ltr\">@State private</code> so SwiftUI owns the storage.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">@Binding: borrowing someone else’s state</h2>\n<p>Extract the toggle into its own view, and the child needs to change state that the parent owns:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FilterBar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">View </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @Binding</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> showLargeOnly: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Bool</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">some</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> View {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        Toggle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Large expenses only\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isOn</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $showLargeOnly)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// In ExpensesScreen's body:</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">FilterBar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">showLargeOnly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $showLargeOnly)</span></span></code></pre></div>\n<p>The parent remains the <strong>single source of truth</strong>; the child gets a binding to it. If the child declared <code dir=\"ltr\">@State</code> instead, it would hold a separate copy that starts with the parent’s value and then drifts apart. That’s the bug behind most “my toggle does nothing” questions.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 240\" role=\"img\" aria-labelledby=\"l-sw-3-2-t1\"><defs><marker id=\"arrow-l-sw-3-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-sw-3-2-t1\">ExpensesScreen owns showLargeOnly with State. It passes a binding to FilterBar, whose Toggle writes through the binding back to the state. The store, an Observable class owned by the app, is read by both ExpensesScreen and SettingsScreen.</title>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"30\" width=\"260\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"160\" y=\"58\" text-anchor=\"middle\">ExpensesScreen</text>\n  <text class=\"d-label-muted\" x=\"160\" y=\"82\" text-anchor=\"middle\">@State showLargeOnly</text>\n  <rect class=\"d-box\" x=\"30\" y=\"150\" width=\"260\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"160\" y=\"178\" text-anchor=\"middle\">FilterBar</text>\n  <text class=\"d-label-muted\" x=\"160\" y=\"202\" text-anchor=\"middle\">@Binding showLargeOnly</text>\n  <path class=\"d-arrow\" d=\"M130 100 L130 148\" marker-end=\"url(#arrow-l-sw-3-2-1)\"/>\n  <text class=\"d-label\" x=\"100\" y=\"130\" text-anchor=\"end\">$binding</text>\n  <path class=\"d-arrow d-dashed\" d=\"M190 150 L190 102\" marker-end=\"url(#arrow-l-sw-3-2-1)\"/>\n  <text class=\"d-label\" x=\"200\" y=\"130\">writes back</text>\n  <rect class=\"d-box-success\" x=\"400\" y=\"90\" width=\"250\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"525\" y=\"118\" text-anchor=\"middle\">@Observable BudgetStore</text>\n  <text class=\"d-label-muted\" x=\"525\" y=\"142\" text-anchor=\"middle\">owned once with @State</text>\n  <path class=\"d-arrow\" d=\"M400 110 L292 70\" marker-end=\"url(#arrow-l-sw-3-2-1)\"/>\n  <text class=\"d-label-muted\" x=\"525\" y=\"200\" text-anchor=\"middle\">views that read a property</text>\n  <text class=\"d-label-muted\" x=\"525\" y=\"220\" text-anchor=\"middle\">update when it changes</text>\n</svg><figcaption>One source of truth, shared through bindings</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">@Observable: shared app data</h2>\n<p>A filter switch belongs to one screen. The list of expenses belongs to the whole app: the list shows it, the summary totals it, the add form appends to it. That’s shared identity, which the structs lesson said calls for a class. Mark it with the <code dir=\"ltr\">@Observable</code> macro from the Observation framework:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">BudgetStore.swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Observation</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Foundation</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@Observable</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">final</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> BudgetStore</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses: [Expense] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> []</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> monthlyLimit: Decimal </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1500</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> totalSpent: Decimal {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        expenses.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">$0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> $1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.amount }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> remaining: Decimal {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        monthlyLimit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> totalSpent</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    func</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">_</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense: Expense) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        expenses.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">append</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>No property wrappers on the properties, no publishers. The macro rewrites each stored property so SwiftUI can record which ones a <code dir=\"ltr\">body</code> reads. A view that shows <code dir=\"ltr\">store.remaining</code> reads <code dir=\"ltr\">monthlyLimit</code> and <code dir=\"ltr\">expenses</code> through it, so it updates when either changes; a view that only shows the limit ignores new expenses.</p>\n<p>Notice that <code dir=\"ltr\">totalSpent</code> and <code dir=\"ltr\">remaining</code> are computed, not stored. It’s tempting to keep a <code dir=\"ltr\">var total</code> and update it in <code dir=\"ltr\">add</code>, but then every future method that touches <code dir=\"ltr\">expenses</code> (delete, edit, import) must remember to update it too, and one day one won’t. Store the minimum facts and derive everything else. Observation tracks computed properties through the stored ones they read, so the screen stays correct for free.</p>\n<p>Create the store once, near the top of the app, and own it with <code dir=\"ltr\">@State</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">PocketBudgetApp.swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@main</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PocketBudgetApp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">App </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> BudgetStore</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">some</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Scene {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        WindowGroup</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            ExpensesScreen</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">store</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: store)   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ExpensesScreen now has `let store: BudgetStore`</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(store)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Child views can take the store as a plain <code dir=\"ltr\">let store: BudgetStore</code> property and call <code dir=\"ltr\">store.add(…)</code>; no wrapper is needed to read or call methods. For screens deep in the hierarchy, <code dir=\"ltr\">.environment(store)</code> makes it available without threading it through every initializer, and a view picks it up with <code dir=\"ltr\">@Environment(BudgetStore.self) private var store</code>.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">@Bindable: bindings into an observable object</h2>\n<p>A <code dir=\"ltr\">TextField</code> that edits the monthly limit needs a binding to <code dir=\"ltr\">store.monthlyLimit</code>. <code dir=\"ltr\">@Bindable</code> provides it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> SettingsScreen</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">View </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @Environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(BudgetStore.</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">some</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> View {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        @Bindable</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        Form</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            TextField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Monthly limit\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $store.monthlyLimit, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">format</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">currency</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">code</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">keyboardType</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.decimalPad)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>When the store arrives as a property, write <code dir=\"ltr\">@Bindable var store: BudgetStore</code> instead; the local form shown here is for objects that come from the environment.</p>\n<aside class=\"callout callout-note\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">Note</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Older code: ObservableObject</span></p>\n<p>Before iOS 17, the same job used <code dir=\"ltr\">ObservableObject</code>, <code dir=\"ltr\">@Published</code>, <code dir=\"ltr\">@StateObject</code> and <code dir=\"ltr\">@ObservedObject</code>. You’ll meet them in older tutorials and codebases. New code should use <code dir=\"ltr\">@Observable</code>: less ceremony, and views update only for the properties they read.</p>\n</aside>\n<p>To decide which wrapper to use, ask who owns the data. Does this view own a simple value? <code dir=\"ltr\">@State</code>. Does it need to change a value someone else owns? <code dir=\"ltr\">@Binding</code>. Is it shared app data? An <code dir=\"ltr\">@Observable</code> class, owned once with <code dir=\"ltr\">@State</code> and passed down. The next lesson puts the store to work in a real list with navigation and an add-expense form.</p>\n","toc":[{"id":"sec-1","text":"@State: data the view owns","level":2},{"id":"sec-2","text":"@Binding: borrowing someone else’s state","level":2},{"id":"sec-3","text":"@Observable: shared app data","level":2},{"id":"sec-4","text":"@Bindable: bindings into an observable object","level":2}],"takeaways":["<code dir=\"ltr\">@State</code> stores a value outside the view struct so it survives re-renders; mark it <code dir=\"ltr\">private</code>, because the view owns it.","A <code dir=\"ltr\">$</code> prefix gives you a <code dir=\"ltr\">Binding</code>: read and write access to someone else’s source of truth, which is what controls like <code dir=\"ltr\">Toggle</code> and <code dir=\"ltr\">TextField</code> need.","Mark a shared model class with <code dir=\"ltr\">@Observable</code>, and SwiftUI re-renders exactly the views whose <code dir=\"ltr\">body</code> read a property that changed.","Own an <code dir=\"ltr\">@Observable</code> object with <code dir=\"ltr\">@State</code> at the top, then pass it down directly or through <code dir=\"ltr\">.environment(_:)</code>.","Use <code dir=\"ltr\">@Bindable</code> when you need bindings to the properties of an <code dir=\"ltr\">@Observable</code> object, for example to edit them in a form."],"further":[{"title":"Managing model data in your app","url":"https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app"},{"title":"State","url":"https://developer.apple.com/documentation/swiftui/state"},{"title":"Observation","url":"https://developer.apple.com/documentation/observation"}],"quiz":[{"q":"<p>A view declares <code dir=\"ltr\">var count = 0</code> and a button runs <code dir=\"ltr\">count += 1</code>. What happens?</p>","options":[{"text":"It compiles, but the screen never updates.","why":"It doesn’t get that far. A view’s <code dir=\"ltr\">body</code> can’t mutate a plain stored property, so the build fails."},{"text":"It fails to compile, because <code dir=\"ltr\">self</code> is immutable inside <code dir=\"ltr\">body</code>; the property needs <code dir=\"ltr\">@State</code>.","why":"Correct. <code dir=\"ltr\">@State</code> moves the storage out of the struct into SwiftUI, which makes it writable and observed."},{"text":"It works, because SwiftUI views are classes.","why":"Views are structs, and that’s exactly why plain properties can’t change from inside <code dir=\"ltr\">body</code>."}],"answer":1},{"q":"<p>A parent owns <code dir=\"ltr\">@State private var showLargeOnly = false</code>. A child <code dir=\"ltr\">FilterBar</code> must flip it. What does the child declare, and what does the parent pass?</p>","options":[{"text":"Child: <code dir=\"ltr\">@Binding var showLargeOnly: Bool</code>. Parent passes <code dir=\"ltr\">$showLargeOnly</code>.","why":"Correct. The binding gives the child read and write access while the parent stays the single source of truth."},{"text":"Child: <code dir=\"ltr\">@State var showLargeOnly: Bool</code>. Parent passes <code dir=\"ltr\">showLargeOnly</code>.","why":"<code dir=\"ltr\">@State</code> would create a second, independent copy; the parent would never see the change."},{"text":"Child: <code dir=\"ltr\">var showLargeOnly: Bool</code>. Parent passes <code dir=\"ltr\">$showLargeOnly</code>.","why":"A plain property can’t receive a <code dir=\"ltr\">Binding</code>, and even with a matching type it couldn’t write back."}],"answer":0},{"q":"<p>Which views re-render when <code dir=\"ltr\">store.monthlyLimit</code> changes on an <code dir=\"ltr\">@Observable</code> store?</p>","options":[{"text":"Every view in the app.","why":"Observation tracks access per property, so unrelated views are left alone."},{"text":"Every view that holds a reference to the store.","why":"Holding a reference isn’t enough; a view is only tracked for properties its <code dir=\"ltr\">body</code> actually read."},{"text":"Only views whose <code dir=\"ltr\">body</code> read <code dir=\"ltr\">monthlyLimit</code>, or a computed property that uses it.","why":"Correct. That fine-grained tracking is the main improvement over the older <code dir=\"ltr\">ObservableObject</code>."}],"answer":2},{"q":"<p>A settings screen gets the store with <code dir=\"ltr\">@Environment(BudgetStore.self) private var store</code> and needs <code dir=\"ltr\">$store.monthlyLimit</code> for a <code dir=\"ltr\">TextField</code>. What do you add?</p>","options":[{"text":"Change the property wrapper to <code dir=\"ltr\">@State</code>.","why":"<code dir=\"ltr\">@State</code> would create a new store owned by this screen instead of using the shared one."},{"text":"<code dir=\"ltr\">@Bindable var store = store</code> at the top of <code dir=\"ltr\">body</code>.","why":"Correct. <code dir=\"ltr\">@Bindable</code> creates bindings to an <code dir=\"ltr\">@Observable</code> object’s properties, and it can be declared as a local in <code dir=\"ltr\">body</code>."},{"text":"Mark <code dir=\"ltr\">monthlyLimit</code> with <code dir=\"ltr\">@Published</code>.","why":"<code dir=\"ltr\">@Published</code> belongs to the older <code dir=\"ltr\">ObservableObject</code> system and doesn’t create bindings by itself."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"swift","code":"import SwiftUI\n\nstruct ExpenseCard: View {\n    let expense: Expense\n    var isExpanded = false\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            Text(expense.title)\n                .font(.headline)\n            if isExpanded {\n                Text(expense.date, format: .dateTime.weekday().day().month())\n                Text(expense.category.title)\n            }\n            Button(isExpanded ? \"Less\" : \"More\") {\n                isExpanded.toggle()\n            }\n        }\n    }\n}","bugLines":[5],"title":"The card that won't expand","prompt":"<p>This card should show the date and category when the user taps More. It doesn’t build: Xcode reports “cannot use mutating member on immutable value: ‘self’ is immutable” on the <code dir=\"ltr\">toggle()</code> call. Select the line you need to change to fix it.</p>\n","hints":["The error appears on the toggle line, but that line is a symptom. Where is <code dir=\"ltr\">isExpanded</code> declared?","View structs can’t change their own plain stored properties from inside <code dir=\"ltr\">body</code>.","Which property wrapper tells SwiftUI to own and store a value for this view?"],"explanation":"<p>Line 5 declares <code dir=\"ltr\">isExpanded</code> as a plain stored property of a struct, so nothing inside <code dir=\"ltr\">body</code> can change it, and SwiftUI wouldn’t know to redraw even if it could. Declare it as view-owned state:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> isExpanded </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</span></span></code></pre></div>\n<p>Now SwiftUI stores the value outside the struct, <code dir=\"ltr\">toggle()</code> compiles, and every tap re-renders the card. <code dir=\"ltr\">private</code> signals that only this view owns the value; a parent that needs to control it would pass a <code dir=\"ltr\">@Binding</code> instead.</p>\n","checkLabels":{}},"words":688,"readMinutes":3}