feat(iOS, FormSheet v5): Add support for preventNativeDismiss & onNativeDismissPrevented#4022
feat(iOS, FormSheet v5): Add support for preventNativeDismiss & onNativeDismissPrevented#4022t0maboro wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the experimental Gamma FormSheet (Fabric/iOS) implementation with a preventNativeDismiss prop to block user-initiated dismiss (swipe-down + backdrop tap), and introduces an onNativeDismissPrevented event so JS can react to blocked dismissal attempts.
Changes:
- Added
preventNativeDismissto the FormSheet JS API and Fabric native component props. - Added
onNativeDismissPreventedevent plumbing (codegen prop → C++ event emitter → ObjC++ event emitter → JS). - Implemented iOS native prevention via
UIAdaptivePresentationControllerDelegateand a custom backdropUITapGestureRecognizer, plus added an SFT scenario for manual validation.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/fabric/gamma/modals/form-sheet/FormSheetHostNativeComponent.ts | Adds codegen prop/event definitions for preventNativeDismiss + onNativeDismissPrevented. |
| src/components/gamma/modals/form-sheet/FormSheet.types.ts | Exposes the new prop + event in the public TS types/docs. |
| ios/gamma/modals/form-sheet/RNSFormSheetHostEventEmitter.mm | Emits onNativeDismissPrevented to JS when dismissal is blocked. |
| ios/gamma/modals/form-sheet/RNSFormSheetHostEventEmitter.h | Declares emitOnNativeDismissPrevented. |
| ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.mm | Tracks preventNativeDismiss prop and forwards it to the controller; emits prevented event via delegate callback. |
| ios/gamma/modals/form-sheet/RNSFormSheetContentController.mm | Implements presentationControllerShouldDismiss / ...DidAttemptToDismiss and backdrop-tap interception via gesture recognizer. |
| ios/gamma/modals/form-sheet/RNSFormSheetContentController.h | Adds delegate method + preventNativeDismiss property. |
| apps/src/tests/single-feature-tests/form-sheet/test-form-sheet-prevent-native-dismiss-ios/scenario.md | Adds manual test scenario documentation for the new behavior. |
| apps/src/tests/single-feature-tests/form-sheet/test-form-sheet-prevent-native-dismiss-ios/index.tsx | Adds an SFT screen to exercise the new prop/event. |
| apps/src/tests/single-feature-tests/form-sheet/index.ts | Registers the new SFT scenario in the FormSheet group. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
9eebb45 to
7efa595
Compare
322ae24 to
804c8c7
Compare
kligarski
left a comment
There was a problem hiding this comment.
Screen.Recording.2026-05-18.at.17.53.06.mov
might be related to missing detachBackdropTapGestureRecognizer call
804c8c7 to
ad4e555
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Krzysztof Ligarski <63918941+kligarski@users.noreply.github.com>
3ff5602 to
b5dc467
Compare
|
|
||
| - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController | ||
| { | ||
| if (self.preventNativeDismiss) { |
There was a problem hiding this comment.
| if (self.preventNativeDismiss) { | |
| if (_behaviorProvider.preventNativeDismiss) { |
Maybe we can use the behavior provider here and in other places instead of duplicating state in ContentController? But we might want to check if it's not nil before.
| * @summary Prevents the user from dismissing the sheet natively by swiping down or tapping the backdrop. | ||
| * | ||
| * When set to `true`, the sheet will resist the swipe-down gesture and backdrop tap, | ||
| * remaining on the resting detent. Dismissal programmatically via `isOpen={false}` will still work. |
There was a problem hiding this comment.
| * remaining on the resting detent. Dismissal programmatically via `isOpen={false}` will still work. | |
| * remaining on the resting detent. Programmatic dismissal via `isOpen={false}` will still work. |
| * remaining on the resting detent. Dismissal programmatically via `isOpen={false}` will still work. | |
| * remaining on the resting detent. Programmatically dismissing the sheet via `isOpen={false}` will still work. |
Description
Adds
preventNativeDismisssupport. WhenpreventNativeDismissistrue, the user cannot dismiss the sheet natively via swipe-down or by tapping the backdrop. Programmatic dismissal from JS still works.When dismissal is prevented, the sheet emits a new
onNativeDismissPreventedevent so JS can react.Note
Backdrop taps need extra handling because UIKit doesn't fire delegate callbacks for this path. I follow the same approach as in #3771 with attaching a
UITapGestureRecognizerto the presentation controller'scontainerViewand filter touches, so only taps outsidepresentedVieware recognized. The recognizer is attached/detached inviewWillAppear/viewDidDisappear, because UIKit recreates the container view on every present cycle.Closes: https://github.com/software-mansion/react-native-screens-labs/issues/1272
Changes
preventNativeDismissproponNativeDismissPreventedeventUITapGestureRecognizerfor backdrop taps.presentationControllerShouldDismiss,presentationControllerDidAttemptToDismissVisual documentation
preventnativedismiss.mov