diff --git a/README.md b/README.md index 2e15bbf7..a9b42f67 100644 --- a/README.md +++ b/README.md @@ -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 | +| `allowNavigationBackGesture: 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 | | ------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | @@ -242,6 +243,7 @@ const pageScrollHandler = usePageScrollHandler({ ``` ## usePagerView Hook Usage + The `usePagerView` hook is a convenient way to manage the state and control the behavior of the `` component. It provides functions and variables to interact with the pager, such as navigating between pages and enabling/disabling scrolling. Below is an example of how to use the usePager hook: @@ -294,7 +296,8 @@ export function PagerHookExample() { ); } ``` -### How the Example Works: + +### How the Example Works - **Pager View Setup**: The `AnimatedPagerView` component wraps `PagerView` in React Native's animation capabilities. It accepts multiple props from the `usePager` hook, such as `overdragEnabled`, `scrollEnabled`, `onPageScroll`, `onPageSelected`, and others to manage pager behavior. @@ -304,7 +307,6 @@ export function PagerHookExample() { The `usePager` hook makes it easy to handle pagination with dynamic views. This example demonstrates how to set up a simple paginated interface where users can scroll through pages, interact with page elements, and control the pager with external navigation. - ## License MIT diff --git a/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt index 8ec286a7..76b540e0 100644 --- a/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -163,6 +163,11 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa } } + @ReactProp(name = "allowNavigationBackGesture") + override fun setAllowNavigationBackGesture(view: NestedScrollableHost?, value: Boolean) { + return + } + @ReactProp(name = "overdrag") override fun setOverdrag(view: NestedScrollableHost?, value: Boolean) { return diff --git a/ios/RNCPagerViewComponentView.mm b/ios/RNCPagerViewComponentView.mm index c6a150b0..047167ea 100644 --- a/ios/RNCPagerViewComponentView.mm +++ b/ios/RNCPagerViewComponentView.mm @@ -12,7 +12,7 @@ using namespace facebook::react; -@interface RNCPagerViewComponentView () +@interface RNCPagerViewComponentView () @property(nonatomic, strong) UIPageViewController *nativePageViewController; @property(nonatomic, strong) NSMutableArray *nativeChildrenViewControllers; @@ -28,6 +28,8 @@ @implementation RNCPagerViewComponentView { NSInteger _destinationIndex; BOOL _overdrag; NSString *_layoutDirection; + BOOL _allowNavigationBackGesture; + UIPanGestureRecognizer *_navGestureRecognizer; } // Needed because of this: https://github.com/facebook/react-native/pull/37274 @@ -76,6 +78,8 @@ - (instancetype)initWithFrame:(CGRect)frame _destinationIndex = -1; _layoutDirection = @"ltr"; _overdrag = NO; + _allowNavigationBackGesture = NO; + _navGestureRecognizer = nil; } return self; @@ -88,6 +92,27 @@ - (void)willMoveToSuperview:(UIView *)newSuperview { } } +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + if (!_allowNavigationBackGesture) { + return NO; + } + + if (otherGestureRecognizer == self->scrollView.panGestureRecognizer) { + UIPanGestureRecognizer* panGestureRecognizer = (UIPanGestureRecognizer*) gestureRecognizer; + CGPoint velocity = [panGestureRecognizer velocityInView:self]; + BOOL shouldPagerGestureFail = !_overdrag && !scrollView.isDecelerating && _currentIndex == 0 && velocity.x > 0; + if (shouldPagerGestureFail) { + self->scrollView.panGestureRecognizer.enabled = NO; + return NO; + } else { + self->scrollView.panGestureRecognizer.enabled = self->scrollView.scrollEnabled; + } + } else { + self->scrollView.panGestureRecognizer.enabled = self->scrollView.scrollEnabled; + } + + return YES; +} #pragma mark - React API @@ -121,11 +146,12 @@ -(void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics]; } - -(void)prepareForRecycle { [super prepareForRecycle]; _nativePageViewController = nil; _currentIndex = -1; + _allowNavigationBackGesture = NO; + [self cleanupGestureRecognizer]; } - (void)shouldDismissKeyboard:(RNCViewPagerKeyboardDismissMode)dismissKeyboard { @@ -173,6 +199,18 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(cons _overdrag = newScreenProps.overdrag; } + if (newScreenProps.allowNavigationBackGesture != _allowNavigationBackGesture) { + _allowNavigationBackGesture = newScreenProps.allowNavigationBackGesture; + + if (_allowNavigationBackGesture) { + _navGestureRecognizer = [[UIPanGestureRecognizer alloc] init]; + _navGestureRecognizer.delegate = self; + [self addGestureRecognizer:_navGestureRecognizer]; + } else { + [self cleanupGestureRecognizer]; + } + } + [super updateProps:props oldProps:oldProps]; } @@ -407,6 +445,13 @@ - (BOOL)isLtrLayout { return [_layoutDirection isEqualToString: @"ltr"]; } +- (void)cleanupGestureRecognizer { + if (_navGestureRecognizer) { + [self removeGestureRecognizer:_navGestureRecognizer]; + _navGestureRecognizer = nil; + } +} + - (std::shared_ptr)pagerEventEmitter { if (!_eventEmitter) { diff --git a/src/PagerViewNativeComponent.ts b/src/PagerViewNativeComponent.ts index 19d935ec..57c9186b 100644 --- a/src/PagerViewNativeComponent.ts +++ b/src/PagerViewNativeComponent.ts @@ -33,6 +33,7 @@ export interface NativeProps extends ViewProps { overScrollMode?: WithDefault<'auto' | 'always' | 'never', 'auto'>; overdrag?: WithDefault; keyboardDismissMode?: WithDefault<'none' | 'on-drag', 'none'>; + allowNavigationBackGesture?: WithDefault; onPageScroll?: DirectEventHandler; onPageSelected?: DirectEventHandler; onPageScrollStateChanged?: DirectEventHandler;