Views and layout · core

The layout system

SwiftUI layout is a three-step negotiation between parent and child — once you know the three steps, layout stops being guesswork.

12 min read15 min practice0/2 exercises4 recall cards

By the end you will be able to

  • State the three steps of SwiftUI's layout negotiation
  • Predict how a stack divides space between its children
  • Use frame, Spacer, padding and alignment deliberately rather than by trial and error
Guess firstAnswering before you read makes the explanation stick — even when you get it wrong.

Text("Hi").frame(width: 200, height: 100) — what is the size of the Text itself?

The three steps

Every layout in SwiftUI is the same negotiation, repeated down the tree:

  1. The parent proposes a size to the child.
  2. The child chooses its own size — it may accept the proposal, take less, or take more.
  3. The parent places the child within its own bounds.

The crucial part is step 2: the child decides. A parent proposes; it cannot compel. That is the reverse of Auto Layout, where constraints are solved globally, and it is why "why is this view not filling the space?" is nearly always answered by "because the child chose not to".

How different views respond to a proposal

ViewGiven a large proposal, it takes
Textexactly what it needs for its content
Imageits natural size, unless .resizable()
Color, Rectangle, shapesall of it — shapes are greedy
Spacerall of it along the stack's axis
VStack / HStackonly what its children need
.frame(width:height:)exactly what you specified
Loading runnable Swift…

The Text has a background only as wide as its letters. The Rectangle fills every available point of width, because shapes accept whatever they are offered.

Stacks divide space

A VStack proposes to its children in order of flexibility: the least flexible first. Inflexible children (like Text) take what they need; whatever is left is divided among the flexible ones.

This is why a Spacer pushes things apart: it is maximally flexible, so it absorbs all remaining space along the axis.

Loading runnable Swift…

.frame makes a container

Because .frame creates a parent rather than resizing the child, these two are different:

Text("Hi").frame(width: 200).background(.blue)   // blue fills 200pt
Text("Hi").background(.blue).frame(width: 200)   // blue hugs the text

In the first, the background is applied to the 200-point frame. In the second, it is applied to the text, and the frame wraps the already-backgrounded text. Modifier order is layout order — the next lesson is entirely about this.

.frame(maxWidth: .infinity) is the one you will reach for constantly: it means "take as much width as offered", turning a modest view into a greedy one.

Loading runnable Swift…

Alignment

Stacks take an alignment for the axis they do not control:

VStack(alignment: .leading) { … }    // controls horizontal placement
HStack(alignment: .top) { … }        // controls vertical placement
ZStack(alignment: .bottomTrailing) { … }
Loading runnable Swift…

ZStack layers

ZStack draws its children back to front — first child at the back:

Loading runnable Swift…
Predict, then runA wrong prediction you have committed to is worth more than a right answer you read.

Two views, identical except for where .padding() sits. Look at the preview and decide which rectangle is larger before you run it.

#Preview {
    VStack(spacing: 12) {
        Text("Padding inside")
            .padding()
            .background(.blue)

        Text("Padding outside")
            .background(.blue)
            .padding()
    }
}
Check yourself

Your VStack sits as a small block in the middle of the screen instead of filling it. What is the most likely fix?

Loading exercise…
Loading exercise…
PredictSaved on this device. Never graded.

Predict what Text("Hi").frame(width: 50).frame(width: 200).background(.blue) looks like — two frames stacked. Write your prediction, then build it in the playground and check.

Checkpoint

You can now:

  • State the three-step layout negotiation and say who decides the size
  • Predict how a stack divides space and why Spacer behaves as it does
  • Use .frame, maxWidth: .infinity and alignment on purpose
  • Layer views with ZStack

Next up: modifier order — why the same modifiers in a different sequence produce a different view.

How well do you know this now? Rating yourself honestly, then being tested on it, is how you find out where your intuition is wrong.