Views and ViewBuilders
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.
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.
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 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.