Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"source": "CharlesWiltgen/Axiom",
|
||||
"sourceType": "git",
|
||||
"repoUrl": "https://github.com/CharlesWiltgen/Axiom",
|
||||
"subpath": "axiom-codex/skills/axiom-swiftui-containers-ref",
|
||||
"installedAt": "2026-04-12T08:06:47.496Z"
|
||||
}
|
||||
416
.claude/skills/axiom-swiftui-containers-ref/SKILL.md
Normal file
416
.claude/skills/axiom-swiftui-containers-ref/SKILL.md
Normal file
@@ -0,0 +1,416 @@
|
||||
---
|
||||
name: axiom-swiftui-containers-ref
|
||||
description: Reference — SwiftUI stacks, grids, outlines, and scroll enhancements through iOS 26
|
||||
license: MIT
|
||||
metadata:
|
||||
version: "1.2.0"
|
||||
---
|
||||
|
||||
# SwiftUI 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 `children:` | 14+ |
|
||||
| Custom hierarchies | OutlineGroup | 14+ |
|
||||
| Show/hide content | DisclosureGroup | 14+ |
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Stacks
|
||||
|
||||
### VStack, HStack, ZStack
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
HStack {
|
||||
Text("Left")
|
||||
Spacer()
|
||||
Text("Right")
|
||||
}
|
||||
|
||||
Spacer(minLength: 20) // Minimum size
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### LazyVStack, LazyHStack (iOS 14+)
|
||||
|
||||
Render children only when visible. Use inside ScrollView.
|
||||
|
||||
```swift
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(items) { item in
|
||||
ItemRow(item: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pinned Section Headers
|
||||
|
||||
```swift
|
||||
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.
|
||||
|
||||
```swift
|
||||
Grid(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10) {
|
||||
GridRow {
|
||||
Text("Name")
|
||||
TextField("Enter name", text: $name)
|
||||
}
|
||||
GridRow {
|
||||
Text("Email")
|
||||
TextField("Enter email", text: $email)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Modifiers**:
|
||||
- `gridCellColumns(_:)` — Span multiple columns
|
||||
- `gridColumnAlignment(_:)` — Override column alignment
|
||||
|
||||
```swift
|
||||
Grid {
|
||||
GridRow {
|
||||
Text("Header").gridCellColumns(2)
|
||||
}
|
||||
GridRow {
|
||||
Text("Left")
|
||||
Text("Right").gridColumnAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### LazyVGrid (iOS 14+)
|
||||
|
||||
Vertical-scrolling grid. Define **columns**; rows grow unbounded.
|
||||
|
||||
```swift
|
||||
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.
|
||||
|
||||
```swift
|
||||
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 |
|
||||
|------|----------|
|
||||
| `.fixed(CGFloat)` | Exact width/height |
|
||||
| `.flexible(minimum:maximum:)` | Fills space equally |
|
||||
| `.adaptive(minimum:maximum:)` | Creates as many as fit |
|
||||
|
||||
```swift
|
||||
// Adaptive: responsive column count
|
||||
let columns = [GridItem(.adaptive(minimum: 150))]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Outlines
|
||||
|
||||
### List with Hierarchical Data (iOS 14+)
|
||||
|
||||
```swift
|
||||
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.
|
||||
|
||||
```swift
|
||||
List {
|
||||
ForEach(canvases) { canvas in
|
||||
Section(header: Text(canvas.name)) {
|
||||
OutlineGroup(canvas.graphics, children: \.children) { graphic in
|
||||
GraphicRow(graphic: graphic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DisclosureGroup (iOS 14+)
|
||||
|
||||
```swift
|
||||
@State private var isExpanded = false
|
||||
|
||||
DisclosureGroup("Advanced Options", isExpanded: $isExpanded) {
|
||||
Toggle("Enable Feature", isOn: $feature)
|
||||
Slider(value: $intensity)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 4: Common Patterns
|
||||
|
||||
### Photo Grid
|
||||
|
||||
```swift
|
||||
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
|
||||
|
||||
```swift
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
LazyHStack(spacing: 16) {
|
||||
ForEach(items) { item in
|
||||
CarouselCard(item: item).frame(width: 280)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
```
|
||||
|
||||
### File Browser
|
||||
|
||||
```swift
|
||||
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:
|
||||
|
||||
```swift
|
||||
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:
|
||||
|
||||
```swift
|
||||
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.
|
||||
|
||||
```swift
|
||||
ScrollView(.horizontal) {
|
||||
LazyHStack {
|
||||
ForEach(cards) { card in
|
||||
CardView(card: card)
|
||||
.containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### scrollTargetLayout (iOS 17+)
|
||||
|
||||
Enable snapping.
|
||||
|
||||
```swift
|
||||
ScrollView(.horizontal) {
|
||||
LazyHStack {
|
||||
ForEach(items) { ItemCard(item: $0) }
|
||||
}
|
||||
.scrollTargetLayout()
|
||||
}
|
||||
.scrollTargetBehavior(.viewAligned)
|
||||
```
|
||||
|
||||
### scrollPosition (iOS 17+)
|
||||
|
||||
Track topmost visible item. **Requires `.id()` on each item.**
|
||||
|
||||
```swift
|
||||
@State private var position: Item.ID?
|
||||
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(items) { item in
|
||||
ItemRow(item: item).id(item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollPosition(id: $position)
|
||||
```
|
||||
|
||||
### scrollTransition (iOS 17+)
|
||||
|
||||
```swift
|
||||
.scrollTransition { content, phase in
|
||||
content
|
||||
.opacity(1 - abs(phase.value) * 0.5)
|
||||
.scaleEffect(phase.isIdentity ? 1.0 : 0.75)
|
||||
}
|
||||
```
|
||||
|
||||
### onScrollGeometryChange (iOS 18+)
|
||||
|
||||
```swift
|
||||
.onScrollGeometryChange(for: Bool.self) { geo in
|
||||
geo.contentOffset.y < geo.contentInsets.top
|
||||
} action: { _, isTop in
|
||||
showBackButton = !isTop
|
||||
}
|
||||
```
|
||||
|
||||
### onScrollVisibilityChange (iOS 18+)
|
||||
|
||||
```swift
|
||||
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
|
||||
@@ -0,0 +1,3 @@
|
||||
interface:
|
||||
display_name: "SwiftUI Containers Reference"
|
||||
short_description: "Reference — SwiftUI stacks, grids, outlines, and scroll enhancements through iOS 26"
|
||||
Reference in New Issue
Block a user