{"id":"l-sw-3-3","courseId":"swift-essentials","lang":"en","kind":"lesson","summary":"Show expenses in a List, push a detail screen with NavigationStack and value-based links, and collect new expenses in a validated Form presented as a sheet.","html":"<p>Pocket Budget has a store, a row view and state that drives the screen. Now it needs to become an app people can use: a list of this month’s expenses, a tap that opens the details, and a plus button that opens a form. Those three patterns (list, push navigation and modal form) make up the skeleton of most iPhone apps you’ve used.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">A list of identifiable rows</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\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store: BudgetStore</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\"> isAdding </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\">        NavigationStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            List</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                ForEach</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(store.expenses) { expense </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    NavigationLink</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\">: expense) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                        ExpenseRow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">expense</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">onDelete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { offsets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    store.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">delete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">at</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: offsets)</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">overlay</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">                if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store.expenses.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isEmpty</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    ContentUnavailableView</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"No expenses yet\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemImage</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tray\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                                           description</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Tap + to add your first one.\"</span><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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">navigationTitle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Expenses\"</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\">navigationDestination</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Expense.</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { expense </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                ExpenseDetail</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">expense</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">toolbar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Add expense\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemImage</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"plus\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    isAdding </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> true</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">sheet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isPresented</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $isAdding) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                AddExpenseView</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>\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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>That’s the whole main screen; take it one piece at a time. <code dir=\"ltr\">ForEach(store.expenses)</code> produces a row per expense. It needs a stable identity for each element so that, when an expense is inserted or deleted, SwiftUI animates the right row instead of redrawing everything, and that identity is the <code dir=\"ltr\">id</code> that <code dir=\"ltr\">Identifiable</code> requires. <code dir=\"ltr\">.onDelete</code> adds swipe-to-delete and hands you the positions to remove; the store’s <code dir=\"ltr\">delete(at:)</code> is one line, <code dir=\"ltr\">expenses.remove(atOffsets: offsets)</code>. That method is defined in SwiftUI, so <code dir=\"ltr\">BudgetStore.swift</code> needs <code dir=\"ltr\">import SwiftUI</code> alongside <code dir=\"ltr\">Observation</code>.</p>\n<p><code dir=\"ltr\">ContentUnavailableView</code> is the system’s standard empty state. An empty list with no explanation looks broken; a short message and a hint at the next action looks finished.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Navigation with values</h2>\n<p><code dir=\"ltr\">NavigationStack</code> manages a stack of screens: the list at the bottom, details pushed on top, and the system back button and swipe gesture for free.</p>\n<p>Navigation is split into two halves. <code dir=\"ltr\">NavigationLink(value: expense)</code> says <em>what</em> was tapped. <code dir=\"ltr\">.navigationDestination(for: Expense.self)</code> says <em>which screen</em> shows an <code dir=\"ltr\">Expense</code>. The value must be <code dir=\"ltr\">Hashable</code>, which <code dir=\"ltr\">Expense</code> already is. Keeping the two halves apart means the destination is declared once, however many places link to an expense, and it makes programmatic navigation possible:</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\">@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\"> path: [Expense] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><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 body:</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">NavigationStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $path) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">    // …the same List and modifiers as above</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\">// Anywhere in this view, for example after saving:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">path.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">append</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(newExpense)    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// pushes ExpenseDetail</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">path.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">removeAll</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()           </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// pops back to the list</span></span></code></pre></div>\n<p>The path is plain state, so navigation becomes data you can inspect, test and change from code. That’s how you’d open a specific expense from a notification, or jump back to the root after a multi-step flow. For screens with mixed destination types, <code dir=\"ltr\">NavigationPath</code> plays the same role with type-erased values.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-sw-3-3-t1\"><defs><marker id=\"arrow-l-sw-3-3-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-3-t1\">Inside a NavigationStack, the Expenses list pushes the Expense detail screen when a row is tapped, and back pops it. Separately, the plus button presents the Add Expense form as a sheet, which is dismissed by Save or Cancel.</title>\n  <rect class=\"d-box d-dashed\" x=\"10\" y=\"20\" width=\"430\" height=\"190\" rx=\"14\"/>\n  <text class=\"d-label-muted\" x=\"225\" y=\"44\" text-anchor=\"middle\">NavigationStack</text>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"70\" width=\"170\" height=\"110\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"115\" y=\"118\" text-anchor=\"middle\">Expenses</text>\n  <text class=\"d-label-muted\" x=\"115\" y=\"142\" text-anchor=\"middle\">List</text>\n  <rect class=\"d-box-accent\" x=\"250\" y=\"70\" width=\"170\" height=\"110\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"335\" y=\"118\" text-anchor=\"middle\">Detail</text>\n  <text class=\"d-label-muted\" x=\"335\" y=\"142\" text-anchor=\"middle\">pushed</text>\n  <path class=\"d-arrow\" d=\"M200 105 L248 105\" marker-end=\"url(#arrow-l-sw-3-3-1)\"/>\n  <path class=\"d-arrow d-dashed\" d=\"M250 150 L202 150\" marker-end=\"url(#arrow-l-sw-3-3-1)\"/>\n  <text class=\"d-label\" x=\"225\" y=\"96\" text-anchor=\"middle\">tap</text>\n  <text class=\"d-label\" x=\"225\" y=\"172\" text-anchor=\"middle\">back</text>\n  <rect class=\"d-box-success\" x=\"500\" y=\"70\" width=\"180\" height=\"110\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"590\" y=\"118\" text-anchor=\"middle\">Add Expense</text>\n  <text class=\"d-label-muted\" x=\"590\" y=\"142\" text-anchor=\"middle\">sheet</text>\n  <path class=\"d-arrow\" d=\"M200 85 C 300 0, 450 0, 520 68\" marker-end=\"url(#arrow-l-sw-3-3-1)\"/>\n  <text class=\"d-label\" x=\"470\" y=\"30\" text-anchor=\"middle\">+ button</text>\n</svg><figcaption>Push navigation and a modal sheet</figcaption></figure>\n<p>The detail screen can be a <code dir=\"ltr\">Form</code> used for display, with <code dir=\"ltr\">LabeledContent</code> rows that format values for you:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"2\"><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\"> ExpenseDetail</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\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense: Expense</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\">        Form</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            LabeledContent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Amount\"</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\">: expense.amount, </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:#0550AE;--shiki-dark:#79C0FF\">            LabeledContent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Category\"</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\">: expense.category.title)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            LabeledContent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Date\"</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\">: expense.date, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">format</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .dateTime.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">year</span><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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">navigationTitle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.title)</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<h2 id=\"sec-3\" tabindex=\"-1\">A form in a sheet</h2>\n<p>Pushing is for drilling into content. A <strong>sheet</strong> is for a self-contained task the user finishes or abandons, like adding an expense. <code dir=\"ltr\">.sheet(isPresented: $isAdding)</code> shows the form whenever <code dir=\"ltr\">isAdding</code> becomes true. Here’s the form:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">AddExpenseView.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\"> AddExpenseView</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\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store: BudgetStore</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @Environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(\\.dismiss) </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\"> dismiss</span></span>\n<span class=\"line\"></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\"> title </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\"</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\"> amount: Decimal</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</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\"> category: Category </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .food</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\"> date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Date.now</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><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\"> canSave: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Bool</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        !</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">title.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">trimmingCharacters</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .whitespaces).</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isEmpty</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</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\"> 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\">        NavigationStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</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\">                Section</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Details\"</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\">\"Title\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $title)</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\">\"Amount\"</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\">: $amount, </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:#0550AE;--shiki-dark:#79C0FF\">                Section</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    Picker</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Category\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selection</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $category) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                        ForEach</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(Category.allCases, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \\.</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { category </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                            Label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category.title, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemImage</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: category.symbolName)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">tag</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category)</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:#0550AE;--shiki-dark:#79C0FF\">                    DatePicker</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selection</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $date, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">Date.now, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">displayedComponents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .date)</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">navigationTitle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"New Expense\"</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\">navigationBarTitleDisplayMode</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.inline)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">toolbar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                ToolbarItem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">placement</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .cancellationAction) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Cancel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">dismiss</span><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:#0550AE;--shiki-dark:#79C0FF\">                ToolbarItem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">placement</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .confirmationAction) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Save\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">                        guard</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">else</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                        store.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Expense</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: title, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">amount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: amount, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: category, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: date))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                        dismiss</span><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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">disabled</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">canSave)</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>\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>The sheet wraps its form in its own <code dir=\"ltr\">NavigationStack</code>. A sheet is a separate presentation that doesn’t inherit the list’s stack, and without one there’s no bar to hold the title or the Cancel and Save buttons. Placing them with <code dir=\"ltr\">.cancellationAction</code> and <code dir=\"ltr\">.confirmationAction</code> puts them where iOS users expect.</p>\n<p>Each field binds to its own <code dir=\"ltr\">@State</code>, so the form is a draft: nothing touches the store until Save. Cancel throws the draft away. That’s value semantics paying off again.</p>\n<p>The amount field is the important one. <code dir=\"ltr\">TextField(value:format:)</code> parses what the user types using their locale, so “12,50” in Germany becomes 12.5, and <code dir=\"ltr\">amount</code> only ever holds a successfully parsed number, or <code dir=\"ltr\">nil</code> while there isn’t one. This replaces the lenient <code dir=\"ltr\">Decimal(string:)</code> parsing from the optionals lesson. <code dir=\"ltr\">guard let amount</code> in the Save action unwraps it, and <code dir=\"ltr\">canSave</code> disables the button until both title and amount make sense, so invalid input can’t be saved at all. The <code dir=\"ltr\">in: ...Date.now</code> range stops future dates. <code dir=\"ltr\">dismiss</code> comes from the environment and closes whatever presented this view.</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>Picker tags of the wrong type</span></p>\n<p>If <code dir=\"ltr\">selection</code> is a <code dir=\"ltr\">Category</code> but you write <code dir=\"ltr\">.tag(category.rawValue)</code>, the code compiles, the picker shows, and choosing a row changes nothing, because a <code dir=\"ltr\">String</code> tag never equals a <code dir=\"ltr\">Category</code> selection. Tags must have exactly the selection’s type. With an optional selection such as <code dir=\"ltr\">Category?</code>, tag with <code dir=\"ltr\">Optional(category)</code>.</p>\n</aside>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Validate by disabling, explain with text</span></p>\n<p>A disabled Save button prevents bad data, but on its own it can leave people guessing. For anything less obvious than “fill in the title”, add a short footer to the section saying what’s missing.</p>\n</aside>\n<p>Pocket Budget is now a working app with a real flow from list to detail to form. It forgets everything when you quit, though, and it knows nothing beyond the phone. The final section fixes both, starting with how Swift runs work that takes time.</p>\n","toc":[{"id":"sec-1","text":"A list of identifiable rows","level":2},{"id":"sec-2","text":"Navigation with values","level":2},{"id":"sec-3","text":"A form in a sheet","level":2}],"takeaways":["<code dir=\"ltr\">List</code> and <code dir=\"ltr\">ForEach</code> need to tell rows apart, which is why your models conform to <code dir=\"ltr\">Identifiable</code>.","<code dir=\"ltr\">NavigationLink(value:)</code> plus <code dir=\"ltr\">.navigationDestination(for:)</code> separates what was tapped from which screen shows it.","Use a sheet for a self-contained task such as adding an expense, and dismiss it with the <code dir=\"ltr\">dismiss</code> environment value.","A <code dir=\"ltr\">TextField</code> with a <code dir=\"ltr\">format:</code> turns typing into a typed value using the user’s locale, instead of hand-parsing strings.","A <code dir=\"ltr\">Picker</code>’s <code dir=\"ltr\">.tag</code> values must have exactly the same type as its <code dir=\"ltr\">selection</code>, or the picker silently stops working."],"further":[{"title":"NavigationStack","url":"https://developer.apple.com/documentation/swiftui/navigationstack"},{"title":"Lists","url":"https://developer.apple.com/documentation/swiftui/lists"},{"title":"Form","url":"https://developer.apple.com/documentation/swiftui/form"}],"quiz":[{"q":"<p>You write <code dir=\"ltr\">NavigationLink(value: expense)</code> but tapping a row does nothing. What’s the most likely cause?</p>","options":[{"text":"There’s no <code dir=\"ltr\">.navigationDestination(for: Expense.self)</code> inside the <code dir=\"ltr\">NavigationStack</code>.","why":"Correct. A value link only says what was selected; the destination modifier decides what to show for it."},{"text":"<code dir=\"ltr\">Expense</code> doesn’t conform to <code dir=\"ltr\">Codable</code>.","why":"Navigation values must be <code dir=\"ltr\">Hashable</code>, not <code dir=\"ltr\">Codable</code>. Codable only matters if you save the path."},{"text":"The row needs an <code dir=\"ltr\">.onTapGesture</code>.","why":"<code dir=\"ltr\">NavigationLink</code> handles the tap itself; adding a gesture can actually block it."}],"answer":0},{"q":"<p>The category picker uses <code dir=\"ltr\">selection: $category</code> where <code dir=\"ltr\">category</code> is a <code dir=\"ltr\">Category</code>, and its rows are tagged with <code dir=\"ltr\">.tag(category.rawValue)</code>. What happens?</p>","options":[{"text":"It works, because SwiftUI converts raw values automatically.","why":"SwiftUI compares tags to the selection by type and value; a <code dir=\"ltr\">String</code> never equals a <code dir=\"ltr\">Category</code>."},{"text":"The picker shows, but choosing a row never changes the selection.","why":"Correct. The tags are <code dir=\"ltr\">String</code>s and the selection is a <code dir=\"ltr\">Category</code>, so nothing matches. Tag with the enum value itself."},{"text":"The build fails with a type error.","why":"<code dir=\"ltr\">.tag</code> accepts any <code dir=\"ltr\">Hashable</code> value, so this compiles and fails quietly at runtime, which makes it nasty."}],"answer":1},{"q":"<p>Why bind the amount field to a <code dir=\"ltr\">Decimal?</code> with <code dir=\"ltr\">format: .currency(code: &quot;USD&quot;)</code> instead of a <code dir=\"ltr\">String</code>?</p>","options":[{"text":"Strings can’t be used with <code dir=\"ltr\">TextField</code>.","why":"<code dir=\"ltr\">TextField(&quot;Title&quot;, text: $title)</code> is the most common text field there is."},{"text":"It makes the keyboard numeric automatically.","why":"The format doesn’t change the keyboard; that’s what <code dir=\"ltr\">.keyboardType(.decimalPad)</code> is for."},{"text":"The field parses the user’s input with their locale and hands you a typed <code dir=\"ltr\">Decimal</code>, so there’s no string to parse.","why":"Correct. “12,50” in Germany becomes 12.5, and you never write a parser yourself."}],"answer":2}],"exercise":{"kind":"fill","language":"swift","template":"NavigationStack {\n    List(store.expenses) { expense in\n        NavigationLink({{0}}: expense) {\n            ExpenseRow(expense: expense)\n        }\n    }\n    .navigationTitle(\"Expenses\")\n    .{{1}}(for: Expense.self) { expense in\n        ExpenseDetail(expense: expense)\n    }\n    .toolbar {\n        Button(\"Add expense\", systemImage: \"plus\") { isAdding = true }\n    }\n    .{{2}}(isPresented: $isAdding) {\n        AddExpenseView(store: store)\n    }\n}","blanks":[{"answers":["value"]},{"answers":["navigationDestination"]},{"answers":["sheet"]}],"title":"Wire up the main screen","prompt":"<p>Complete Pocket Budget’s main screen. Tapping a row must push <code dir=\"ltr\">ExpenseDetail</code> for that expense using value-based navigation, and the plus button must present <code dir=\"ltr\">AddExpenseView</code> modally while <code dir=\"ltr\">isAdding</code> is true.</p>\n","hints":["A value-based link names the value it carries with an argument label.","The modifier that maps a value type to a destination screen has “navigation” in its name and takes <code dir=\"ltr\">for:</code>.","The modifier that presents a modal card over the current screen has a one-word name."],"explanation":"<p><code dir=\"ltr\">NavigationLink(value: expense)</code> only records which expense was tapped. <code dir=\"ltr\">.navigationDestination(for: Expense.self)</code> tells the enclosing <code dir=\"ltr\">NavigationStack</code> what to show for any <code dir=\"ltr\">Expense</code> value, so the two work as a pair; without the destination, taps do nothing. <code dir=\"ltr\">.sheet(isPresented: $isAdding)</code> presents the form whenever <code dir=\"ltr\">isAdding</code> becomes <code dir=\"ltr\">true</code>, and sets it back to <code dir=\"ltr\">false</code> when the sheet is dismissed.</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:#0550AE;--shiki-dark:#79C0FF\">NavigationLink</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\">: 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:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">navigationDestination</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Expense.</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\">…</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\">sheet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isPresented</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $isAdding) { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">…</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span></code></pre></div>\n","checkLabels":{}},"words":697,"readMinutes":3}