deer social fork for personal usage. but you might see a use idk. github mirror

patch react native screens (#9087)

authored by samuel.fm and committed by GitHub 7f4e3091 6114b33c

Changed files
+326
patches
+326
patches/react-native-screens+4.16.0.patch
··· 1 + diff --git a/node_modules/react-native-screens/ios/RNSScreen.mm b/node_modules/react-native-screens/ios/RNSScreen.mm 2 + index b62a2e2..cb469db 100644 3 + --- a/node_modules/react-native-screens/ios/RNSScreen.mm 4 + +++ b/node_modules/react-native-screens/ios/RNSScreen.mm 5 + @@ -729,9 +729,26 @@ - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingFor 6 + #endif 7 + } 8 + 9 + -#if !RCT_NEW_ARCH_ENABLED 10 + +- (void)willMoveToWindow:(UIWindow *)newWindow 11 + +{ 12 + + if (@available(iOS 26, *)) { 13 + + // In iOS 26, as soon as another screen appears in transition, it is interactable 14 + + // To avoid glitches resulting from clicking buttons mid transition, we temporarily disable all interactions 15 + + // Disabling interactions for parent navigation controller won't be enough in case of nested stack 16 + + // Furthermore, a stack put inside a modal will exist in an entirely different hierarchy 17 + + // To be sure, we block interactions on the whole window. 18 + + // Note that newWindows is nil when moving from instead of moving to, and Obj-C handles nil correctly 19 + + newWindow.userInteractionEnabled = false; 20 + + } 21 + +} 22 + + 23 + - (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController 24 + { 25 + + if (@available(iOS 26, *)) { 26 + + // Disable interactions to disallow multiple modals dismissed at once; see willMoveToWindow 27 + + presentationController.containerView.window.userInteractionEnabled = false; 28 + + } 29 + +#if !RCT_NEW_ARCH_ENABLED 30 + // On Paper, we need to call both "cancel" and "reset" here because RN's gesture 31 + // recognizer does not handle the scenario when it gets cancelled by other top 32 + // level gesture recognizer. In this case by the modal dismiss gesture. 33 + @@ -744,8 +761,8 @@ - (void)presentationControllerWillDismiss:(UIPresentationController *)presentati 34 + // down. 35 + [_touchHandler cancel]; 36 + [_touchHandler reset]; 37 + -} 38 + #endif // !RCT_NEW_ARCH_ENABLED 39 + +} 40 + 41 + - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController 42 + { 43 + @@ -757,6 +774,10 @@ - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presenta 44 + 45 + - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)presentationController 46 + { 47 + + if (@available(iOS 26, *)) { 48 + + // Reenable interactions; see presentationControllerWillDismiss 49 + + presentationController.containerView.window.userInteractionEnabled = true; 50 + + } 51 + // NOTE(kkafar): We should consider depracating the use of gesture cancel here & align 52 + // with usePreventRemove API of react-navigation v7. 53 + [self notifyGestureCancel]; 54 + @@ -767,6 +788,11 @@ - (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)pr 55 + 56 + - (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController 57 + { 58 + + if (@available(iOS 26, *)) { 59 + + // Reenable interactions; see presentationControllerWillDismiss 60 + + // Dismissed screen doesn't hold a reference to window, but presentingViewController.view does 61 + + presentationController.presentingViewController.view.window.userInteractionEnabled = true; 62 + + } 63 + if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) { 64 + [_reactSuperview performSelector:@selector(presentationControllerDidDismiss:) withObject:presentationController]; 65 + } 66 + @@ -1518,6 +1544,10 @@ - (void)viewWillDisappear:(BOOL)animated 67 + 68 + - (void)viewDidAppear:(BOOL)animated 69 + { 70 + + if (@available(iOS 26, *)) { 71 + + // Reenable interactions, see willMoveToWindow 72 + + self.view.window.userInteractionEnabled = true; 73 + + } 74 + [super viewDidAppear:animated]; 75 + if (!_isSwiping || _shouldNotify) { 76 + // we are going forward or dismissing without swipe 77 + diff --git a/node_modules/react-native-screens/ios/RNSScreenStack.mm b/node_modules/react-native-screens/ios/RNSScreenStack.mm 78 + index 229dc58..10b365b 100644 79 + --- a/node_modules/react-native-screens/ios/RNSScreenStack.mm 80 + +++ b/node_modules/react-native-screens/ios/RNSScreenStack.mm 81 + @@ -62,26 +62,6 @@ @interface RNSScreenStackView () < 82 + 83 + @implementation RNSNavigationController 84 + 85 + -#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) 86 + -- (void)viewDidLoad 87 + -{ 88 + - // iOS 26 introduces new gesture recognizer which replaces our RNSPanGestureRecognizer. 89 + - // The problem is that we are not able to handle it here for various reasons: 90 + - // - the new recognizer comes with its own delegate and our current approach is to wire 91 + - // all recognizers to RNSScreenStackView; to be 100% sure we don't break the logic, 92 + - // we would have to decorate its delegate and call it after our code, which would 93 + - // break other recognizers that the stack view is the delegate for 94 + - // - when RNSScreenStackView.setupGestureHandler method is called, the recognizer hasn't been 95 + - // loaded yet and there is no other place to configure in a not "hacky" way 96 + - // - the official docs warn us to not use it for anything other than "setting up failure requirements with it" 97 + - // - we expose fullScreenGestureEnabled prop to enable/disable the feature, 98 + - // so we need control over the delegate 99 + - if (@available(iOS 26.0, *)) { 100 + - self.interactiveContentPopGestureRecognizer.enabled = NO; 101 + - } 102 + -} 103 + -#endif // iOS 26 104 + - 105 + #if !TARGET_OS_TV 106 + - (UIViewController *)childViewControllerForStatusBarStyle 107 + { 108 + @@ -219,50 +199,6 @@ - (bool)onRepeatedTabSelectionOfTabScreenController:(RNSTabsScreenViewController 109 + return false; 110 + } 111 + 112 + -#pragma mark - UINavigationBarDelegate 113 + - 114 + -#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) 115 + -- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item 116 + -{ 117 + - if (@available(iOS 26, *)) { 118 + - // To prevent popping multiple screens when back button is pressed repeatedly, 119 + - // We allow for pop operation to proceed only if no transition is in progress, 120 + - // which we check indirectly by checking if transitionCoordinator is set. 121 + - // If it's not, we are safe to proceed. 122 + - if (self.transitionCoordinator == nil) { 123 + - // We still need to disable interactions for back button so click effects are not applied, 124 + - // and there is unfortunately no better place for it currently 125 + - UIView *button = [navigationBar rnscreens_findBackButtonWrapperView]; 126 + - if (button != nil) { 127 + - button.userInteractionEnabled = false; 128 + - } 129 + - 130 + - return true; 131 + - } 132 + - 133 + - return false; 134 + - } 135 + - 136 + - return true; 137 + -} 138 + - 139 + -- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item 140 + -{ 141 + - if (@available(iOS 26, *)) { 142 + - // Reset interactions on back button -> see navigationBar:shouldPopItem 143 + - // IMPORTANT: This reset won't execute when preventNativeDismiss is on. 144 + - // However, on iOS 26, unlike in previous versions, the back button instance changes 145 + - // when handling preventNativeDismiss and userIteractionEnabled is reset. 146 + - // The instance also changes when regular screen pop happens, but in that case 147 + - // the value of userInteractionEnabled is carried on, and we reset it here. 148 + - UIView *button = [navigationBar rnscreens_findBackButtonWrapperView]; 149 + - if (button != nil) { 150 + - button.userInteractionEnabled = true; 151 + - } 152 + - } 153 + -} 154 + -#endif // Check for iOS >= 26 155 + - 156 + #pragma mark - RNSFrameCorrectionProvider 157 + 158 + #ifdef RNS_GAMMA_ENABLED 159 + @@ -327,7 +263,7 @@ @implementation RNSScreenStackView { 160 + UINavigationController *_controller; 161 + NSMutableArray<RNSScreenView *> *_reactSubviews; 162 + BOOL _invalidated; 163 + - BOOL _isFullWidthSwiping; 164 + + BOOL _isFullWidthSwipingWithPanGesture; // used only for content swipe with RNSPanGestureRecognizer 165 + RNSPercentDrivenInteractiveTransition *_interactionController; 166 + __weak RNSScreenStackManager *_manager; 167 + BOOL _updateScheduled; 168 + @@ -522,6 +458,11 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller 169 + [self addSubview:controller.view]; 170 + #if !TARGET_OS_TV 171 + _controller.interactivePopGestureRecognizer.delegate = self; 172 + + #if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) 173 + + if (@available(iOS 26, *)) { 174 + + _controller.interactiveContentPopGestureRecognizer.delegate = self; 175 + + } 176 + +#endif // Check for iOS >= 26.0 177 + #endif 178 + [controller didMoveToParentViewController:parentView.reactViewController]; 179 + // On iOS pre 12 we observed that `willShowViewController` delegate method does not always 180 + @@ -943,7 +884,7 @@ - (void)dismissOnReload 181 + // when preventing the native dismiss with back button, we have to return the animator. 182 + // Also, we need to return the animator when full width swiping even if the animation is not custom, 183 + // otherwise the screen will be just popped immediately due to no animation 184 + - ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwiping || 185 + + ((operation == UINavigationControllerOperationPop && shouldCancelDismiss) || _isFullWidthSwipingWithPanGesture || 186 + [RNSScreenStackAnimator isCustomAnimation:screen.stackAnimation] || _customAnimation)) { 187 + return [[RNSScreenStackAnimator alloc] initWithOperation:operation]; 188 + } 189 + @@ -967,23 +908,39 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 190 + } 191 + RNSScreenView *topScreen = _reactSubviews.lastObject; 192 + 193 + + BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom = 194 + + topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]; 195 + + 196 + #if TARGET_OS_TV || TARGET_OS_VISION 197 + [self cancelTouchesInParent]; 198 + return YES; 199 + #else 200 + - // RNSPanGestureRecognizer will receive events iff topScreen.fullScreenSwipeEnabled == YES; 201 + - // Events are filtered in gestureRecognizer:shouldReceivePressOrTouchEvent: method 202 + if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) { 203 + - if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) { 204 + - _isFullWidthSwiping = YES; 205 + - [self cancelTouchesInParent]; 206 + - return YES; 207 + + // On iOS < 26, we have a custom full screen swipe recognizer that functions similarily 208 + + // to interactiveContentPopGestureRecognizer introduced in iOS 26. 209 + + // On iOS >= 26, we want to use the native one, but we are unable to handle custom animations 210 + + // with native interactiveContentPopGestureRecognizer, so we have to fallback to the old implementation. 211 + + // In this case, the old one should behave as close as the new native one, having only the difference 212 + + // in animation, and without any customization that is exclusive for it (e.g. gestureResponseDistance). 213 + + if (@available(iOS 26, *)) { 214 + + if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) { 215 + + _isFullWidthSwipingWithPanGesture = YES; 216 + + [self cancelTouchesInParent]; 217 + + return YES; 218 + + } 219 + + return NO; 220 + + } else { 221 + + if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) { 222 + + _isFullWidthSwipingWithPanGesture = YES; 223 + + [self cancelTouchesInParent]; 224 + + return YES; 225 + + } 226 + + return NO; 227 + } 228 + - return NO; 229 + } 230 + 231 + // Now we're dealing with RNSScreenEdgeGestureRecognizer (or _UIParallaxTransitionPanGestureRecognizer) 232 + - if (topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]) { 233 + + if (customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) { 234 + if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) { 235 + UIRectEdge edges = ((RNSScreenEdgeGestureRecognizer *)gestureRecognizer).edges; 236 + BOOL isRTL = _controller.view.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft; 237 + @@ -1028,7 +985,9 @@ - (void)setupGestureHandlers 238 + rightEdgeSwipeGestureRecognizer.delegate = self; 239 + [self addGestureRecognizer:rightEdgeSwipeGestureRecognizer]; 240 + 241 + - // gesture recognizer for full width swipe gesture 242 + + // Starting from iOS 26, RNSPanGestureRecognizer has been mostly replaced by native 243 + + // interactiveContentPopGestureRecognizer. It still needs to handle custom dismiss animations, 244 + + // which we are not able to handle with the latter. 245 + RNSPanGestureRecognizer *panRecognizer = [[RNSPanGestureRecognizer alloc] initWithTarget:self 246 + action:@selector(handleSwipe:)]; 247 + panRecognizer.delegate = self; 248 + @@ -1091,7 +1050,7 @@ - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer 249 + [_interactionController cancelInteractiveTransition]; 250 + } 251 + _interactionController = nil; 252 + - _isFullWidthSwiping = NO; 253 + + _isFullWidthSwipingWithPanGesture = NO; 254 + } 255 + default: { 256 + break; 257 + @@ -1225,14 +1184,6 @@ - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognize 258 + // Be careful when adding another type of gesture recognizer. 259 + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePressOrTouchEvent:(NSObject *)event 260 + { 261 + - if (@available(iOS 26, *)) { 262 + - // in iOS 26, you can swipe to pop screen before the previous one finished transitioning; 263 + - // this prevents from registering the second gesture 264 + - if ([self isTransitionInProgress]) { 265 + - return NO; 266 + - } 267 + - } 268 + - 269 + RNSScreenView *topScreen = _reactSubviews.lastObject; 270 + 271 + for (RNSScreenView *s in _reactSubviews.reverseObjectEnumerator) { 272 + @@ -1249,10 +1200,30 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive 273 + return NO; 274 + } 275 + 276 + + BOOL customAnimationOnSwipePropSetAndSelectedAnimationIsCustom = 277 + + topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]; 278 + +#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) 279 + + if (@available(iOS 26, *)) { 280 + + // On iOS 26, fullScreenSwipeEnabled takes no effect, and depending on whether custom animations are on, 281 + + // we select either interactiveContentPopGestureRecognizer or RNSPanGestureRecognizer 282 + + if (([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] && 283 + + !customAnimationOnSwipePropSetAndSelectedAnimationIsCustom) || 284 + + (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer && 285 + + customAnimationOnSwipePropSetAndSelectedAnimationIsCustom)) { 286 + + return NO; 287 + + } 288 + + } else { 289 + + // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled. 290 + + if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) { 291 + + return topScreen.fullScreenSwipeEnabled; 292 + + } 293 + + } 294 + +#else // check for iOS >= 26 295 + // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled. 296 + if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) { 297 + return topScreen.fullScreenSwipeEnabled; 298 + } 299 + +#endif // check for iOS >= 26 300 + 301 + // RNSScreenEdgeGestureRecognizer || _UIParallaxTransitionPanGestureRecognizer 302 + return YES; 303 + @@ -1268,15 +1239,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive 304 + return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:touch]; 305 + } 306 + 307 + -- (BOOL)isTransitionInProgress 308 + -{ 309 + - if (_controller.transitionCoordinator != nil) { 310 + - return YES; 311 + - } 312 + - 313 + - return NO; 314 + -} 315 + - 316 + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 317 + shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 318 + { 319 + @@ -1289,7 +1251,6 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 320 + if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) { 321 + return NO; 322 + } 323 + - 324 + return YES; 325 + } 326 + return NO;