Frames and Alignments in SwiftUI | SwiftUI Bootcamp #8
Understanding frames is what separates developers who fight with SwiftUI layouts from those who work with them. After this lesson you'll understand the parent-child size negotiation model and know how to use chained frames to build any layout precisely.
What You'll Learn
- How SwiftUI's layout model works: parent proposes, child decides, parent places
- The difference between fixed frames (
width: 150) and flexible frames (maxWidth: .infinity) - How stacking multiple
.frame()modifiers with differentalignmentvalues controls placement
Mental Model
Think of SwiftUI layout like a conversation between a parent and child. The parent says "you can have up to 300 points of width." The child says "I only need 50." The parent then places that 50-point child somewhere within its 300-point space, based on alignment. The child doesn't get to decide where it sits — only how big it is.
Each .frame() modifier creates a new invisible container that wraps the previous view. Stacking frames is like nesting boxes: the inner box decides its own size, and the outer box positions it. Adding a .background() after each .frame() in the sample makes this tangible — you can see each layer of the frame stack as a different color.
Detailed Explanation
In SwiftUI, layout flows in three steps: (1) a parent proposes a size to each child, (2) the child chooses its own size (ignoring the parent if needed), (3) the parent places the child within its bounds using alignment. Views can't force their children to be a specific size — they can only propose.
.frame(width:height:) with concrete values is an exact size request. The frame container becomes exactly that size, and the child is asked to fill it. If the child is smaller, the alignment parameter determines where the child sits inside the frame.
.frame(maxWidth:.infinity) expands the frame to take as much of the parent's offered space as possible. This is how you make something full-width. On its own a Text view only takes as much width as its content — .frame(maxWidth: .infinity) expands the invisible container around it to the full available width.
Chaining multiple .frame() modifiers in sequence (as in the sample) is an advanced but powerful technique. Each frame wraps the previous result, and each alignment parameter positions the previous container within the new container.
Code Structure
The sample is in FrameBootcamp.swift and uses a Text with seven chained .frame() and .background() calls. Each background color reveals the shape and position of one layer of the frame stack. This is the most educational way to see exactly how nested frames behave.
Complete Code
FrameBootcamp.swift
import SwiftUI
struct FrameBootcamp: View {
var body: some View {
Text("Hello, World!")
.background(Color.red) // layer 1: wraps exactly around the text
.frame(height: 100, alignment: .top) // layer 2: expands height to 100pt, text sits at top
.background(Color.orange) // reveals the 100pt-tall container
.frame(width: 150) // layer 3: constrains width to 150pt
.background(Color.purple) // reveals the 150pt-wide container
.frame(maxWidth: .infinity, alignment: .leading) // layer 4: expands to full screen width, content aligned left
.background(Color.pink) // reveals the full-width container
.frame(height: 400) // layer 5: constrains height to 400pt
.background(Color.green) // reveals the 400pt-tall container
.frame(maxHeight: .infinity, alignment: .top) // layer 6: expands to full screen height, previous content at top
.background(Color.yellow) // reveals the full-height container
}
}
struct FrameBootcamp_Previews: PreviewProvider {
static var previews: some View {
FrameBootcamp()
}
}Code Walkthrough
Text("Hello, World!")— At its natural size, aTextview is just wide enough for its string content and just tall enough for one line. No container, no alignment — it floats..background(Color.red)— Applies a background to the raw text. The red rectangle is exactly the same size as the text — this proves thatTexthas no inherent padding..frame(height: 100, alignment: .top)— Creates a new container that is 100pt tall (the text's natural width is kept). Thealignment: .topplaces the red-background text at the top of this 100pt container..background(Color.orange)— Reveals the 100pt-tall container. You can now see that the text sits at the top of a taller box..frame(width: 150)— Adds a fixed-width wrapper. Now the orange container is 150pt wide. Any content narrower than 150pt is centered by default (alignment defaults to.center)..frame(maxWidth: .infinity, alignment: .leading)— Expands the width to consume all space offered by the parent, aligning the 150pt purple container at the leading (left) edge..frame(height: 400)— Explicitly sets the height of the full-width layer to 400pt. Content continues to be vertically centered within it (since no vertical alignment was specified)..frame(maxHeight: .infinity, alignment: .top)— Expands to fill the entire remaining screen height, positioning the previous 400pt green block at the top.
Common Mistakes
Mistake: Expecting .frame(width: 200) to make a Text view exactly 200pt wide.frame() creates a container of that size, but the Text inside still chooses its own size. The container is 200pt but the text may be narrower. Add a .background() after the text (before the frame) and after the frame to see the actual sizes of both layers.
Mistake: Using .frame(maxWidth: .infinity) and being confused when nothing expands This works only when the parent offers flexible space. Inside a VStack or ScrollView, the parent offers full width and .maxWidth: .infinity expands correctly. Inside a HStack, the parent splits space among children so the result depends on sibling sizes. If nothing seems to expand, check what container the view lives in.
Mistake: Confusing the alignment on .frame() with the alignment on stacks The alignment parameter on .frame() positions the child view inside that frame container. The alignment parameter on VStack, HStack, etc. positions all children along the cross-axis. These are two different systems operating at different levels.
Key Takeaways
- Each
.frame()creates a new invisible container that wraps the previous view — stacking frames is like nesting boxes .frame(maxWidth: .infinity)expands to fill available parent space;.frame(width: N)sets a fixed size- Use
.background()after each frame layer to visually debug exactly what each frame is doing
Last updated: June 27, 2026