{"id":"l-sw-4-3","courseId":"swift-essentials","lang":"en","kind":"lesson","summary":"Turn Expense into a SwiftData model, set up a model container, read with @Query, insert and delete through the model context, and edit saved objects with @Bindable.","html":"<p>Add five expenses, quit Pocket Budget, open it again, and the list is empty. Everything lived in memory. Users expect their data to survive restarts, updates and a phone that ran out of battery mid-save. <strong>SwiftData</strong> is Apple’s persistence framework built for Swift: you describe your model in code, and it handles the database, the schema and keeping SwiftUI in sync.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Making Expense a model</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Expense.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\"> Foundation</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> SwiftData</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@Model</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\"> Expense</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\"> title: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount: Decimal</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> category: Category</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> date: Date</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> note: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    init</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">amount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Decimal, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">category</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Category, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">date</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\"> .now) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.title </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> title</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.category </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> category</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        self</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</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>Two things changed from the struct you’ve used since the modelling section. First, <code dir=\"ltr\">@Model</code> requires a <strong>class</strong>. A saved expense has one identity: the list, the detail screen and the edit form must all see the same record, which is the reference semantics you learned to reach for when data is shared. Second, you write an initializer yourself, because classes don’t get a memberwise one.</p>\n<p><code dir=\"ltr\">@Model</code> makes the class conform to <code dir=\"ltr\">PersistentModel</code>, which brings <code dir=\"ltr\">Identifiable</code> and <code dir=\"ltr\">Hashable</code>, so <code dir=\"ltr\">ForEach</code> and <code dir=\"ltr\">NavigationLink(value:)</code> keep working unchanged. The <code dir=\"ltr\">Category</code> enum needs to conform to <code dir=\"ltr\">Codable</code> to be stored; add it to its declaration. You can delete the <code dir=\"ltr\">id</code> property: SwiftData gives every model a persistent identifier.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">One container for the app</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"1\"><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\">    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>\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\">modelContainer</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>\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 <strong>model container</strong> owns the database file and the schema. Creating it once on the scene also puts a <strong>model context</strong> into the environment. The context is your workspace: it tracks inserted, changed and deleted models and writes them to disk. The main context saves automatically, so most apps never call <code dir=\"ltr\">save()</code> by hand.</p>\n<p>Create the container in exactly one place. A <code dir=\"ltr\">ModelContainer</code> built inside a view’s <code dir=\"ltr\">body</code> or initializer gets recreated whenever SwiftUI rebuilds that view, and each copy opens the store again. Put it on the scene, as here, and every view below shares the same one.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-sw-4-3-t1\"><defs><marker id=\"arrow-l-sw-4-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-4-3-t1\">The ModelContainer owns the store on disk. Its main ModelContext sits in the SwiftUI environment. Views read through Query and write through the context with insert and delete; the context autosaves to the store and Query results update.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"70\" width=\"150\" height=\"80\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"95\" y=\"105\" text-anchor=\"middle\">Store on disk</text>\n  <text class=\"d-label-muted\" x=\"95\" y=\"128\" text-anchor=\"middle\">SQLite file</text>\n  <rect class=\"d-box-primary\" x=\"215\" y=\"70\" width=\"170\" height=\"80\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"300\" y=\"105\" text-anchor=\"middle\">ModelContext</text>\n  <text class=\"d-label-muted\" x=\"300\" y=\"128\" text-anchor=\"middle\">autosaves</text>\n  <path class=\"d-arrow\" d=\"M215 110 L172 110\" marker-end=\"url(#arrow-l-sw-4-3-1)\"/>\n  <rect class=\"d-box-success\" x=\"460\" y=\"20\" width=\"220\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"570\" y=\"50\" text-anchor=\"middle\">@Query expenses</text>\n  <text class=\"d-label-muted\" x=\"570\" y=\"72\" text-anchor=\"middle\">reads, stays up to date</text>\n  <rect class=\"d-box-accent\" x=\"460\" y=\"130\" width=\"220\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"570\" y=\"160\" text-anchor=\"middle\">context.insert / delete</text>\n  <text class=\"d-label-muted\" x=\"570\" y=\"182\" text-anchor=\"middle\">writes</text>\n  <path class=\"d-arrow\" d=\"M385 95 L458 60\" marker-end=\"url(#arrow-l-sw-4-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M458 165 L387 130\" marker-end=\"url(#arrow-l-sw-4-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"300\" y=\"200\" text-anchor=\"middle\">created once by .modelContainer(for:)</text>\n</svg><figcaption>Container, context and queries</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">Reading with @Query, writing through the context</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"2\"><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\">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\">    @Query</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(sort</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> \\Expense.date, order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .reverse) </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\"> expenses: [Expense]</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @Environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(\\.modelContext) </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\"> context</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\">(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\">                        Text</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>\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:#CF222E;--shiki-dark:#FF7B72\">                    for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> index </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> offsets {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                        context.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">delete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expenses[index])</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 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\">                EditExpenseView</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">@Query</code> fetches every <code dir=\"ltr\">Expense</code>, newest first, and re-runs whenever the stored data changes, so a delete or insert anywhere in the app updates this list on its own. The query result is read-only: you change data through the context. In the add form from the SwiftUI section, the Save button now calls <code dir=\"ltr\">context.insert(Expense(title: title, amount: amount, category: category, date: date))</code> instead of <code dir=\"ltr\">store.add(...)</code>. The expenses no longer live in <code dir=\"ltr\">BudgetStore</code>; it keeps app settings such as the monthly limit and the exchange rates.</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>Editing the query result</span></p>\n<p><code dir=\"ltr\">expenses.remove(atOffsets: offsets)</code> doesn’t compile, because <code dir=\"ltr\">@Query</code> exposes a read-only array. And removing items from a copy wouldn’t delete anything from the database anyway. Always go through <code dir=\"ltr\">context.delete</code>, and the query catches up.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Editing with @Bindable</h2>\n<p>A model is an observable class, so <code dir=\"ltr\">@Bindable</code> gives you bindings straight into it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"3\"><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\"> EditExpenseView</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\">    @Bindable</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</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\">            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\">: $expense.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\">: $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\">            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\">: $expense.date, </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 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<p>There’s no Save button. Typing changes the model, the context notices and autosaves, and the list behind updates too. If you want Cancel semantics instead, copy the values into <code dir=\"ltr\">@State</code> drafts as the add form does, and write them back on Save.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Filtering</h2>\n<p>Pass a predicate to <code dir=\"ltr\">@Query</code> to fetch a subset. When the filter depends on a value the view receives, build the query in <code dir=\"ltr\">init</code>:</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\"> RecentExpensesView</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\">    @Query</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\"> recent: [Expense]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    init</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">since</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> start: Date) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        _recent </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Query</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#Predicate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">$0</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\"> start },</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                        sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \\.date, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .reverse)</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\">        List</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(recent) { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Text</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\">.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<p><code dir=\"ltr\">#Predicate</code> is checked at compile time and translated into a database query, so filtering happens in storage rather than in a Swift loop. Keep predicates to simple stored properties such as dates, numbers and strings. Filtering on enum properties inside a predicate has historically been unreliable, so filter categories in Swift after fetching, or store the category’s raw value as a <code dir=\"ltr\">String</code> if you need to query by it.</p>\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>Previews and tests get their own store</span></p>\n<p><code dir=\"ltr\">.modelContainer(for: Expense.self, inMemory: true)</code> in a <code dir=\"ltr\">#Preview</code> gives you a throwaway database, so you can insert sample expenses without polluting real data. Tests do the same with <code dir=\"ltr\">ModelContainer(for: Expense.self, configurations: ModelConfiguration(isStoredInMemoryOnly: true))</code>.</p>\n</aside>\n<p>Adding a new property later, like <code dir=\"ltr\">note</code> above, works without extra code when it has a default value: SwiftData migrates the store automatically. A rename needs a hint, <code dir=\"ltr\">@Attribute(originalName: &quot;oldName&quot;)</code>, so the old column carries over; changing a property’s type needs a versioned schema and a migration plan, which you can postpone until you have real users with real data.</p>\n<p>Pocket Budget now survives restarts. The last lesson makes sure it keeps working as you change it, with Swift Testing, and gets it onto other people’s phones through TestFlight.</p>\n","toc":[{"id":"sec-1","text":"Making Expense a model","level":2},{"id":"sec-2","text":"One container for the app","level":2},{"id":"sec-3","text":"Reading with @Query, writing through the context","level":2},{"id":"sec-4","text":"Editing with @Bindable","level":2},{"id":"sec-5","text":"Filtering","level":2}],"takeaways":["<code dir=\"ltr\">@Model</code> turns a class into persisted data; SwiftData builds the storage schema from its stored properties.","<code dir=\"ltr\">.modelContainer(for:)</code> on the app’s scene creates the database once and puts a model context in the environment.","<code dir=\"ltr\">@Query</code> fetches, sorts and filters models for a view and keeps the result up to date as data changes.","Insert and delete through <code dir=\"ltr\">modelContext</code>; changes to a model’s properties save automatically, including edits bound with <code dir=\"ltr\">@Bindable</code>.","[object Object]"],"further":[{"title":"SwiftData","url":"https://developer.apple.com/documentation/swiftdata"},{"title":"Preserving your app's model data across launches","url":"https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches"},{"title":"Query","url":"https://developer.apple.com/documentation/swiftdata/query"}],"quiz":[{"q":"<p>Why does <code dir=\"ltr\">Expense</code> change from a struct to a <code dir=\"ltr\">final class</code> when it becomes a SwiftData model?</p>","options":[{"text":"Classes are faster to save than structs.","why":"Speed isn’t the reason. It’s about identity."},{"text":"<code dir=\"ltr\">@Model</code> requires a class, because a saved record has one identity that every screen observes and edits.","why":"Correct. Edit an expense in one view and every view showing it sees the change, which is reference semantics."},{"text":"Structs can’t contain <code dir=\"ltr\">Decimal</code> or <code dir=\"ltr\">Date</code> properties.","why":"Structs hold those types all the time; the earlier lessons used them."}],"answer":1},{"q":"<p>A list uses <code dir=\"ltr\">@Query private var expenses: [Expense]</code>. How do you remove an expense the user swiped away?</p>","options":[{"text":"<code dir=\"ltr\">expenses.remove(at: index)</code>","why":"The query result is read-only; it reflects the store, it doesn’t control it."},{"text":"Set the expense’s properties to empty values.","why":"That saves an empty expense; it doesn’t delete anything."},{"text":"<code dir=\"ltr\">context.delete(expenses[index])</code>, using the model context from the environment.","why":"Correct. Changes go through the context, and the query updates itself to match."}],"answer":2},{"q":"<p>An edit screen binds a <code dir=\"ltr\">TextField</code> to <code dir=\"ltr\">$expense.title</code> through <code dir=\"ltr\">@Bindable var expense: Expense</code>. When is the change saved?</p>","options":[{"text":"Automatically; the context tracks the change and autosaves it.","why":"Correct. SwiftData’s main context autosaves, so editing the model is enough in most screens."},{"text":"Only after you call <code dir=\"ltr\">context.insert(expense)</code> again.","why":"The expense is already in the context; inserting again isn’t how updates work."},{"text":"Never, because bindings create a copy of the model.","why":"A model is a class, and the binding writes straight to the shared instance."}],"answer":0},{"q":"<p>Your SwiftUI preview of the expenses list should show sample data without touching the real database. What do you use?</p>","options":[{"text":"The app’s normal <code dir=\"ltr\">.modelContainer(for: Expense.self)</code>.","why":"That points at the same on-disk store the app uses; previews would read and write real data."},{"text":"<code dir=\"ltr\">.modelContainer(for: Expense.self, inMemory: true)</code>, then insert sample expenses.","why":"Correct. The data lives only in memory and disappears when the preview ends."},{"text":"A plain array instead of <code dir=\"ltr\">@Query</code>.","why":"That previews a different view from the one you ship, which defeats the purpose."}],"answer":1}],"exercise":{"kind":"fill","language":"swift","template":"@{{0}}\nfinal class Expense {\n    var title: String\n    var amount: Decimal\n    var date: Date\n\n    init(title: String, amount: Decimal, date: Date = .now) {\n        self.title = title\n        self.amount = amount\n        self.date = date\n    }\n}\n\nstruct ExpensesList: View {\n    @{{1}}(sort: \\Expense.date, order: .reverse) private var expenses: [Expense]\n    @Environment(\\.modelContext) private var context\n\n    var body: some View {\n        List {\n            ForEach(expenses) { expense in\n                Text(expense.title)\n            }\n            .onDelete { offsets in\n                for index in offsets {\n                    context.{{2}}(expenses[index])\n                }\n            }\n        }\n        .toolbar {\n            Button(\"Add coffee\") {\n                context.{{3}}(Expense(title: \"Coffee\", amount: 4.5))\n            }\n        }\n    }\n}","blanks":[{"answers":["Model"]},{"answers":["Query"]},{"answers":["delete"]},{"answers":["insert"]}],"title":"Persist expenses with SwiftData","prompt":"<p>Make <code dir=\"ltr\">Expense</code> a SwiftData model and finish the list: it must fetch expenses newest first, delete the ones the user swipes away, and add a coffee expense when the button is tapped. The app’s scene already has <code dir=\"ltr\">.modelContainer(for: Expense.self)</code>.</p>\n","hints":["The macro that makes a class persistent is named after what it describes.","The property wrapper that fetches models for a view is also the name of a database read.","All writes go through the model context: one method adds a new model, another removes an existing one."],"explanation":"<p><code dir=\"ltr\">@Model</code> turns the class into a persisted SwiftData model. <code dir=\"ltr\">@Query(sort: \\Expense.date, order: .reverse)</code> fetches every expense newest first and refreshes when the data changes. Writes go through the context from the environment: <code dir=\"ltr\">context.delete(_:)</code> removes a model and <code dir=\"ltr\">context.insert(_:)</code> adds one. The main context autosaves, and the query updates the list on its own, so there’s no array to keep in sync by hand.</p>\n","checkLabels":{}},"words":706,"readMinutes":3}