From fe34551d7885fc450fcf6c0a74f4a183e8a74332 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sun, 15 Mar 2026 11:12:21 +0100 Subject: [PATCH] cleanup --- swiftui-ui-patterns/SKILL.md | 40 ------------------ .../references/components-index.md | 2 +- swiftui-ui-patterns/references/sheets.md | 42 +++++++++++++++++++ 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/swiftui-ui-patterns/SKILL.md b/swiftui-ui-patterns/SKILL.md index 04464f4..d7a18c3 100644 --- a/swiftui-ui-patterns/SKILL.md +++ b/swiftui-ui-patterns/SKILL.md @@ -88,46 +88,6 @@ Use `references/components-index.md` as the entry point. Each component referenc - Pitfalls and performance notes. - Paths to existing examples in the current repo. -For detail surfaces that progressively reveal actions, metadata, or contextual panels, use `references/scroll-reveal.md`. - -## 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/.md`. diff --git a/swiftui-ui-patterns/references/components-index.md b/swiftui-ui-patterns/references/components-index.md index 80c005c..0156b4d 100644 --- a/swiftui-ui-patterns/references/components-index.md +++ b/swiftui-ui-patterns/references/components-index.md @@ -6,7 +6,7 @@ Use this file to find component and cross-cutting guidance. Each entry lists whe - TabView: `references/tabview.md` — Use when building a tab-based app or any tabbed feature set. - NavigationStack: `references/navigationstack.md` — Use when you need push navigation and programmatic routing, especially per-tab history. -- Sheets and modal routing: `references/sheets.md` — Use when you want centralized, enum-driven sheet presentation. +- Sheets and presentation: `references/sheets.md` — Use for local item-driven sheets, centralized modal routing, and sheet-specific action patterns. - Form and Settings: `references/form.md` — Use for settings, grouped inputs, and structured data entry. - macOS Settings: `references/macos-settings.md` — Use when building a macOS Settings window with SwiftUI's Settings scene. - Split views and columns: `references/split-views.md` — Use for iPad/macOS multi-column layouts or custom secondary columns. diff --git a/swiftui-ui-patterns/references/sheets.md b/swiftui-ui-patterns/references/sheets.md index 9ba07d6..ce52412 100644 --- a/swiftui-ui-patterns/references/sheets.md +++ b/swiftui-ui-patterns/references/sheets.md @@ -11,6 +11,18 @@ Use a centralized sheet routing pattern so any view can present modals without p - Create a view modifier like `withSheetDestinations(...)` that maps the enum to concrete sheet views. - Inject the router into the environment so child views can set `presentedSheet` directly. +## Example: item-driven local sheet + +Use this when sheet state is local to one screen and does not need centralized routing. + +```swift +@State private var selectedItem: Item? + +.sheet(item: $selectedItem) { item in + EditItemSheet(item: item) +} +``` + ## Example: SheetDestination enum ```swift @@ -99,15 +111,45 @@ struct NavigationSheet: View { } ``` +## Example: sheet owns its actions + +Keep dismissal and confirmation logic inside the sheet when the actions belong to the modal itself. + +```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() + } +} +``` + ## Design choices to keep - Centralize sheet routing so features can present modals without wiring bindings through many layers. - Use `sheet(item:)` to guarantee a single sheet is active and to drive presentation from the enum. - Group related sheets under the same `id` when they are mutually exclusive (e.g., editor flows). - Keep sheet views lightweight and composed from smaller views; avoid large monoliths. +- Let sheets own their actions and call `dismiss()` internally instead of forwarding `onCancel` or `onConfirm` closures through many layers. ## Pitfalls - Avoid mixing `sheet(isPresented:)` and `sheet(item:)` for the same concern; prefer a single enum. +- Avoid `if let` inside a sheet body when the presentation state already carries the selected model; prefer `sheet(item:)`. - Do not store heavy state inside `SheetDestination`; pass lightweight identifiers or models. - If multiple sheets can appear from the same screen, give them distinct `id` values.