SwiftUI UI Patterns
Quick start
Choose a track based on your goal:
Existing project
- •Identify the feature or screen and the primary interaction model (list, detail, editor, settings, tabbed).
- •Find a nearby example in the repo with
rg "TabView\("or similar, then read the closest SwiftUI view. - •Apply local conventions: prefer SwiftUI-native state, keep state local when possible, and use environment injection for shared dependencies.
- •Choose the relevant component reference from
references/components-index.mdand follow its guidance. - •Build the view with small, focused subviews and SwiftUI-native data flow.
New project scaffolding
- •Start with
references/app-scaffolding-wiring.mdto wire TabView + NavigationStack + sheets. - •Add a minimal
AppTabandRouterPathbased on the provided skeletons. - •Choose the next component reference based on the UI you need first (TabView, NavigationStack, Sheets).
- •Expand the route and sheet enums as new screens are added.
General rules to follow
- •Use modern SwiftUI state (
@State,@Binding,@Observable,@Environment) and avoid unnecessary view models. - •Prefer composition; keep views small and focused.
- •Use async/await with
.taskand explicit loading/error states. - •Maintain existing legacy patterns only when editing legacy files.
- •Follow the project's formatter and style guide.
- •Sheets: Prefer
.sheet(item:)over.sheet(isPresented:)when state represents a selected model. Avoidif letinside a sheet body. Sheets should own their actions and calldismiss()internally instead of forwardingonCancel/onConfirmclosures.
Workflow for a new SwiftUI view
- •Define the view's state and its ownership location.
- •Identify dependencies to inject via
@Environment. - •Sketch the view hierarchy and extract repeated parts into subviews.
- •Implement async loading with
.taskand explicit state enum if needed. - •Add accessibility labels or identifiers when the UI is interactive.
- •Validate with a build and update usage callsites if needed.
Component references
Use references/components-index.md as the entry point. Each component reference should include:
- •Intent and best-fit scenarios.
- •Minimal usage pattern with local conventions.
- •Pitfalls and performance notes.
- •Paths to existing examples in the current repo.
Sheet patterns
Item-driven sheet (preferred)
swift
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}
Sheet owns its actions
swift
struct EditItemSheet: View {
@Environment(\.dismiss) private var dismiss
@Environment(Store.self) private var store
let item: Item
@State private var isSaving = false
var body: some View {
VStack {
Button(isSaving ? "Saving…" : "Save") {
Task { await save() }
}
}
}
private func save() async {
isSaving = true
await store.save(item)
dismiss()
}
}
Adding a new component reference
- •Create
references/<component>.md. - •Keep it short and actionable; link to concrete files in the current repo.
- •Update
references/components-index.mdwith the new entry.