Layout: Stacks, Lists & ScrollViews
iOS Development Fundamentals
Chapter 7 · Layout: Stacks, Lists & ScrollViews
Every real screen needs to arrange more than one view. This chapter covers the three stack containers,
then SwiftUI's real, purpose-built tools for showing many items at once — List and
ScrollView.
The Three Stacks
Arranges children vertically, top to bottom.
Arranges children horizontally, left to right.
Layers children on top of one another, back to front.
alignment and spacing — are optional, with
sensible system defaults if omitted. Spacer() is a real, invisible view that expands to fill
all available space along its stack's own axis, pushing the views on either side of it apart — exactly how
"Left" and "Right" land at opposite edges above.
List and ForEach
A real List is SwiftUI's own dedicated, scrollable, row-based view — the same visual pattern
behind Settings, Mail, and most other built-in Apple apps. For dynamic data, it's paired with
ForEach:
ForEach needs a real way to tell each element apart across data updates — either the element's
own type conforms to Identifiable (a real protocol requiring one stable id
property, as with Task above), or a specific key path is provided explicitly:
ForEach(tasks, id: \.title). Skipping both is a real compile error, not a warning — SwiftUI
genuinely can't animate or diff a list it can't tell individual rows apart within.
List is lazy by real, built-in default — rows are created only as they're about to scroll into
view, not all at once up front. A list of thousands of rows performs well without any extra work.
ScrollView for Everything Else
List's own row-based, one-item-per-line design doesn't fit every layout — a horizontal photo
carousel, or a custom card grid, needs a genuinely different container: ScrollView.
| Container | Real Laziness | Use It For |
|---|---|---|
List | Lazy by built-in default | Standard, scrollable, row-based data — the common real case |
VStack/HStack | Not lazy — builds every child immediately | A small, fixed, known number of views |
ScrollView + LazyVStack/LazyHStack | Lazy, explicitly opted into | Custom scrollable layouts List doesn't fit — grids, carousels, non-row designs |
List's automatic laziness is conceptually close to what "virtualization" libraries add on top
of a plain React list — only rendering what's actually visible. The real difference: it's a genuine,
built-in SwiftUI default, not a separate library to reach for.
Hands-On Exercises
Build a struct Contact: Identifiable with let id = UUID() and let name: String, an array of five sample contacts, and a List/ForEach combination displaying each contact's name.
Build a horizontal ScrollView containing a LazyHStack of ten numbered, colored rectangle views. Explain, in your own words, why LazyHStack is the better real choice here over a plain HStack.
Explain, in your own words, why ForEach(tasks) compiles fine when Task conforms to Identifiable, but produces a real compile error for a plain struct with no id property and no explicit id: argument.
Chapter 7 Quick Reference
VStack/HStack/ZStack— vertical, horizontal, layered arrangement;Spacer()fills available space along the stack's axisList+ForEach— the standard scrollable, row-based container, lazy by real built-in defaultForEachrequiresIdentifiableconformance or an explicitid:key pathScrollView+LazyVStack/LazyHStack— for custom scrollable layoutsList's row design doesn't fit- Plain
VStack/HStackbuild every child immediately — fine for a small, fixed number of views, not for large or dynamic collections