EduFlow

Section 3 · Building Screens with SwiftUI

Views and Modifiers

17 min3 min readChoose wisely

Lesson at a glance

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.

  1. 1A view is a struct with a body
  2. 2Stacks and spacers
  3. 3Modifiers wrap views
  4. 4Logic in a body
  5. 5Previews
  6. 6Small views, composed

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

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.

A view is a struct with a body

Here is the row Pocket Budget shows for each expense, using the Expense and Category types from the previous section:

ExpenseRow.swift
import SwiftUI

struct ExpenseRow: View {
    let expense: Expense

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: expense.category.symbolName)
                .font(.title3)
                .frame(width: 32)
                .foregroundStyle(.tint)

            VStack(alignment: .leading, spacing: 2) {
                Text(expense.title)
                    .font(.headline)
                Text(expense.date, format: .dateTime.day().month())
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            Text(expense.amount, format: .currency(code: "USD"))
                .font(.body.monospacedDigit())
        }
        .padding(.vertical, 4)
    }
}

ExpenseRow conforms to the View protocol, whose one requirement is body. 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.

some View is the opaque type from the protocols lesson. The real type of that body is a deeply nested generic built from every stack and modifier, and some View lets the compiler track it without you writing it out.

Stacks and spacers

Three containers handle most layouts. HStack places children left to right (right to left in Arabic and Hebrew, automatically), VStack top to bottom, and ZStack layers them front to back. alignment and spacing control how children line up. Spacer() expands to fill free space, which is how the amount ends up pinned to the trailing edge.

Text can format values for you: Text(expense.amount, format: .currency(code: "USD")) and Text(expense.date, format: .dateTime.day().month()) follow the user’s locale, so a user in Cairo or Berlin sees separators and date order they expect. .monospacedDigit() keeps columns of amounts aligned.

Modifiers wrap views

.font(.headline) 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:

Swift
Text("Food")
    .padding()
    .background(.blue)      // blue covers the text AND the padding

Text("Food")
    .background(.blue)      // blue covers only the text
    .padding()              // clear space outside the blue
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. .padding().background(.blue) Food blue includes the padding .background(.blue).padding() Food padding is outside the blue
Each modifier wraps the view before it

Read a modifier chain from top to bottom as “take this, then wrap it in that”. Some modifiers, such as .font and .foregroundStyle, flow down to every child, which is why setting .font(.title3) on a VStack styles all the text inside it.

Logic in a body

Because body is a description, plain Swift decides what’s in it. An if includes a view only when a condition holds, and a switch picks between views:

Swift
HStack {
    Text(expense.title)
    if expense.amount > 100 {
        Image(systemName: "exclamationmark.circle")
            .foregroundStyle(.orange)
            .accessibilityLabel("Large expense")
    }
}

There’s no “show” or “hide” call anywhere. When the amount changes, SwiftUI reads body 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.

Previews

Add a preview to the bottom of the file:

Swift
#Preview {
    ExpenseRow(expense: Expense(title: "Groceries", amount: 64.2, category: .food, date: .now))
        .padding()
}

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.

Small views, composed

When a body passes about a screenful, extract pieces into their own structs:

Swift
struct CategoryBadge: View {
    let category: Category

    var body: some View {
        Text(category.title)
            .font(.caption.bold())
            .padding(.horizontal, 10)
            .padding(.vertical, 4)
            .background(.blue.opacity(0.15), in: Capsule())
            .foregroundStyle(.blue)
    }
}

Now CategoryBadge(category: expense.category) 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.

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.

Key takeaways

  • A SwiftUI view is a lightweight struct whose body describes what the screen should look like for the current data.
  • VStack, HStack and ZStack arrange children vertically, horizontally and in layers; Spacer pushes content apart.
  • Each modifier wraps the view in a new one, so order matters; padding before background paints behind the padding.
  • Extract a subview as soon as a body gets hard to read; small views cost nothing at runtime.

Further reading

Check your understanding

3 questions

Question 1 of 3

What does Text("Food").background(.blue).padding() look like?

Question 2 of 3

Why is body declared as some View instead of a specific type?

Question 3 of 3

A row’s body has grown to 70 lines. What does an experienced SwiftUI developer do?

Practice

Choose wisely

Build the category pill

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?

Build the category pill