2929#import " RNSScreenWindowTraits.h"
3030#import " RNSScrollViewFinder.h"
3131#import " RNSTabsScreenViewController.h"
32+ #import " RNSViewInteractionAware.h"
3233#import " UIScrollView+RNScreens.h"
3334#import " UIView+RNSUtility.h"
3435#import " integrations/RNSDismissibleModalProtocol.h"
@@ -47,7 +48,8 @@ @interface RNSScreenStackView () <
4748 UINavigationControllerDelegate,
4849 UIAdaptivePresentationControllerDelegate,
4950 UIGestureRecognizerDelegate,
50- UIViewControllerTransitioningDelegate
51+ UIViewControllerTransitioningDelegate,
52+ RNSViewInteractionAware
5153#ifdef RCT_NEW_ARCH_ENABLED
5254 ,
5355 RCTMountingTransactionObserving
@@ -259,6 +261,7 @@ @implementation RNSScreenStackView {
259261 RNSPercentDrivenInteractiveTransition *_interactionController;
260262 __weak RNSScreenStackManager *_manager;
261263 BOOL _updateScheduled;
264+ UIPanGestureRecognizer *_sinkEventsPanGestureRecognizer;
262265#ifdef RCT_NEW_ARCH_ENABLED
263266 // / Screens that are subject of `ShadowViewMutation::Type::Delete` mutation
264267 // / in current transaction. This vector should be populated when we receive notification via
@@ -304,6 +307,7 @@ - (void)initCommonProps
304307 _presentedModals = [NSMutableArray new ];
305308 _controller = [RNSNavigationController new ];
306309 _controller.delegate = self;
310+ _sinkEventsPanGestureRecognizer = [[UIPanGestureRecognizer alloc ] init ];
307311#if !TARGET_OS_TV && !TARGET_OS_VISION
308312 [self setupGestureHandlers ];
309313#endif
@@ -899,6 +903,20 @@ - (void)cancelTouchesInParent
899903 [[self rnscreens_findTouchHandlerInAncestorChain ] rnscreens_cancelTouches ];
900904}
901905
906+ - (void )disableInteractions
907+ {
908+ // When transitioning between screens, disable interactions on stack subview which wraps the screens
909+ // and sink all gesture events. This should work for nested stacks and stack inside bottom tabs, inside stack.
910+ self.subviews [0 ].userInteractionEnabled = NO ;
911+ [self addGestureRecognizer: _sinkEventsPanGestureRecognizer];
912+ }
913+
914+ - (void )enableInteractions
915+ {
916+ self.subviews [0 ].userInteractionEnabled = YES ;
917+ [self removeGestureRecognizer: _sinkEventsPanGestureRecognizer];
918+ }
919+
902920- (BOOL )gestureRecognizerShouldBegin : (UIGestureRecognizer *)gestureRecognizer
903921{
904922 if (_disableSwipeBack) {
@@ -1265,6 +1283,15 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
12651283- (BOOL )gestureRecognizer : (UIGestureRecognizer *)gestureRecognizer
12661284 shouldRequireFailureOfGestureRecognizer : (UIGestureRecognizer *)otherGestureRecognizer
12671285{
1286+ if (otherGestureRecognizer == _sinkEventsPanGestureRecognizer) {
1287+ // When transition happens between two stack screens, a special "sink" recognizer is added, and then removed.
1288+ // It captures all gestures for the time of transition and does nothing, so that in nested stack scenario,
1289+ // the outer most stack does not recognize swipe gestures, otherwise it would dismiss the whole nested stack.
1290+ // For the recognizer to work as described, it should have precedence over all other recognizers.
1291+ // see also: enableInteractions, disableInteractions
1292+ return YES ;
1293+ }
1294+
12681295 if (@available (iOS 26 , *)) {
12691296 if (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
12701297 [self isScrollViewPanGestureRecognizer: otherGestureRecognizer]) {
0 commit comments