Repository navigation
refactor!: BottomSheet drops its navigation container - #44
Conversation
BottomSheet wrapped fillsHeight content in a NavigationView with a hidden bar and closed itself through presentationMode, both pre-iOS 16 APIs. The container served one purpose in practice: giving the content back the bottom safe area that the sheet ignored so its background could run under the home indicator. The content now sits directly in the sheet, and the sheet keeps its safe areas: the content stops above the home indicator and keyboard avoidance stays on, while the background (a shape-style fill) runs under both. The close button dismisses through @Environment(\.dismiss). The two content branches differed only in maxHeight and are merged; the content no longer draws a second copy of the sheet background. The background doc no longer claims BottomSheet sets presentationBackground, and a preview covers a scrolling sheet with a text field at the bottom. BREAKING CHANGE: content inside a fillsHeight BottomSheet no longer has a navigation container, so a NavigationLink inside it no longer pushes. A host that moves between screens inside the sheet keeps the route in its own state and drives the header's back button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Sheet rendering, dismissal, and guidance Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift, docs/navigation-and-containers.md |
Both sizing modes use shared sheet content. Natural-height sheets retain vertical fixed sizing and height measurement; filled sheets do not. The close button uses dismiss. The preview and documentation describe the updated sheet layout and navigation behavior. |
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Other
Possibly related PRs
- dashpay/DashUIKit#15: It set the iOS deployment baseline that precedes removal of the remaining legacy BottomSheet APIs.
- dashpay/DashUIKit#47: It changes BottomSheet sizing behavior, including paths refactored in this PR.
- dashpay/DashUIKit#40: Its BottomSheet snapshot tests cover sizing and dismissal behavior changed here.
Suggested reviewers: jeanpierreroma
Merge Risk: 
Minimal · up to cce7b
-
No actionable merge-blocking issue remains. The navigation-container removal is documented, including host responsibilities for routing and accessibility.
Pre-merge checks: 
5
-
Full details: Linked Issues check
-
Explanation
Issue
#24coding requirements are implemented.BottomSheetuses@Environment(\.dismiss), removes theNavigationViewandnavigationBarHiddenusage, and unifies the filled and natural-height paths.BottomSheetDismissalAction.performstill invokesonClosefirst and otherwise callsdismiss()only when dismissal is enabled. The documentation explains the new container behavior and host-managed routes. The PR reports successful build and test runs. The reported missing keyboard visual check is a manual validation item, not a coding requirement.
Full details: Out of Scope Changes check
-
Explanation
The changed source supports Issue
#24by removing obsolete SwiftUI APIs, preserving sheet sizing and dismissal behavior, and adding the related preview. The documentation explains the resulting navigation behavior and migration pattern. No unrelated change is established.
Full details: Docstring Coverage
-
Explanation
No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 1 files. (1 skipped: 1 unsupported.)
-
Finishing Touches
-
Generate docstrings
-
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
romchornyi-pitcrew
left a comment
There was a problem hiding this comment.
✅ no blockers · no majors · 6 non-blocking recommendations — reviewed at cce7bb9b2e
| Level | Verdict | |
|---|---|---|
| 1 · Idea | ✅ valid | Closes #24: presentationMode, NavigationView and .navigationBarHidden predate the iOS 18 floor. Dropping the container (instead of swapping in a NavigationStack) removes the only reason it existed — handing the bottom safe area back to the content — and the sheet now keeps its safe areas itself |
| 2 · Architecture | ✅ right layer | The container and the close button live in DashUIKit; in-sheet routing moves to host state, which the header's showBackButton / onBackButtonPressed already supported |
| 3 · Completeness | ✅ complete | Every fillsHeight BottomSheet host in dashwallet-ios develop (2ba855841f, 28 call sites) was read: none uses NavigationLink, navigationDestination, .searchable, .toolbar, .navigationTitle or a wrapped controller's navigationController inside the sheet. The grouped-transactions route is already off NavigationLink (dashpay/dashwallet-ios#1286), so the merge-order note holds |
| 4 · Code | ✅ clean | Natural-height measurement is unchanged (same fixedSize → GeometryReader → preference order, bottom safe area still not ignored); .background(_:ignoresSafeAreaEdges:) is the overload .background(Color) already resolved to; fixedSize(vertical: false) is a no-op; dismiss and presentationMode resolve to the same presentation from the header. CI is green |
6 non-blocking recommendations
- The documented route leaves VoiceOver escape on the header closing the sheet — docs/navigation-and-containers.md#L166 — the snippet puts
.accessibilityAction(.escape)on the detail only. With focus on the library-owned header (Back, title, Close) the escape is not inside that subtree, so it reaches the presentation and closes the whole sheet instead of going back; a UIKit-wrapped detail never sees the SwiftUI action at all. The app's own host puts the action on theBottomSheetitself and adds a UIKit escape for the wrapped detail (dashwallet-iosHomeView.swiftL1193–L1194, L1239–L1241). Moving the snippet's action to the sheet — or havingBottomSheetmap escape toonBackButtonPressedwhileshowBackButtonis true — would match it. (fall-through not run on a device) - The docs name only part of what the container provided — docs/navigation-and-containers.md#L141-L145, BottomSheet.swift#L33-L35 — besides
NavigationLinkandnavigationController,.searchable,.navigationTitle, navigation-bar.toolbaritems andnavigationDestinationalso stop working inside afillsHeightsheet — or, when a host's ownNavigationStackencloses theBottomSheet, now attach to that outer stack (aNavigationLinkthen pushes there), which the hidden innerNavigationViewused to swallow. Naming them, plus the other migration (the content wraps its ownNavigationStackwith the bar hidden), would save the next host a surprise. Separately, thebackgrounddoc (#L37-L41) says the fill runs under the home indicator unconditionally; forfillsHeight: falsewithoutselfSizingSheet(a UIKit detent) that strip is the presentation background, notbackground. - No snapshot covers the rewritten
fillsHeightpath — BottomSheetSnapshotTests.swift — all BottomSheet snapshot tests usefillsHeight: false; the default path this PR changes is pinned only by the out-of-repo harness. The repoCLAUDE.mdasks for a snapshot per visually meaningful state; onefillsHeightstate (title + back, custom background) would pin content and background layout. The new "Text Field" preview (BottomSheet.swift#L526-L552) renders the sheet as the canvas root, so it shows neither the sheet's safe areas nor the keyboard it comments on; wrapping it in a.sheetwith a detent, as the other sheet previews do, would exercise the real path. - Content that ignores every safe-area region and the keyboard — BottomSheet.swift#L78-L79 — content with a bare
.ignoresSafeArea()(regions.all, so.keyboardtoo) now sits directly under the sheet's safe areas; under the oldNavigationViewthe UIKit boundary may have kept a bottom field above the keyboard. No current app host combines this with a text field, and the PR's harness could not raise a keyboard. (not verified) - Close button in an embedded hosting controller — BottomSheet.swift#L155-L160 — the PR already lists it: dashwallet-ios
RequestAmountHostingControllerembeds the sheet as a child controller with noonClose, so X relies ondismissreaching the presenter. Worth the planned check right after the app's DashUIKit pin moves past this change, together withTxDetailViewController's UIKit-presented sheets (explorer selection, raw transaction). - In-sheet routing has no library primitive — docs/navigation-and-containers.md#L146-L170 — every host now re-implements the ZStack, hit-testing,
accessibilityHidden, escape and focus wiring, and the snippet and the app'sInSheetPushStackalready differ (rec. 1). A small DashUIKit container for it would keep the pieces in one place; fine as a follow-up.
Review details
- Triage: normal (a shared container's behaviour;
refactor!with no public API change). - Levels 1–3 inline against the PR description, issue #24 and
BottomSheet.swiftat the head andmaster. Level 3 included an audit of everyBottomSheetconstruction in dashwallet-iosdevelop(2ba855841f) for navigation-container dependencies, UIKit presentation and child embedding, bottom-inset and keyboard behaviour. - Level 4: the general lane (
/code-review high) and a UI/compatibility lane (layout equivalence of both modes,selfSizingmeasurement, identity,dismisssemantics, macOS / iOS 18, the repoCLAUDE.md, docs and snippet correctness). - Verifier: recs 2, 3 and 6 were checked against the code; rec 1 against the code and the app's own escape handling; recs 4 and 5 need a running app and are marked accordingly. Kept out: the PR body's "flipping
fillsHeightno longer rebuilds the sheet" is true of the component, but the one app host that flips it (gift-card sheet) branches itself, so the benefit does not reach the app yet — description only.
🤖 Reviewed with Claude Code
Issue being fixed or feature implemented
Closes #24.
BottomSheetstill used three APIs that predate iOS 16:@Environment(\.presentationMode), aNavigationViewaroundfillsHeightcontent, and.navigationBarHidden(true)on it.What was done?
.background) still runs under the home indicator.@Environment(\.dismiss).fillsHeightand natural-height paths are one view with the mode passed as values. They differed only inmaxHeight,fixedSizeand the height preference. FlippingfillsHeightno longer rebuilds the sheet and drops host state.NavigationView).backgrounddoc no longer claimsBottomSheetsetspresentationBackground(onlyselfSizing→selfSizingSheetdoes).fillsHeightsheet with a text field at the bottom.docs/navigation-and-containers.mddescribes the behaviour and how to keep an in-sheet route in host state (list kept alive under the detail, back button, VoiceOver escape).The public API is unchanged.
How Has This Been Tested?
swift build,swift test, andxcodebuild test -scheme DashUIKiton an iPhone 17 Pro / iOS 27 simulator: all green.conventions.sh: OK.UIWindowwithlayer.render), run againstorigin/master(402db65) and this branch:.sheeton both master and this branch. For a UIKitpresented hosting controller, the harness cannot observe a dismiss on master or on this branch, so it gives no signal there.@Environment(\.dismiss)already closes UIKit-presented BottomSheets in production (dashwallet-iosHomeViewController+Shortcuts→ShortcutSelectionView).NavigationLinkpush inside the sheet works on master and does not on this branch — the expected breaking change.RequestAmountHostingController). Tapping X there was checked by hand in the app (refactor(ui): open a grouped transaction's details inside the sheet dashwallet-ios#1286); it needs one more check after the app bumps DashUIKit past this change.Before / After
All 14 harness states are byte-identical; three of them:
Breaking Changes
Behavioural, not in the API: content inside a
fillsHeightBottomSheetno longer has a navigation container. ANavigationLinkinside it no longer pushes, and a UIKit view controller wrapped in the content has nonavigationController. A host that moves between screens inside the sheet keeps the route in its own state and drives the header's back button (see the docs). In dashwallet-ios the only such host is the grouped-transactions screen, which the companion app change moves first.Checklist:
🤖 Generated with Claude Code
Summary by CodeRabbit