327 lines
15 KiB
Diff
327 lines
15 KiB
Diff
diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm
|
|
index b62a2e2..cb469db 100644
|
|
--- a/node_modules/react-native-screens/ios/RNSScreen.mm
|
|
+++ b/node_modules/react-native-screens/ios/RNSScreen.mm
|
|
@@ -729,9 +729,26 @@ - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingFor
|
|
#endif
|
|
}
|
|
|
|
-#if !RCT_NEW_ARCH_ENABLED
|
|
+- (void)willMoveToWindow:(UIWindow *)newWindow
|
|
+{
|
|
+ if (@available(iOS 26, *)) {
|
|
+ // In iOS 26, as soon as another screen appears in transition, it is interactable
|
|
+ // To avoid glitches resulting from clicking buttons mid transition, we temporarily disable all interactions
|
|
+ // Disabling interactions for parent navigation controller won't be enough in case of nested stack
|
|
+ // Furthermore, a stack put inside a modal will exist in an entirely different hierarchy
|
|
+ // To be sure, we block interactions on the whole window.
|
|
+ // Note that newWindows is nil when moving from instead of moving to, and Obj-C handles nil correctly
|
|
+ newWindow.userInteractionEnabled = false;
|
|
+ }
|
|
+}
|
|
+
|
|
- (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
|
|
{
|
|
+ if (@available(iOS 26, *)) {
|
|
+ // Disable interactions to disallow multiple modals dismissed at once; see willMoveToWindow
|
|
+ presentationController.containerView.window.userInteractionEnabled = false;
|
|
+ }
|
|
+#if !RCT_NEW_ARCH_ENABLED
|
|
// On Paper, we need to call both "cancel" and "reset" here because RN's gesture
|
|
// recognizer does not handle the scenario when it gets cancelled by other top
|
|
// level gesture recognizer. In this case by the modal dismiss gesture.
|
|
@@ -744,8 +761,8 @@ - (void)presentationControllerWillDismiss:(UIPresentationController *)presentati
|
|
// down.
|
|
[_touchHandler cancel];
|
|
[_touchHandler reset];
|
|
-}
|
|
#endif // !RCT_NEW_ARCH_ENABLED
|
|
+}
|
|
|
|
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
|
|
{
|
|
@@ -757,6 +774,10 @@ - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presenta
|
|
|
|
- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController
|
|
{
|
|
+ if (@available(iOS 26, *)) {
|
|
+ // Reenable interactions; see presentationControllerWillDismiss
|
|
+ presentationController.containerView.window.userInteractionEnabled = true;
|
|
+ }
|
|
// NOTE(kkafar): We should consider depracating the use of gesture cancel here & align
|
|
// with usePreventRemove API of react-navigation v7.
|
|
[self notifyGestureCancel];
|
|
@@ -767,6 +788,11 @@ - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)pr
|
|
|
|
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
|
{
|
|
+ if (@available(iOS 26, *)) {
|
|
+ // Reenable interactions; see presentationControllerWillDismiss
|
|
+ // Dismissed screen doesn't hold a reference to window, but presentingViewController.view does
|
|
+ presentationController.presentingViewController.view.window.userInteractionEnabled = true;
|
|
+ }
|
|
if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) {
|
|
[_reactSuperview performSelector:@selector(presentationControllerDidDismiss:) withObject:presentationController];
|
|
}
|
|
@@ -1518,6 +1544,10 @@ - (void)viewWillDisappear:(BOOL)animated
|
|
|
|
- (void)viewDidAppear:(BOOL)animated
|
|
{
|
|
+ if (@available(iOS 26, *)) {
|
|
+ // Reenable interactions, see willMoveToWindow
|
|
+ self.view.window.userInteractionEnabled = true;
|
|
+ }
|
|
[super viewDidAppear:animated];
|
|
if (!_isSwiping || _shouldNotify) {
|
|
// we are going forward or dismissing without swipe
|
|
diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm
|
|
index 229dc58..10b365b 100644
|
|
--- a/node_modules/react-native-screens/ios/RNSScreenStack.mm
|
|
+++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm
|
|
@@ -62,26 +62,6 @@ @interface RNSScreenStackView () <
|
|
|
|
@implementation RNSNavigationController
|
|
|
|
-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
|
-- (void)viewDidLoad
|
|
-{
|
|
- // iOS 26 introduces new gesture recognizer which replaces our RNSPanGestureRecognizer.
|
|
- // The problem is that we are not able to handle it here for various reasons:
|
|
- // - the new recognizer comes with its own delegate and our current approach is to wire
|
|
- // all recognizers to RNSScreenStackView; to be 100% sure we don't break the logic,
|
|
- // we would have to decorate its delegate and call it after our code, which would
|
|
- // break other recognizers that the stack view is the delegate for
|
|
- // - when RNSScreenStackView.setupGestureHandler method is called, the recognizer hasn't been
|
|
- // loaded yet and there is no other place to configure in a not "hacky" way
|
|
- // - the official docs warn us to not use it for anything other than "setting up failure requirements with it"
|
|
- // - we expose fullScreenGestureEnabled prop to enable/disable the feature,
|
|
- // so we need control over the delegate
|
|
- if (@available(iOS 26.0, *)) {
|
|
- self.interactiveContentPopGestureRecognizer.enabled = NO;
|
|
- }
|
|
-}
|
|
-#endif // iOS 26
|
|
-
|
|
#if !TARGET_OS_TV
|
|
- (UIViewController *)childViewControllerForStatusBarStyle
|
|
{
|
|
@@ -219,50 +199,6 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController
|
|
return false;
|
|
}
|
|
|
|
-#pragma mark - UINavigationBarDelegate
|
|
-
|
|
-#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
|
-- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
|
|
-{
|
|
- if (@available(iOS 26, *)) {
|
|
- // To prevent popping multiple screens when back button is pressed repeatedly,
|
|
- // We allow for pop operation to proceed only if no transition is in progress,
|
|
- // which we check indirectly by checking if transitionCoordinator is set.
|
|
- // If it's not, we are safe to proceed.
|
|
- if (self.transitionCoordinator == nil) {
|
|
- // We still need to disable interactions for back button so click effects are not applied,
|
|
- // and there is unfortunately no better place for it currently
|
|
- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
|
|
- if (button != nil) {
|
|
- button.userInteractionEnabled = false;
|
|
- }
|
|
-
|
|
- return true;
|
|
- }
|
|
-
|
|
- return false;
|
|
- }
|
|
-
|
|
- return true;
|
|
-}
|
|
-
|
|
-- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
|
|
-{
|
|
- if (@available(iOS 26, *)) {
|
|
- // Reset interactions on back button -> see navigationBar:shouldPopItem
|
|
- // IMPORTANT: This reset won't execute when preventNativeDismiss is on.
|
|
- // However, on iOS 26, unlike in previous versions, the back button instance changes
|
|
- // when handling preventNativeDismiss and userIteractionEnabled is reset.
|
|
- // The instance also changes when regular screen pop happens, but in that case
|
|
- // the value of userInteractionEnabled is carried on, and we reset it here.
|
|
- UIView *button = [navigationBar rnscreens_findBackButtonWrapperView];
|
|
- if (button != nil) {
|
|
- button.userInteractionEnabled = true;
|
|
- }
|
|
- }
|
|
-}
|
|
-#endif // Check for iOS >= 26
|
|
-
|
|
#pragma mark - RNSFrameCorrectionProvider
|
|
|
|
#ifdef RNS_GAMMA_ENABLED
|
|
@@ -327,7 +263,7 @@ @implementation RNSScreenStackView {
|
|
UINavigationController *_controller;
|
|
NSMutableArray<RNSScreenView *> *_reactSubviews;
|
|
BOOL _invalidated;
|
|
- BOOL _isFullWidthSwiping;
|
|
+ BOOL _isFullWidthSwipingWithPanGesture; // used only for content swipe with RNSPanGestureRecognizer
|
|
RNSPercentDrivenInteractiveTransition *_interactionController;
|
|
__weak RNSScreenStackManager *_manager;
|
|
BOOL _updateScheduled;
|
|
@@ -522,6 +458,11 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller
|
|
[self addSubview:controller.view];
|
|
#if !TARGET_OS_TV
|
|
_controller.interactivePopGestureRecognizer.delegate = self;
|
|
+ #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
|
+ if (@available(iOS 26, *)) {
|
|
+ _controller.interactiveContentPopGestureRecognizer.delegate = self;
|
|
+ }
|
|
+#endif // Check for iOS >= 26.0
|
|
#endif
|
|
[controller didMoveToParentViewController:parentView.reactViewController];
|
|
// On iOS pre 12 we observed that `willShowViewController` delegate method does not always
|
|
@@ -943,7 +884,7 @@ - (void)dismissOnReload
|
|
// 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 && shouldCancelDismiss) || _isFullWidthSwiping ||
|
|
+ ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwipingWithPanGesture ||
|
|
[RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation] || _customAnimation)) {
|
|
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
|
|
}
|
|
@@ -967,23 +908,39 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
}
|
|
RNSScreenView *topScreen = _reactSubviews.lastObject;
|
|
|
|
+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
|
|
+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
|
|
+
|
|
#if TARGET_OS_TV || TARGET_OS_VISION
|
|
[self cancelTouchesInParent];
|
|
return YES;
|
|
#else
|
|
- // RNSPanGestureRecognizer will receive events iff topScreen.fullScreenSwipeEnabled == YES;
|
|
- // Events are filtered in gestureRecognizer:shouldReceivePressOrTouchEvent: method
|
|
if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
|
|
- if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
|
|
- _isFullWidthSwiping = YES;
|
|
- [self cancelTouchesInParent];
|
|
- return YES;
|
|
+ // On iOS < 26, we have a custom full screen swipe recognizer that functions similarily
|
|
+ // to interactiveContentPopGestureRecognizer introduced in iOS 26.
|
|
+ // On iOS >= 26, we want to use the native one, but we are unable to handle custom animations
|
|
+ // with native interactiveContentPopGestureRecognizer, so we have to fallback to the old implementation.
|
|
+ // In this case, the old one should behave as close as the new native one, having only the difference
|
|
+ // in animation, and without any customization that is exclusive for it (e.g. gestureResponseDistance).
|
|
+ if (@available(iOS 26, *)) {
|
|
+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
|
|
+ _isFullWidthSwipingWithPanGesture = YES;
|
|
+ [self cancelTouchesInParent];
|
|
+ return YES;
|
|
+ }
|
|
+ return NO;
|
|
+ } else {
|
|
+ if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
|
|
+ _isFullWidthSwipingWithPanGesture = YES;
|
|
+ [self cancelTouchesInParent];
|
|
+ return YES;
|
|
+ }
|
|
+ return NO;
|
|
}
|
|
- return NO;
|
|
}
|
|
|
|
// Now we're dealing with RNSScreenEdgeGestureRecognizer (or _UIParallaxTransitionPanGestureRecognizer)
|
|
- if (topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]) {
|
|
+ if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) {
|
|
if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) {
|
|
UIRectEdge edges = ((RNSScreenEdgeGestureRecognizer *)gestureRecognizer).edges;
|
|
BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft;
|
|
@@ -1028,7 +985,9 @@ - (void)setupGestureHandlers
|
|
rightEdgeSwipeGestureRecognizer.delegate = self;
|
|
[self addGestureRecognizer:rightEdgeSwipeGestureRecognizer];
|
|
|
|
- // gesture recognizer for full width swipe gesture
|
|
+ // Starting from iOS 26, RNSPanGestureRecognizer has been mostly replaced by native
|
|
+ // interactiveContentPopGestureRecognizer. It still needs to handle custom dismiss animations,
|
|
+ // which we are not able to handle with the latter.
|
|
RNSPanGestureRecognizer *panRecognizer = [[RNSPanGestureRecognizer alloc] initWithTarget:self
|
|
action:@selector(handleSwipe:)];
|
|
panRecognizer.delegate = self;
|
|
@@ -1091,7 +1050,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
|
|
[_interactionController cancelInteractiveTransition];
|
|
}
|
|
_interactionController = nil;
|
|
- _isFullWidthSwiping = NO;
|
|
+ _isFullWidthSwipingWithPanGesture = NO;
|
|
}
|
|
default: {
|
|
break;
|
|
@@ -1225,14 +1184,6 @@ - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognize
|
|
// Be careful when adding another type of gesture recognizer.
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePressOrTouchEvent:(NSObject *)event
|
|
{
|
|
- if (@available(iOS 26, *)) {
|
|
- // in iOS 26, you can swipe to pop screen before the previous one finished transitioning;
|
|
- // this prevents from registering the second gesture
|
|
- if ([self isTransitionInProgress]) {
|
|
- return NO;
|
|
- }
|
|
- }
|
|
-
|
|
RNSScreenView *topScreen = _reactSubviews.lastObject;
|
|
|
|
for (RNSScreenView *s in _reactSubviews.reverseObjectEnumerator) {
|
|
@@ -1249,10 +1200,30 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
|
|
return NO;
|
|
}
|
|
|
|
+ BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom =
|
|
+ topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation];
|
|
+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
|
+ if (@available(iOS 26, *)) {
|
|
+ // On iOS 26, fullScreenSwipeEnabled takes no effect, and depending on whether custom animations are on,
|
|
+ // we select either interactiveContentPopGestureRecognizer or RNSPanGestureRecognizer
|
|
+ if (([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] &&
|
|
+ !customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) ||
|
|
+ (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
|
|
+ customAnimationOnSwipePropSetAndSelectedAnimationIsCustom)) {
|
|
+ return NO;
|
|
+ }
|
|
+ } else {
|
|
+ // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
|
|
+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
|
|
+ return topScreen.fullScreenSwipeEnabled;
|
|
+ }
|
|
+ }
|
|
+#else // check for iOS >= 26
|
|
// We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
|
|
if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
|
|
return topScreen.fullScreenSwipeEnabled;
|
|
}
|
|
+#endif // check for iOS >= 26
|
|
|
|
// RNSScreenEdgeGestureRecognizer || _UIParallaxTransitionPanGestureRecognizer
|
|
return YES;
|
|
@@ -1268,15 +1239,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
|
|
return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:touch];
|
|
}
|
|
|
|
-- (BOOL)isTransitionInProgress
|
|
-{
|
|
- if (_controller.transitionCoordinator != nil) {
|
|
- return YES;
|
|
- }
|
|
-
|
|
- return NO;
|
|
-}
|
|
-
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
{
|
|
@@ -1289,7 +1251,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) {
|
|
return NO;
|
|
}
|
|
-
|
|
return YES;
|
|
}
|
|
return NO;
|