Views and ViewBuildersswift-6.4/ios-26
Lesson 1 / 8
Unit 7 · SwiftUI foundations

Views and ViewBuilders

Coming from
Not covered in this lesson — showing TypeScript.
Note

Unit 7 · SwiftUI foundations. This is the first lesson of the UI layer. Everything about state, layout, and navigation in the rest of the unit builds on the view model established here.

A SwiftUI view is a value-type struct that describes what the screen should look like for the current state. It is not a long-lived object you mutate; it is a lightweight description that SwiftUI creates, reads, and throws away — many times per second if needed. Internalize that one fact and the rest of SwiftUI stops being surprising.

The View protocol

Conform to View and provide a body:

struct GreetingView: View {
    let name: String

    var body: some View {
        Text("Hello, \(name)")
    }
}

body returns some View — an opaque type meaning "a specific concrete view type I'm not going to spell out." The compiler infers it. You never write the real type (it can be a deeply nested generic like Text wrapped in modifiers); some View is the contract that hides it.

Composition over configuration

You build screens by nesting small views, not by configuring one big object. Each piece is its own struct, and larger views embed smaller ones:

struct ProfileHeader: View {
    let name: String
    let role: String

    var body: some View {
        VStack(alignment: .leading) {
            Text(name).font(.title2)
            Text(role).foregroundStyle(.secondary)
        }
    }
}

struct ProfileScreen: View {
    var body: some View {
        VStack(spacing: 16) {
            ProfileHeader(name: "Ada", role: "Engineer")
            Divider()
            Text("Bio goes here.")
        }
        .padding()
    }
}

When a body grows past a screenful, that is the signal to extract a subview — not to add comments. Small views compose, re-render independently, and read like a table of contents.

@ViewBuilder: how body can list several views

body seems to return one view, yet you write several lines. That works because body (and container initializers like VStack { }) are marked @ViewBuilder — a result builder that collects the views you list and combines them into one composite view.

@ViewBuilder
func statusRow(isOnline: Bool) -> some View {
    if isOnline {
        Label("Online", systemImage: "circle.fill")
    } else {
        Label("Offline", systemImage: "circle")
    }
}

Because of the builder, you can use if, if let, and switch directly inside a view's body — the builder turns each branch into the right view. That is why SwiftUI layouts read like declarative markup rather than imperative append-calls.

Tip

Views are cheap; treat them as disposable. Constructing a view struct allocates almost nothing — it is just a description. SwiftUI re-runs body whenever the state it read changes, diffs the result against what is on screen, and updates only what differs. So never do expensive work in body (no network calls, no heavy computation); keep it a fast, pure function of state.

Coming from TypeScript

If you have written React, body is your render function and a View struct is a function component: a pure description of UI for the current props/state, re-invoked on change, with a virtual-DOM-style diff applied for you. @ViewBuilder is JSX's ability to return a fragment of several elements. The mental model transfers almost one-to-one.

Coming from older Swift

Coming from UIKit: a UIView/UIViewController is a mutable object you hold and imperatively mutate (label.text = …). A SwiftUI View is the opposite — a value you rebuild from state, never a thing you reach into and poke. Stop thinking "get the label and set it"; think "describe what the label says for this state."

Knowledge check

Q: Why is it safe for SwiftUI to re-create view structs constantly? Because a view is a tiny value-type description, not a heavyweight object. Creating one is nearly free; SwiftUI diffs the descriptions and only touches the real UI where it changed.

Q: What lets you write an if/else directly inside a view's body? body is a @ViewBuilder. The result builder collects each branch and combines the listed views into one composite view, so control flow produces views instead of needing manual assembly.