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
A greedy shape and a modest Text, given the same proposal.
Run to see the preview.

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.

Spacers absorb the leftover space. Try moving or deleting them.
Run to see the preview.

.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.

maxWidth: .infinity turns a Text into a full-width view.
Run to see the preview.

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) { … }
Change .leading to .center or .trailing and re-run.
Run to see the preview.

ZStack layers

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

SwiftUI preview
Run to see the preview.
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?

Build a header bar

Build the view

Build a row with a title on the left, a Spacer, and a caption on the right — full width, padded, on a grey background.

Solution unlocks after 3 attempts

Layer a badge

Build the view

Use a ZStack to place a small orange circle at the top-trailing corner of a blue rounded rectangle.

Solution unlocks after 3 attempts
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.