From d73dce5efa93de15218aa7b14892c2c200f35702 Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Mon, 22 May 2023 18:48:16 +0200 Subject: [PATCH 1/4] feat: prevent native back button dismissal on iOS --- ios/RNSScreen.h | 1 + ios/RNSScreenStack.mm | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ios/RNSScreen.h b/ios/RNSScreen.h index e4e0f23cb3..c0b00abf9a 100644 --- a/ios/RNSScreen.h +++ b/ios/RNSScreen.h @@ -111,6 +111,7 @@ NS_ASSUME_NONNULL_BEGIN #endif - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingForward:(BOOL)goingForward; +- (void)notifyDismissCancelledWithDismissCount:(int)dismissCount; - (BOOL)isModal; @end diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index 4c45bdf2b5..ef41930c99 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -567,9 +567,11 @@ - (void)dismissOnReload screen = ((RNSScreen *)fromVC).screenView; } if (screen != nil && - // we need to return the animator when full width swiping even if the animation is not custom, + // when preventing the native dismiss with back button, we have to return the animator. + // Also, we need to return the animator when full width swiping even if the animation is not custom, // otherwise the screen will be just popped immediately due to no animation - (_isFullWidthSwiping || [RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation])) { + ((operation == UINavigationControllerOperationPop && screen.preventNativeDismiss) || _isFullWidthSwiping || + [RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation])) { return [[RNSScreenStackAnimator alloc] initWithOperation:operation]; } return nil; @@ -727,6 +729,17 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer interactionControllerForAnimationController: (id)animationController { + RNSScreenView *sourceView = [_controller.transitionCoordinator viewForKey:UITransitionContextFromViewKey]; + // we can intercept clicking back button here, we check reactSuperview since this method also fires when + // going back from JS + if (_interactionController == nil && sourceView.reactSuperview && sourceView.preventNativeDismiss) { + _interactionController = [UIPercentDrivenInteractiveTransition new]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self->_interactionController cancelInteractiveTransition]; + self->_interactionController = nil; + [sourceView notifyDismissCancelledWithDismissCount:1]; + }); + } return _interactionController; } From 8ebe7bfe8d9edf88ff3bddffcffe9c6195efe533 Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Tue, 23 May 2023 12:44:56 +0200 Subject: [PATCH 2/4] fix: discarding multiple screens --- ios/RNSScreenStack.mm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index ef41930c99..5e9ced107a 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -730,14 +730,18 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer (id)animationController { RNSScreenView *sourceView = [_controller.transitionCoordinator viewForKey:UITransitionContextFromViewKey]; + RNSScreenView *targetView = [_controller.transitionCoordinator viewForKey:UITransitionContextToViewKey]; // we can intercept clicking back button here, we check reactSuperview since this method also fires when - // going back from JS if (_interactionController == nil && sourceView.reactSuperview && sourceView.preventNativeDismiss) { _interactionController = [UIPercentDrivenInteractiveTransition new]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self->_interactionController cancelInteractiveTransition]; self->_interactionController = nil; - [sourceView notifyDismissCancelledWithDismissCount:1]; + int sourceIndex = (int)[[self reactSubviews] indexOfObject:sourceView]; + int targetIndex = (int)[[self reactSubviews] indexOfObject:targetView]; + int dismissCount = sourceIndex - targetIndex > 0 ? sourceIndex - targetIndex : 1; + [self updateContainer]; + [sourceView notifyDismissCancelledWithDismissCount:dismissCount]; }); } return _interactionController; From 9cfc385c9b40198cfc77d9c1ebb6dd2fa7615570 Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Tue, 23 May 2023 13:58:15 +0200 Subject: [PATCH 3/4] Update ios/RNSScreenStack.mm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kacper KapuĊ›ciak <39658211+kacperkapusciak@users.noreply.github.com> --- ios/RNSScreenStack.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index 5e9ced107a..7f915e2dcb 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -739,7 +739,8 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer self->_interactionController = nil; int sourceIndex = (int)[[self reactSubviews] indexOfObject:sourceView]; int targetIndex = (int)[[self reactSubviews] indexOfObject:targetView]; - int dismissCount = sourceIndex - targetIndex > 0 ? sourceIndex - targetIndex : 1; + int indexDiff = sourceIndex - targetIndex; + int dismissCount = indexDiff > 0 ? indexDiff : 1; [self updateContainer]; [sourceView notifyDismissCancelledWithDismissCount:dismissCount]; }); From e905d5590680594a343c0fc336545b334fad2621 Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Tue, 23 May 2023 16:01:33 +0200 Subject: [PATCH 4/4] fix: inner prevent dismiss --- ios/RNSScreenStack.mm | 51 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm index 7f915e2dcb..e97011e70e 100644 --- a/ios/RNSScreenStack.mm +++ b/ios/RNSScreenStack.mm @@ -140,6 +140,21 @@ - (void)initCommonProps [_controller setViewControllers:@[ [UIViewController new] ]]; } +#pragma mark - helper methods + +- (BOOL)shouldCancelDismissFromView:(RNSScreenView *)fromView toView:(RNSScreenView *)toView +{ + int fromIndex = (int)[_reactSubviews indexOfObject:fromView]; + int toIndex = (int)[_reactSubviews indexOfObject:toView]; + for (int i = fromIndex; i > toIndex; i--) { + if (_reactSubviews[i].preventNativeDismiss) { + return YES; + break; + } + } + return NO; +} + #pragma mark - Common - (void)emitOnFinishTransitioningEvent @@ -566,11 +581,13 @@ - (void)dismissOnReload } else if (operation == UINavigationControllerOperationPop) { screen = ((RNSScreen *)fromVC).screenView; } + BOOL shouldCancelDismiss = [self shouldCancelDismissFromView:(RNSScreenView *)fromVC.view + toView:(RNSScreenView *)toVC.view]; if (screen != nil && // when preventing the native dismiss with back button, we have to return the animator. // Also, we need to return the animator when full width swiping even if the animation is not custom, // otherwise the screen will be just popped immediately due to no animation - ((operation == UINavigationControllerOperationPop && screen.preventNativeDismiss) || _isFullWidthSwiping || + ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwiping || [RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation])) { return [[RNSScreenStackAnimator alloc] initWithOperation:operation]; } @@ -729,21 +746,25 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer interactionControllerForAnimationController: (id)animationController { - RNSScreenView *sourceView = [_controller.transitionCoordinator viewForKey:UITransitionContextFromViewKey]; - RNSScreenView *targetView = [_controller.transitionCoordinator viewForKey:UITransitionContextToViewKey]; + RNSScreenView *fromView = [_controller.transitionCoordinator viewForKey:UITransitionContextFromViewKey]; + RNSScreenView *toView = [_controller.transitionCoordinator viewForKey:UITransitionContextToViewKey]; // we can intercept clicking back button here, we check reactSuperview since this method also fires when - if (_interactionController == nil && sourceView.reactSuperview && sourceView.preventNativeDismiss) { - _interactionController = [UIPercentDrivenInteractiveTransition new]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self->_interactionController cancelInteractiveTransition]; - self->_interactionController = nil; - int sourceIndex = (int)[[self reactSubviews] indexOfObject:sourceView]; - int targetIndex = (int)[[self reactSubviews] indexOfObject:targetView]; - int indexDiff = sourceIndex - targetIndex; - int dismissCount = indexDiff > 0 ? indexDiff : 1; - [self updateContainer]; - [sourceView notifyDismissCancelledWithDismissCount:dismissCount]; - }); + // navigating back from JS + if (_interactionController == nil && fromView.reactSuperview) { + BOOL shouldCancelDismiss = [self shouldCancelDismissFromView:fromView toView:toView]; + if (shouldCancelDismiss) { + _interactionController = [UIPercentDrivenInteractiveTransition new]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self->_interactionController cancelInteractiveTransition]; + self->_interactionController = nil; + int fromIndex = (int)[self->_reactSubviews indexOfObject:fromView]; + int toIndex = (int)[self->_reactSubviews indexOfObject:toView]; + int indexDiff = fromIndex - toIndex; + int dismissCount = indexDiff > 0 ? indexDiff : 1; + [self updateContainer]; + [fromView notifyDismissCancelledWithDismissCount:dismissCount]; + }); + } } return _interactionController; }