Files
bsky-social-app/patches/react-native-screens+4.16.0.patch
T
2025-09-26 10:41:17 -07:00

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;