+326
patches/react-native-screens+4.16.0.patch
+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;