Building a Flow Layout
Wrap items across lines dynamically.
Building a Flow Layout is a free Swift Academy lesson on CoddyKit. This is lesson 3 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, and your progress syncs across the web and the CoddyKit app. The Swift Academy course includes 4 lessons in total.
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.
Learn Swift with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 122
- Lessons
- 409
Frequently Asked Questions
Is the “Building a Flow Layout” lesson free?
Yes — any 3 lessons of the Swift Academy learning path, including “Building a Flow Layout”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Swift Academy course includes 4 lessons in total.
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, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.
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