Building a Flow Layout
Wrap items across lines dynamically.
Building a Flow Layout is a free Swift Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a Flow Layout?
A flow layout places items left to right, wrapping to a new row when the next item would overflow the available width—like tag chips or word wrapping.
The Plan
We will compute row breaks in a shared helper, return total height from sizeThatFits, and reuse the same computation in placeSubviews. Sharing logic keeps the two methods consistent.
Modeling a Placement
A small struct records where each subview goes. Computing all placements up front makes both protocol methods trivial.
struct Placement {
let index: Int
let point: CGPoint
let size: CGSize
}The Wrapping Algorithm
Walk the subviews, tracking the current x. If a child would exceed the max width, reset x and move down by the row height plus spacing.
func layout(_ subviews: Subviews, maxWidth: CGFloat) -> (placements: [Placement], height: CGFloat) {
var placements: [Placement] = []
var x: CGFloat = 0, y: CGFloat = 0, rowHeight: CGFloat = 0
let spacing: CGFloat = 8
for (i, s) in subviews.enumerated() {
let size = s.sizeThatFits(.unspecified)
if x + size.width > maxWidth, x > 0 {
x = 0; y += rowHeight + spacing; rowHeight = 0
}
placements.append(Placement(index: i, point: CGPoint(x: x, y: y), size: size))
x += size.width + spacing
rowHeight = max(rowHeight, size.height)
}
return (placements, y + rowHeight)
}Implementing sizeThatFits
sizeThatFits runs the algorithm against the proposed width and returns the proposal width by the computed total height.
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let maxWidth = proposal.width ?? .infinity
let result = layout(subviews, maxWidth: maxWidth)
return CGSize(width: maxWidth == .infinity ? result.placements.map { $0.point.x + $0.size.width }.max() ?? 0 : maxWidth,
height: result.height)
}Implementing placeSubviews
placeSubviews runs the same algorithm, then offsets each placement by bounds.origin and calls place.
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let result = layout(subviews, maxWidth: bounds.width)
for p in result.placements {
let pt = CGPoint(x: bounds.minX + p.point.x, y: bounds.minY + p.point.y)
subviews[p.index].place(at: pt, anchor: .topLeading,
proposal: ProposedViewSize(p.size))
}
}The Full FlowLayout Type
Combining the pieces gives a complete, reusable flow layout. The shared layout helper guarantees size and placement agree.
struct FlowLayout: Layout {
var spacing: CGFloat = 8
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let r = layout(subviews, maxWidth: proposal.width ?? .infinity)
return CGSize(width: proposal.width ?? 0, height: r.height)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let r = layout(subviews, maxWidth: bounds.width)
for p in r.placements {
subviews[p.index].place(at: CGPoint(x: bounds.minX + p.point.x, y: bounds.minY + p.point.y),
anchor: .topLeading, proposal: ProposedViewSize(p.size))
}
}
}Using the Flow Layout
Drop it in like any container. The tags wrap automatically as the width shrinks.
var body: some View {
FlowLayout {
ForEach(tags, id: \.self) { tag in
Text(tag)
.padding(8)
.background(.blue.opacity(0.2))
.clipShape(Capsule())
}
}
}Handling the First Item Per Row
The guard x > 0 prevents wrapping before the first item in a row. Without it, an item wider than the container would loop forever or push to an empty row needlessly.
if x + size.width > maxWidth, x > 0 {
// wrap only if the row already has content
}Spacing Choices
Here spacing is a stored property with a default. For production, read each subview’s spacing preference to honor system metrics instead of a hardcoded constant.
var spacing: CGFloat = 8 // configurable per instanceTesting Edge Cases
Test with zero items, one giant item, and many tiny items. A correct flow layout reports zero height for empty content and never clips a single oversized child.
Quick Check: Flow Layout
Test your understanding of the wrapping algorithm.
Recap: Building a Flow Layout
A flow layout wraps items by tracking the running x and resetting to a new row when the next child would overflow. A shared helper computes all placements and total height so sizeThatFits and placeSubviews stay consistent.
Offset placements by bounds.origin, guard the first item per row, and test empty/oversized/many-item cases. The result is a reusable wrapping container.
Frequently asked questions
Is the “Building a Flow Layout” lesson free?
Yes — the full text of “Building a Flow Layout” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.
What will I learn in “Building a Flow Layout”?
Wrap items across lines dynamically. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Swift Academy?
No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Building a Flow Layout” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Swift Academy lesson?
Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The Layout Protocol Basics
- Measuring Subviews
- Building a Flow Layout
- Layout Cache and Performance