{"id":"l-sw-3-1","courseId":"swift-essentials","lang":"en","kind":"lesson","summary":"Build SwiftUI views as small structs, lay them out with stacks, style them with modifiers in the right order, and check them instantly with previews.","html":"<p>If you’ve used UIKit, or manipulated the DOM by hand, you’re used to telling the screen what to change: set this label’s text, hide that button, insert a row. SwiftUI flips that around. You describe what the screen should look like for the current data, and SwiftUI works out what changed and updates the pixels. Your job shrinks to writing that description well.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">A view is a struct with a body</h2>\n<p>Here is the row Pocket Budget shows for each expense, using the <code dir=\"ltr\">Expense</code> and <code dir=\"ltr\">Category</code> types from the previous section:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">ExpenseRow.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\"> ExpenseRow</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\">        HStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">spacing</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            Image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemName</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: expense.category.symbolName)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.title3)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">frame</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">width</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">32</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\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.tint)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            VStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">alignment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .leading, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">spacing</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.headline)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Text</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.subheadline)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.secondary)</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:#0550AE;--shiki-dark:#79C0FF\">            Spacer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            Text</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:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.body.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">monospacedDigit</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\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.vertical, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</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><code dir=\"ltr\">ExpenseRow</code> conforms to the <code dir=\"ltr\">View</code> protocol, whose one requirement is <code dir=\"ltr\">body</code>. It’s a struct, so it’s cheap to create, and SwiftUI creates and discards them constantly. Don’t think of a view struct as the thing on screen; think of it as a recipe that SwiftUI reads whenever the data changes.</p>\n<p><code dir=\"ltr\">some View</code> is the opaque type from the protocols lesson. The real type of that <code dir=\"ltr\">body</code> is a deeply nested generic built from every stack and modifier, and <code dir=\"ltr\">some View</code> lets the compiler track it without you writing it out.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Stacks and spacers</h2>\n<p>Three containers handle most layouts. <code dir=\"ltr\">HStack</code> places children left to right (right to left in Arabic and Hebrew, automatically), <code dir=\"ltr\">VStack</code> top to bottom, and <code dir=\"ltr\">ZStack</code> layers them front to back. <code dir=\"ltr\">alignment</code> and <code dir=\"ltr\">spacing</code> control how children line up. <code dir=\"ltr\">Spacer()</code> expands to fill free space, which is how the amount ends up pinned to the trailing edge.</p>\n<p><code dir=\"ltr\">Text</code> can format values for you: <code dir=\"ltr\">Text(expense.amount, format: .currency(code: &quot;USD&quot;))</code> and <code dir=\"ltr\">Text(expense.date, format: .dateTime.day().month())</code> follow the user’s locale, so a user in Cairo or Berlin sees separators and date order they expect. <code dir=\"ltr\">.monospacedDigit()</code> keeps columns of amounts aligned.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Modifiers wrap views</h2>\n<p><code dir=\"ltr\">.font(.headline)</code> doesn’t change the text in place. It returns a new view that wraps the text and applies a font. Chain three modifiers and you’ve built three layers. That’s why order matters:</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:#0550AE;--shiki-dark:#79C0FF\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Food\"</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\">padding</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\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue)      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// blue covers the text AND the padding</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Food\"</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\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue)      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// blue covers only the text</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()              </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// clear space outside the blue</span></span></code></pre></div>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 220\" role=\"img\" aria-labelledby=\"l-sw-3-1-t1\"><defs><marker id=\"arrow-l-sw-3-1-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-1-t1\">Left: padding first, then background, so the blue layer encloses both the text and its padding. Right: background first, then padding, so the blue layer hugs the text and the padding is outside it.</title>\n  <text class=\"d-label-strong\" x=\"170\" y=\"24\" text-anchor=\"middle\">.padding().background(.blue)</text>\n  <rect class=\"d-box-accent\" x=\"70\" y=\"45\" width=\"200\" height=\"120\" rx=\"8\"/>\n  <rect class=\"d-box\" x=\"120\" y=\"85\" width=\"100\" height=\"40\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"170\" y=\"110\" text-anchor=\"middle\">Food</text>\n  <text class=\"d-label-muted\" x=\"170\" y=\"190\" text-anchor=\"middle\">blue includes the padding</text>\n  <text class=\"d-label-strong\" x=\"510\" y=\"24\" text-anchor=\"middle\">.background(.blue).padding()</text>\n  <rect class=\"d-box d-dashed\" x=\"410\" y=\"45\" width=\"200\" height=\"120\" rx=\"8\"/>\n  <rect class=\"d-box-accent\" x=\"460\" y=\"85\" width=\"100\" height=\"40\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"510\" y=\"110\" text-anchor=\"middle\">Food</text>\n  <text class=\"d-label-muted\" x=\"510\" y=\"190\" text-anchor=\"middle\">padding is outside the blue</text>\n</svg><figcaption>Each modifier wraps the view before it</figcaption></figure>\n<p>Read a modifier chain from top to bottom as “take this, then wrap it in that”. Some modifiers, such as <code dir=\"ltr\">.font</code> and <code dir=\"ltr\">.foregroundStyle</code>, flow down to every child, which is why setting <code dir=\"ltr\">.font(.title3)</code> on a <code dir=\"ltr\">VStack</code> styles all the text inside it.</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>Fighting modifier order</span></p>\n<p>When a background, border or tap area is the wrong size, the cause is almost always order. A frequent example is <code dir=\"ltr\">.onTapGesture</code> before <code dir=\"ltr\">.padding()</code>: the padding wraps the tappable view from outside, so taps on it do nothing. Move the gesture after the padding (adding <code dir=\"ltr\">.contentShape(Rectangle())</code> if the area is empty space) rather than stacking another frame to compensate.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Logic in a body</h2>\n<p>Because <code dir=\"ltr\">body</code> is a description, plain Swift decides what’s in it. An <code dir=\"ltr\">if</code> includes a view only when a condition holds, and a <code dir=\"ltr\">switch</code> picks between views:</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:#0550AE;--shiki-dark:#79C0FF\">HStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</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:#CF222E;--shiki-dark:#FF7B72\">    if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        Image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemName</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"exclamationmark.circle\"</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\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.orange)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">accessibilityLabel</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Large expense\"</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>There’s no “show” or “hide” call anywhere. When the amount changes, SwiftUI reads <code dir=\"ltr\">body</code> again and the icon appears or disappears on its own. Keep a body free of side effects, such as network calls or printing, because SwiftUI may read it many times and at moments you don’t control.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Previews</h2>\n<p>Add a preview to the bottom of the file:</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:#0550AE;--shiki-dark:#79C0FF\">#Preview</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</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\">: </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\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Groceries\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">amount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">64.2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .food, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .now))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><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>Xcode renders it in the canvas next to your code and updates as you type. Previews are the fastest feedback loop in iOS development. Add one for each interesting state, such as a long title, a large amount or right-to-left layout, and you catch layout bugs before you ever launch the simulator.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">Small views, composed</h2>\n<p>When a <code dir=\"ltr\">body</code> passes about a screenful, extract pieces into their own structs:</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\"> CategoryBadge</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\"> category: Category</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\">        Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category.title)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.caption.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">bold</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\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.horizontal, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</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\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.vertical, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</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\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">opacity</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.15</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\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Capsule</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\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue)</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>Now <code dir=\"ltr\">CategoryBadge(category: expense.category)</code> can appear in the row, the detail screen and a filter bar, styled identically. Extraction is free at runtime and gives SwiftUI smaller units to compare when data changes.</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>Semantic styles first</span></p>\n<p>Prefer <code dir=\"ltr\">.headline</code>, <code dir=\"ltr\">.secondary</code> and <code dir=\"ltr\">.tint</code> over fixed point sizes and hex colours. They adapt to Dynamic Type, dark mode and accessibility settings without extra work.</p>\n</aside>\n<p>Everything in this lesson shows fixed data. Real screens change: the user types, toggles and adds expenses. The next lesson introduces state, the data that drives those changes, and how SwiftUI knows when to redraw.</p>\n","toc":[{"id":"sec-1","text":"A view is a struct with a body","level":2},{"id":"sec-2","text":"Stacks and spacers","level":2},{"id":"sec-3","text":"Modifiers wrap views","level":2},{"id":"sec-4","text":"Logic in a body","level":2},{"id":"sec-5","text":"Previews","level":2},{"id":"sec-6","text":"Small views, composed","level":2}],"takeaways":["A SwiftUI view is a lightweight struct whose <code dir=\"ltr\">body</code> describes what the screen should look like for the current data.","VStack, HStack and ZStack arrange children vertically, horizontally and in layers; <code dir=\"ltr\">Spacer</code> pushes content apart.","Each modifier wraps the view in a new one, so order matters; <code dir=\"ltr\">padding</code> before <code dir=\"ltr\">background</code> paints behind the padding.","Extract a subview as soon as a <code dir=\"ltr\">body</code> gets hard to read; small views cost nothing at runtime."],"further":[{"title":"Configuring views","url":"https://developer.apple.com/documentation/swiftui/configuring-views"},{"title":"SwiftUI Tutorials","url":"https://developer.apple.com/tutorials/swiftui"}],"quiz":[{"q":"<p>What does <code dir=\"ltr\">Text(&quot;Food&quot;).background(.blue).padding()</code> look like?</p>","options":[{"text":"Blue fills the text and the padding around it.","why":"That would be <code dir=\"ltr\">.padding().background(.blue)</code>. Here the background is applied before the padding exists."},{"text":"Blue fills only the text’s own frame, with clear space around it.","why":"Correct. <code dir=\"ltr\">background</code> wraps the text, then <code dir=\"ltr\">padding</code> adds empty space outside that coloured view."},{"text":"SwiftUI reorders modifiers, so it looks the same either way.","why":"SwiftUI applies modifiers in exactly the order you write them; each one wraps the previous result."}],"answer":1},{"q":"<p>Why is <code dir=\"ltr\">body</code> declared as <code dir=\"ltr\">some View</code> instead of a specific type?</p>","options":[{"text":"Because a view can change type at runtime.","why":"The type is fixed at compile time. <code dir=\"ltr\">some</code> hides a name, it doesn’t make the type dynamic."},{"text":"Because the real type is a long nested generic that you’d never want to write by hand.","why":"Correct. The compiler knows the exact type; <code dir=\"ltr\">some View</code> saves you from spelling it."},{"text":"Because <code dir=\"ltr\">View</code> is a class and <code dir=\"ltr\">some</code> makes it a struct.","why":"<code dir=\"ltr\">View</code> is a protocol, and your views are structs either way."}],"answer":1},{"q":"<p>A row’s <code dir=\"ltr\">body</code> has grown to 70 lines. What does an experienced SwiftUI developer do?</p>","options":[{"text":"Leave it; splitting views costs performance.","why":"Views are cheap value types. Splitting them has no meaningful runtime cost and often helps SwiftUI update less."},{"text":"Move parts into computed properties that return <code dir=\"ltr\">AnyView</code>.","why":"<code dir=\"ltr\">AnyView</code> erases type information that SwiftUI uses to diff efficiently. Use a subview struct instead."},{"text":"Extract meaningful pieces into their own small <code dir=\"ltr\">View</code> structs.","why":"Correct. Named subviews read like an outline of the screen and can be previewed on their own."}],"answer":2}],"exercise":{"kind":"choice","options":["a","b","c","d"],"correct":["b"],"title":"Build the category pill","prompt":"<p>Pocket Budget’s design calls for a category label shaped like a pill: blue capsule background, with the text inset from the capsule’s edges by some breathing room. Which modifier chain produces it?</p>\n","hints":["The blue must cover the space around the text, so the space has to exist before the background is applied.","<code dir=\"ltr\">clipShape</code> cuts a view to a shape. What does it cut if the background hasn’t been added yet?","<code dir=\"ltr\">background(_:in:)</code> paints a style inside a shape in one step."],"explanation":"<p>Modifiers apply top to bottom, each wrapping the result so far. In the correct chain the two <code dir=\"ltr\">padding</code> calls create the inset first, and <code dir=\"ltr\">background(.blue, in: Capsule())</code> then paints a capsule behind the text and its padding.</p>\n<p>The other chains apply the background too early (so the padding is outside the blue) or clip before there’s any background to clip. When a SwiftUI shape is the wrong size, check the modifier order before anything else.</p>\n","checkLabels":{},"optionText":{"a":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).background(.blue).padding().clipShape(Capsule())</code>","why":"The blue is applied before the padding, so it hugs the text; the clip then rounds a mostly transparent area."},"b":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).padding(.horizontal, 12).padding(.vertical, 6).background(.blue, in: Capsule())</code>","why":"Correct. The padding exists first, then the capsule is painted behind the text plus its padding."},"c":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).clipShape(Capsule()).padding().background(.blue)</code>","why":"This clips the bare text, then paints a blue rectangle around the padding. You get a rectangle, not a pill."},"d":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).background(.blue).clipShape(Capsule()).padding()</code>","why":"This makes a tiny pill that hugs the letters with no inset, then adds clear space outside it."}}},"words":691,"readMinutes":3}