Skip to content

Fix: Navigation gesture gestureResponseDistance failure #1016

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ For advanced usage please take a look into our [example project](https://github.
| `offscreenPageLimit: number` | Set the number of pages that should be retained to either side of the currently visible page(s). Pages beyond this limit will be recreated from the adapter when needed. Defaults to RecyclerView's caching strategy. The given value must either be larger than 0. | Android |
| `overdrag: boolean` | Allows for overscrolling after reaching the end or very beginning or pages. Defaults to `false` | iOS |
| `layoutDirection: ('ltr' / 'rtl' / 'locale')` | Specifies layout direction. Use `ltr` or `rtl` to set explicitly or `locale` to deduce from the default language script of a locale. Defaults to `locale` | both |
| `allowNavFullscreenGesture: boolean` | Allows navigation swipe-back gestures to pass through when PagerView is at index 0. If `overdrag` is set to `true`, it will have higher priority. Defaults to `false` | iOS |

| Method | Description | Platform |
| ------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
}
}

@ReactProp(name = "allowNavFullscreenGesture")
override fun setAllowNavFullscreenGesture(view: NestedScrollableHost?, value: Boolean) {
return
}

@ReactProp(name = "overdrag")
override fun setOverdrag(view: NestedScrollableHost?, value: Boolean) {
return
Expand Down
52 changes: 51 additions & 1 deletion ios/RNCPagerViewComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

using namespace facebook::react;

@interface RNCPagerViewComponentView () <RCTRNCViewPagerViewProtocol, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate>
@interface RNCPagerViewComponentView () <RCTRNCViewPagerViewProtocol, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate>

@property(nonatomic, strong) UIPageViewController *nativePageViewController;
@property(nonatomic, strong) NSMutableArray<UIViewController *> *nativeChildrenViewControllers;
Expand All @@ -28,6 +28,9 @@ @implementation RNCPagerViewComponentView {
NSInteger _destinationIndex;
BOOL _overdrag;
NSString *_layoutDirection;
BOOL _isDragging;
BOOL _allowNavFullscreenGesture;
UIPanGestureRecognizer *_navGestureRecognizer;
}

// Needed because of this: https://github.com/facebook/react-native/pull/37274
Expand Down Expand Up @@ -76,6 +79,9 @@ - (instancetype)initWithFrame:(CGRect)frame
_destinationIndex = -1;
_layoutDirection = @"ltr";
_overdrag = NO;
_isDragging = NO;
_allowNavFullscreenGesture = NO;
_navGestureRecognizer = nil;
}

return self;
Expand All @@ -88,6 +94,27 @@ - (void)willMoveToSuperview:(UIView *)newSuperview {
}
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (!_allowNavFullscreenGesture) {
return NO;
}

if (otherGestureRecognizer == self->scrollView.panGestureRecognizer) {
UIPanGestureRecognizer* p = (UIPanGestureRecognizer*) gestureRecognizer;
CGPoint velocity = [p velocityInView:self];
BOOL shouldPagerGestureFail = !_overdrag && !_isDragging && _currentIndex == 0 && velocity.x > 0;
if (shouldPagerGestureFail) {
self->scrollView.panGestureRecognizer.enabled = false;
return NO;
} else {
self->scrollView.panGestureRecognizer.enabled = self->scrollView.scrollEnabled;
}
} else {
self->scrollView.panGestureRecognizer.enabled = self->scrollView.scrollEnabled;
}

return YES;
}

#pragma mark - React API

Expand Down Expand Up @@ -126,6 +153,11 @@ -(void)prepareForRecycle {
[super prepareForRecycle];
_nativePageViewController = nil;
_currentIndex = -1;
_allowNavFullscreenGesture = NO;
if (_navGestureRecognizer) {
[self removeGestureRecognizer:_navGestureRecognizer];
_navGestureRecognizer = nil;
}
}

- (void)shouldDismissKeyboard:(RNCViewPagerKeyboardDismissMode)dismissKeyboard {
Expand Down Expand Up @@ -173,6 +205,22 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(cons
_overdrag = newScreenProps.overdrag;
}

if (newScreenProps.allowNavFullscreenGesture != _allowNavFullscreenGesture) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check previous screen properties in here. Same as other props

_allowNavFullscreenGesture = newScreenProps.allowNavFullscreenGesture;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also don't need to store this as it's stored in _props.allowNavFullscreenGesture for us.

Copy link
Author

@kazhoang kazhoang Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@okwasniewski
I’m encountering issues with oldScreenProps.allowNavFullscreenGesture due to the recycling process while re-enter the pager-view. (It keeps the old properties)
This is why I’m using an additional local property to control it
(as otherwise, it skips attaching the navGestureRecognizer since it’s already been cleaned up)

screenshot_1754629457


if (_allowNavFullscreenGesture) {
_navGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
_navGestureRecognizer.delegate = self;
[self addGestureRecognizer:_navGestureRecognizer];
_isDragging = NO;
} else {
if (_navGestureRecognizer) {
[self removeGestureRecognizer:_navGestureRecognizer];
_navGestureRecognizer = nil;
}
}
}

[super updateProps:props oldProps:oldProps];
}

Expand Down Expand Up @@ -264,6 +312,7 @@ - (UIViewController *)currentlyDisplayed {
#pragma mark - UIScrollViewDelegate

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_isDragging = YES;
const auto eventEmitter = [self pagerEventEmitter];
eventEmitter->onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState = RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Dragging });
}
Expand All @@ -290,6 +339,7 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
_isDragging = NO;
const auto eventEmitter = [self pagerEventEmitter];
eventEmitter->onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState = RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Idle });
}
Expand Down
1 change: 1 addition & 0 deletions src/PagerViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface NativeProps extends ViewProps {
overScrollMode?: WithDefault<'auto' | 'always' | 'never', 'auto'>;
overdrag?: WithDefault<boolean, false>;
keyboardDismissMode?: WithDefault<'none' | 'on-drag', 'none'>;
allowNavFullscreenGesture?: WithDefault<boolean, false>;
onPageScroll?: DirectEventHandler<OnPageScrollEventData>;
onPageSelected?: DirectEventHandler<OnPageSelectedEventData>;
onPageScrollStateChanged?: DirectEventHandler<OnPageScrollStateChangedEventData>;
Expand Down
Loading