Skillshub axiom-swiftui-containers-ref
Reference — SwiftUI stacks, grids, outlines, and scroll enhancements through iOS 26
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/CharlesWiltgen/Axiom/axiom-swiftui-containers-ref" ~/.claude/skills/comeonoliver-skillshub-axiom-swiftui-containers-ref && rm -rf "$T"
skills/CharlesWiltgen/Axiom/axiom-swiftui-containers-ref/SKILL.mdSwiftUI Containers Reference
Stacks, grids, outlines, and scroll enhancements. iOS 14 through iOS 26.
Sources: WWDC 2020-10031, 2022-10056, 2023-10148, 2024-10144, 2025-256
Quick Decision
| Use Case | Container | iOS |
|---|---|---|
| Fixed views vertical/horizontal | VStack / HStack | 13+ |
| Overlapping views | ZStack | 13+ |
| Large scrollable list | LazyVStack / LazyHStack | 14+ |
| Multi-column grid | LazyVGrid | 14+ |
| Multi-row grid (horizontal) | LazyHGrid | 14+ |
| Static grid, precise alignment | Grid | 16+ |
| Hierarchical data (tree) | List with | 14+ |
| Custom hierarchies | OutlineGroup | 14+ |
| Show/hide content | DisclosureGroup | 14+ |
Part 1: Stacks
VStack, HStack, ZStack
VStack(alignment: .leading, spacing: 12) { Text("Title") Text("Subtitle") } HStack(alignment: .top, spacing: 8) { Image(systemName: "star") Text("Rating") } ZStack(alignment: .bottomTrailing) { Image("photo") Badge() }
ZStack alignments:
.center (default), .top, .bottom, .leading, .trailing, .topLeading, .topTrailing, .bottomLeading, .bottomTrailing
Spacer
HStack { Text("Left") Spacer() Text("Right") } Spacer(minLength: 20) // Minimum size
LazyVStack, LazyHStack (iOS 14+)
Render children only when visible. Use inside ScrollView.
ScrollView { LazyVStack(spacing: 0) { ForEach(items) { item in ItemRow(item: item) } } }
Pinned Section Headers
ScrollView { LazyVStack(pinnedViews: [.sectionHeaders]) { ForEach(sections) { section in Section(header: SectionHeader(section)) { ForEach(section.items) { item in ItemRow(item: item) } } } } }
Part 2: Grids
Grid (iOS 16+)
Non-lazy grid with precise alignment. Loads all views at once.
Grid(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10) { GridRow { Text("Name") TextField("Enter name", text: $name) } GridRow { Text("Email") TextField("Enter email", text: $email) } }
Modifiers:
— Span multiple columnsgridCellColumns(_:)
— Override column alignmentgridColumnAlignment(_:)
Grid { GridRow { Text("Header").gridCellColumns(2) } GridRow { Text("Left") Text("Right").gridColumnAlignment(.trailing) } }
LazyVGrid (iOS 14+)
Vertical-scrolling grid. Define columns; rows grow unbounded.
let columns = [ GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) ] ScrollView { LazyVGrid(columns: columns, spacing: 16) { ForEach(items) { item in ItemCard(item: item) } } }
LazyHGrid (iOS 14+)
Horizontal-scrolling grid. Define rows; columns grow unbounded.
let rows = [GridItem(.fixed(100)), GridItem(.fixed(100))] ScrollView(.horizontal) { LazyHGrid(rows: rows, spacing: 16) { ForEach(items) { item in ItemCard(item: item) } } }
GridItem.Size
| Size | Behavior |
|---|---|
| Exact width/height |
| Fills space equally |
| Creates as many as fit |
// Adaptive: responsive column count let columns = [GridItem(.adaptive(minimum: 150))]
Part 3: Outlines
List with Hierarchical Data (iOS 14+)
struct FileItem: Identifiable { let id = UUID() var name: String var children: [FileItem]? // nil = leaf } List(files, children: \.children) { file in Label(file.name, systemImage: file.children != nil ? "folder" : "doc") } .listStyle(.sidebar)
OutlineGroup (iOS 14+)
For custom hierarchical layouts outside List.
List { ForEach(canvases) { canvas in Section(header: Text(canvas.name)) { OutlineGroup(canvas.graphics, children: \.children) { graphic in GraphicRow(graphic: graphic) } } } }
DisclosureGroup (iOS 14+)
@State private var isExpanded = false DisclosureGroup("Advanced Options", isExpanded: $isExpanded) { Toggle("Enable Feature", isOn: $feature) Slider(value: $intensity) }
Part 4: Common Patterns
Photo Grid
let columns = [GridItem(.adaptive(minimum: 100), spacing: 2)] ScrollView { LazyVGrid(columns: columns, spacing: 2) { ForEach(photos) { photo in AsyncImage(url: photo.thumbnailURL) { image in image.resizable().aspectRatio(1, contentMode: .fill) } placeholder: { Color.gray } .aspectRatio(1, contentMode: .fill) .clipped() } } }
Horizontal Carousel
ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 16) { ForEach(items) { item in CarouselCard(item: item).frame(width: 280) } } .padding(.horizontal) }
File Browser
List(selection: $selection) { OutlineGroup(rootItems, children: \.children) { item in Label { Text(item.name) } icon: { Image(systemName: item.children != nil ? "folder.fill" : "doc.fill") } } } .listStyle(.sidebar)
Part 5: Performance
When to Use Lazy
| Size | Scrollable? | Use |
|---|---|---|
| 1-20 | No | VStack/HStack |
| 1-20 | Yes | VStack/HStack in ScrollView |
| 20-100 | Yes | LazyVStack/LazyHStack |
| 100+ | Yes | LazyVStack/LazyHStack or List |
| Grid <50 | No | Grid |
| Grid 50+ | Yes | LazyVGrid/LazyHGrid |
Cache GridItem arrays — define outside body:
struct ContentView: View { let columns = [GridItem(.adaptive(minimum: 150))] // ✅ var body: some View { LazyVGrid(columns: columns) { ... } } }
iOS 26 Performance
- 6x faster list loading for 100k+ items
- 16x faster list updates
- Reduced dropped frames in scrolling
- Nested ScrollViews with lazy stacks now properly defer loading:
ScrollView(.horizontal) { LazyHStack { ForEach(photoSets) { set in ScrollView(.vertical) { LazyVStack { ForEach(set.photos) { PhotoView(photo: $0) } } } } } }
Part 6: Scroll Enhancements
containerRelativeFrame (iOS 17+)
Size views relative to scroll container.
ScrollView(.horizontal) { LazyHStack { ForEach(cards) { card in CardView(card: card) .containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 16) } } }
scrollTargetLayout (iOS 17+)
Enable snapping.
ScrollView(.horizontal) { LazyHStack { ForEach(items) { ItemCard(item: $0) } } .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned)
scrollPosition (iOS 17+)
Track topmost visible item. Requires
on each item..id()
@State private var position: Item.ID? ScrollView { LazyVStack { ForEach(items) { item in ItemRow(item: item).id(item.id) } } } .scrollPosition(id: $position)
scrollTransition (iOS 17+)
.scrollTransition { content, phase in content .opacity(1 - abs(phase.value) * 0.5) .scaleEffect(phase.isIdentity ? 1.0 : 0.75) }
onScrollGeometryChange (iOS 18+)
.onScrollGeometryChange(for: Bool.self) { geo in geo.contentOffset.y < geo.contentInsets.top } action: { _, isTop in showBackButton = !isTop }
onScrollVisibilityChange (iOS 18+)
VideoPlayer(player: player) .onScrollVisibilityChange(threshold: 0.2) { visible in visible ? player.play() : player.pause() }
Resources
WWDC: 2020-10031, 2022-10056, 2023-10148, 2024-10144, 2025-256
Docs: /swiftui/lazyvstack, /swiftui/lazyvgrid, /swiftui/lazyhgrid, /swiftui/grid, /swiftui/outlinegroup, /swiftui/disclosuregroup
Skills: axiom-swiftui-layout, axiom-swiftui-layout-ref, axiom-swiftui-nav, axiom-swiftui-26-ref