Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/internals/mobile-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ an over-the-air JavaScript update alone is insufficient. The Android view manage
implements the generated identifier setter as a no-op because this behavior is
specific to iOS 26 and later.

On iOS 26 the full-screen back swipe is UIKit's `interactiveContentPopGestureRecognizer`,
and react-native-screens makes it wait for any horizontally scrollable ScrollView to
fail first. Upstream applies that to every horizontal ScrollView regardless of
position, so a back swipe on a code block or table that is already at its leading
edge only bounces. The patch narrows the rule to ScrollViews with content still
hidden to the left; at the leading edge they yield to the pop gesture like plain
text does.

After changing a dependency patch, refresh CocoaPods before rebuilding an
existing iOS project. pnpm installs each patch hash in a different directory;
an old Pods project can keep compiling the previous directory even though Metro
Expand Down
49 changes: 49 additions & 0 deletions patches/react-native-screens@4.26.2.patch
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,55 @@ index 1c844846a5c66e31cfa530ca462774d2fb6bbea1..a60b96b44e85d3f4b29dc0d190b95ab6
[self updateViewControllerIfNeeded];

if (needsNavigationControllerLayout) {
diff --git a/ios/RNSScreenStack.mm b/ios/RNSScreenStack.mm
--- a/ios/RNSScreenStack.mm
+++ b/ios/RNSScreenStack.mm
@@ -1074,6 +1074,15 @@ RNS_IGNORE_SUPER_CALL_END
return scrollView.contentSize.width > scrollView.frame.size.width;
}

+// Whether a horizontally scrollable ScrollView is resting at its leading edge, so a
+// pan in the back-gesture direction has nothing left to scroll. Checked in the
+// ScrollView's own coordinate space: React Native mirrors the view for RTL layout,
+// which keeps the leading edge at the minimum content offset in both directions.
+- (BOOL)scrollViewIsAtLeadingEdge:(UIScrollView *)scrollView
+{
+ return scrollView.contentOffset.x <= -scrollView.adjustedContentInset.left + 1.0;
+}
+
// Custom method for compatibility with iOS < 13.4
// RNSScreenStackView is a UIGestureRecognizerDelegate for three types of gesture recognizers:
// RNSPanGestureRecognizer, RNSScreenEdgeGestureRecognizer, _UIParallaxTransitionPanGestureRecognizer
@@ -1167,9 +1176,11 @@ RNS_IGNORE_SUPER_CALL_END
if (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
[self isScrollViewPanGestureRecognizer:otherGestureRecognizer]) {
// ScrollView should take precedence when scrolling horizontally (it should be required to fail).
- // However, if it does not allow for horizontal scrolling, there should be no such restriction,
+ // However, if it does not allow for horizontal scrolling, or is already resting at its leading
+ // edge so a back swipe has nothing left to scroll, there should be no such restriction,
// and swiping horizontally should dismiss the screen.
- return [self scrollViewHasHorizontalPart:static_cast<UIScrollView *>(otherGestureRecognizer.view)];
+ UIScrollView *scrollView = static_cast<UIScrollView *>(otherGestureRecognizer.view);
+ return [self scrollViewHasHorizontalPart:scrollView] && ![self scrollViewIsAtLeadingEdge:scrollView];
}
}

@@ -1189,6 +1200,15 @@ RNS_IGNORE_SUPER_CALL_END
// gestureRecognizer:shouldRequireFailureOfGestureRecognizer
isEdgeSwipeGestureRecognizer =
isEdgeSwipeGestureRecognizer && gestureRecognizer != _controller.interactiveContentPopGestureRecognizer;
+
+ // The exception is a horizontal ScrollView resting at its leading edge: its pan recognizer would
+ // still begin (and merely bounce) on a back swipe, so it must yield to the pop gesture the same
+ // way plain content does.
+ if (gestureRecognizer == _controller.interactiveContentPopGestureRecognizer &&
+ [self isScrollViewPanGestureRecognizer:otherGestureRecognizer]) {
+ UIScrollView *scrollView = static_cast<UIScrollView *>(otherGestureRecognizer.view);
+ return [self scrollViewHasHorizontalPart:scrollView] && [self scrollViewIsAtLeadingEdge:scrollView];
+ }
}
#endif // check for iOS >= 26

diff --git a/ios/RNSScreenStackHeaderSubview.mm b/ios/RNSScreenStackHeaderSubview.mm
index add33c4807e038dcd846f6c72b2fc472ded02809..489afa5fa2da09c1ec6986d1832b58bb595396ae 100644
--- a/ios/RNSScreenStackHeaderSubview.mm
Expand Down
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading