From e6bfc6e33c9db67dc2ff480a68caeff2bfeedecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Ro=C5=BCniata?= <56474758+krozniata@users.noreply.github.com> Date: Tue, 17 Jan 2023 17:57:10 +0100 Subject: [PATCH 01/16] feat(iOS): change Fabric implementation to UIScrollView (#672) * feat(iOS): change Fabric implementation to UIScrollView * fix: fix offset values in vertical orientation * feat: add initialPage props support * feat: add RTL language support * feat: add pageMargin prop support * fix: fix typescript error * feat: remove React.cloneElement * feat(ios): add getPageOffset method * fix: fix styles in old example * fix: behavior on page remove --- .../RNCViewPagerComponentDescriptor.h | 12 + .../RNCViewPager/RNCViewPagerShadowNode.cpp | 35 ++ .../RNCViewPager/RNCViewPagerShadowNode.h | 31 ++ .../RNCViewPager/RNCViewPagerState.cpp | 11 + .../RNCViewPager/RNCViewPagerState.h | 18 + example/src/NestPagerView.tsx | 17 +- example/src/PaginationDotsExample.tsx | 1 + example/src/utils.ts | 1 + fabricexample/ios/Podfile.lock | 13 +- fabricexample/src/NestPagerView.tsx | 17 +- fabricexample/src/PaginationDotsExample.tsx | 1 + fabricexample/src/utils.ts | 1 + ios/Fabric/RNCPagerViewComponentView.h | 10 +- ios/Fabric/RNCPagerViewComponentView.mm | 400 ++++++------------ react-native-pager-view.podspec | 7 + src/PagerView.tsx | 18 +- src/utils.tsx | 25 +- 17 files changed, 302 insertions(+), 316 deletions(-) create mode 100644 common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerComponentDescriptor.h create mode 100644 common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.cpp create mode 100644 common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.h create mode 100644 common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.cpp create mode 100644 common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.h diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerComponentDescriptor.h b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerComponentDescriptor.h new file mode 100644 index 00000000..b5b1b1df --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerComponentDescriptor.h @@ -0,0 +1,12 @@ +#pragma once + +#include "RNCViewPagerShadowNode.h" +#include + +namespace facebook { +namespace react { + +using RNCViewPagerComponentDescriptor = ConcreteComponentDescriptor; + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.cpp b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.cpp new file mode 100644 index 00000000..a81506ac --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.cpp @@ -0,0 +1,35 @@ +#include "RNCViewPagerShadowNode.h" + +#include +#include + +namespace facebook { +namespace react { + +const char RNCViewPagerComponentName[] = "RNCViewPager"; + +void RNCViewPagerShadowNode::updateStateIfNeeded() { + ensureUnsealed(); + + auto contentBoundingRect = Rect{}; + for (const auto &childNode : getLayoutableChildNodes()) { + contentBoundingRect.unionInPlace(childNode->getLayoutMetrics().frame); + } + + auto state = getStateData(); + + if (state.contentBoundingRect != contentBoundingRect) { + state.contentBoundingRect = contentBoundingRect; + setStateData(std::move(state)); + } +} + +#pragma mark - LayoutableShadowNode + +void RNCViewPagerShadowNode::layout(LayoutContext layoutContext) { + ConcreteViewShadowNode::layout(layoutContext); + updateStateIfNeeded(); +} + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.h b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.h new file mode 100644 index 00000000..1aaece35 --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerShadowNode.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char RNCViewPagerComponentName[]; + +class RNCViewPagerShadowNode final : public ConcreteViewShadowNode< + RNCViewPagerComponentName, + RNCViewPagerProps, + RNCViewPagerEventEmitter, + RNCViewPagerState> { +public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + +#pragma mark - LayoutableShadowNode + + void layout(LayoutContext layoutContext) override; + +private: + void updateStateIfNeeded(); +}; + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.cpp b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.cpp new file mode 100644 index 00000000..a99afaff --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.cpp @@ -0,0 +1,11 @@ +#include "RNCViewPagerState.h" + +namespace facebook { +namespace react { + +Size RNCViewPagerState::getContentSize() const { + return contentBoundingRect.size; +} + +} +} diff --git a/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.h b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.h new file mode 100644 index 00000000..ac43dadf --- /dev/null +++ b/common/cpp/react/renderer/components/RNCViewPager/RNCViewPagerState.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace facebook { +namespace react { + +class RNCViewPagerState final { +public: + Point contentOffset; + Rect contentBoundingRect; + + Size getContentSize() const; + +}; + +} +} diff --git a/example/src/NestPagerView.tsx b/example/src/NestPagerView.tsx index f455c06c..5598b1c4 100644 --- a/example/src/NestPagerView.tsx +++ b/example/src/NestPagerView.tsx @@ -20,12 +20,12 @@ export function NestPagerView() { > - + There has two Nest PagerView with horizontal and vertical. @@ -39,7 +39,7 @@ export function NestPagerView() { > @@ -47,7 +47,7 @@ export function NestPagerView() { @@ -64,7 +64,7 @@ export function NestPagerView() { > @@ -72,7 +72,7 @@ export function NestPagerView() { @@ -82,7 +82,7 @@ export function NestPagerView() { @@ -105,5 +105,8 @@ const styles = StyleSheet.create({ PagerView: { flex: 1, }, + page: { + flex: 1, + }, title: { fontSize: 22, paddingVertical: 10 }, }); diff --git a/example/src/PaginationDotsExample.tsx b/example/src/PaginationDotsExample.tsx index dd16f6e6..82899286 100644 --- a/example/src/PaginationDotsExample.tsx +++ b/example/src/PaginationDotsExample.tsx @@ -166,6 +166,7 @@ const styles = StyleSheet.create({ }, progressContainer: { flex: 0.1, backgroundColor: '#63a4ff' }, center: { + flex: 1, justifyContent: 'center', alignItems: 'center', alignContent: 'center', diff --git a/example/src/utils.ts b/example/src/utils.ts index 8b178504..a3de68b3 100644 --- a/example/src/utils.ts +++ b/example/src/utils.ts @@ -23,6 +23,7 @@ export const createPage = (key: number): CreatePage => { return { key: key, style: { + flex: 1, backgroundColor: BGCOLOR[key % BGCOLOR.length], alignItems: 'center', padding: 20, diff --git a/fabricexample/ios/Podfile.lock b/fabricexample/ios/Podfile.lock index 16fd5b18..8db0c2c0 100644 --- a/fabricexample/ios/Podfile.lock +++ b/fabricexample/ios/Podfile.lock @@ -625,7 +625,16 @@ PODS: - React-jsinspector (0.70.5) - React-logger (0.70.5): - glog - - react-native-pager-view (6.1.1): + - react-native-pager-view (6.1.2): + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - react-native-pager-view/common (= 6.1.2) + - React-RCTFabric + - ReactCommon/turbomodule/core + - react-native-pager-view/common (6.1.2): - RCT-Folly - RCTRequired - RCTTypeSafety @@ -1013,7 +1022,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b React-jsinspector: badd81696361249893a80477983e697aab3c1a34 React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c - react-native-pager-view: 22ef94ca5a46cb18e4573ed3e179f4f84d477490 + react-native-pager-view: 991c947924d48f1232a98ba6e6d3466eaf51034d react-native-safe-area-context: 2f75b317784a1a8e224562941e50ecbc932d2097 React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 diff --git a/fabricexample/src/NestPagerView.tsx b/fabricexample/src/NestPagerView.tsx index f455c06c..5598b1c4 100644 --- a/fabricexample/src/NestPagerView.tsx +++ b/fabricexample/src/NestPagerView.tsx @@ -20,12 +20,12 @@ export function NestPagerView() { > - + There has two Nest PagerView with horizontal and vertical. @@ -39,7 +39,7 @@ export function NestPagerView() { > @@ -47,7 +47,7 @@ export function NestPagerView() { @@ -64,7 +64,7 @@ export function NestPagerView() { > @@ -72,7 +72,7 @@ export function NestPagerView() { @@ -82,7 +82,7 @@ export function NestPagerView() { @@ -105,5 +105,8 @@ const styles = StyleSheet.create({ PagerView: { flex: 1, }, + page: { + flex: 1, + }, title: { fontSize: 22, paddingVertical: 10 }, }); diff --git a/fabricexample/src/PaginationDotsExample.tsx b/fabricexample/src/PaginationDotsExample.tsx index dd16f6e6..82899286 100644 --- a/fabricexample/src/PaginationDotsExample.tsx +++ b/fabricexample/src/PaginationDotsExample.tsx @@ -166,6 +166,7 @@ const styles = StyleSheet.create({ }, progressContainer: { flex: 0.1, backgroundColor: '#63a4ff' }, center: { + flex: 1, justifyContent: 'center', alignItems: 'center', alignContent: 'center', diff --git a/fabricexample/src/utils.ts b/fabricexample/src/utils.ts index 8b178504..a3de68b3 100644 --- a/fabricexample/src/utils.ts +++ b/fabricexample/src/utils.ts @@ -23,6 +23,7 @@ export const createPage = (key: number): CreatePage => { return { key: key, style: { + flex: 1, backgroundColor: BGCOLOR[key % BGCOLOR.length], alignItems: 'center', padding: 20, diff --git a/ios/Fabric/RNCPagerViewComponentView.h b/ios/Fabric/RNCPagerViewComponentView.h index de276e0a..6e4187fa 100644 --- a/ios/Fabric/RNCPagerViewComponentView.h +++ b/ios/Fabric/RNCPagerViewComponentView.h @@ -7,14 +7,8 @@ NS_ASSUME_NONNULL_BEGIN -@interface RNCPagerViewComponentView : RCTViewComponentView - -@property(strong, nonatomic, readonly) UIPageViewController *nativePageViewController; -@property(nonatomic, strong) NSMutableArray *nativeChildrenViewControllers; -@property(nonatomic) NSInteger initialPage; -@property(nonatomic) NSInteger currentIndex; -@property(nonatomic) NSInteger destinationIndex; -@property(nonatomic) NSString* layoutDirection; +@interface RNCPagerViewComponentView : RCTViewComponentView + @property(nonatomic) BOOL overdrag; - (void)setPage:(NSInteger)number; diff --git a/ios/Fabric/RNCPagerViewComponentView.mm b/ios/Fabric/RNCPagerViewComponentView.mm index d4fd7a66..9bb1a230 100644 --- a/ios/Fabric/RNCPagerViewComponentView.mm +++ b/ios/Fabric/RNCPagerViewComponentView.mm @@ -2,7 +2,7 @@ #import #import "RNCPagerViewComponentView.h" -#import +#import #import #import #import @@ -15,42 +15,16 @@ using namespace facebook::react; -@interface RNCPagerViewComponentView () +@interface RNCPagerViewComponentView () @end @implementation RNCPagerViewComponentView { - LayoutMetrics _layoutMetrics; - UIScrollView *scrollView; -} - -- (void)initializeNativePageViewController { - const auto &viewProps = *std::static_pointer_cast(_props); - NSDictionary *options = @{ UIPageViewControllerOptionInterPageSpacingKey: @(viewProps.pageMargin) }; - UIPageViewControllerNavigationOrientation orientation = UIPageViewControllerNavigationOrientationHorizontal; - switch (viewProps.orientation) { - case RNCViewPagerOrientation::Horizontal: - orientation = UIPageViewControllerNavigationOrientationHorizontal; - break; - case RNCViewPagerOrientation::Vertical: - orientation = UIPageViewControllerNavigationOrientationVertical; - break; - } - _nativePageViewController = [[UIPageViewController alloc] - initWithTransitionStyle: UIPageViewControllerTransitionStyleScroll - navigationOrientation:orientation - options:options]; - _nativePageViewController.dataSource = self; - _nativePageViewController.delegate = self; - _nativePageViewController.view.frame = self.frame; - self.contentView = _nativePageViewController.view; - - for (UIView *subview in _nativePageViewController.view.subviews) { - if([subview isKindOfClass:UIScrollView.class]){ - ((UIScrollView *)subview).delegate = self; - ((UIScrollView *)subview).delaysContentTouches = NO; - scrollView = (UIScrollView *)subview; - } - } + RNCViewPagerShadowNode::ConcreteState::Shared _state; + UIScrollView *_scrollView; + UIView *_containerView; + + CGSize _contentSize; + NSInteger _initialPage; } - (instancetype)initWithFrame:(CGRect)frame @@ -58,197 +32,120 @@ - (instancetype)initWithFrame:(CGRect)frame if (self = [super initWithFrame:frame]) { static const auto defaultProps = std::make_shared(); _props = defaultProps; - _nativeChildrenViewControllers = [[NSMutableArray alloc] init]; - _currentIndex = -1; - _destinationIndex = -1; - _layoutDirection = @"ltr"; - _overdrag = NO; + _initialPage = -1; + + _scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; + + _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _scrollView.delaysContentTouches = NO; + _scrollView.delegate = self; + _scrollView.pagingEnabled = YES; + _scrollView.showsHorizontalScrollIndicator = NO; + _scrollView.showsVerticalScrollIndicator = NO; + + [self addSubview:_scrollView]; + + _containerView = [[UIView alloc] initWithFrame:CGRectZero]; + + [_scrollView addSubview:_containerView]; } return self; } -- (void)willMoveToSuperview:(UIView *)newSuperview { - if (newSuperview != nil) { - [self initializeNativePageViewController]; - [self goTo:_currentIndex animated:NO]; - } -} - #pragma mark - React API -- (void)mountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { - UIViewController *wrapper = [[UIViewController alloc] initWithView:childComponentView]; - [_nativeChildrenViewControllers insertObject:wrapper atIndex:index]; -} - -- (void)unmountChildComponentView:(UIView *)childComponentView index:(NSInteger)index { - [[_nativeChildrenViewControllers objectAtIndex:index].view removeFromSuperview]; - [_nativeChildrenViewControllers objectAtIndex:index].view = nil; - [_nativeChildrenViewControllers removeObjectAtIndex:index]; - - NSInteger maxPage = _nativeChildrenViewControllers.count - 1; - - if (self.currentIndex >= maxPage) { - [self goTo:maxPage animated:NO]; - } -} - - --(void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics { - [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics]; - self.contentView.frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame()); - _layoutMetrics = layoutMetrics; -} - - --(void)prepareForRecycle { - [super prepareForRecycle]; - - _nativeChildrenViewControllers = [[NSMutableArray alloc] init]; - [_nativePageViewController.view removeFromSuperview]; - _nativePageViewController = nil; - - _currentIndex = -1; -} - -- (void)shouldDismissKeyboard:(RNCViewPagerKeyboardDismissMode)dismissKeyboard { - UIScrollViewKeyboardDismissMode dismissKeyboardMode = UIScrollViewKeyboardDismissModeNone; - switch (dismissKeyboard) { - case RNCViewPagerKeyboardDismissMode::None: - dismissKeyboardMode = UIScrollViewKeyboardDismissModeNone; - break; - case RNCViewPagerKeyboardDismissMode::OnDrag: - dismissKeyboardMode = UIScrollViewKeyboardDismissModeOnDrag; - break; - } - scrollView.keyboardDismissMode = dismissKeyboardMode; -} - - - (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(const facebook::react::Props::Shared &)oldProps{ const auto &oldScreenProps = *std::static_pointer_cast(_props); const auto &newScreenProps = *std::static_pointer_cast(props); - // change index only once - if (_currentIndex == -1) { - _currentIndex = newScreenProps.initialPage; - [self shouldDismissKeyboard: newScreenProps.keyboardDismissMode]; - } - - const auto newLayoutDirectionStr = RCTNSStringFromString(toString(newScreenProps.layoutDirection)); - - - if (self.layoutDirection != newLayoutDirectionStr) { - self.layoutDirection = newLayoutDirectionStr; + if (_scrollView.bounces != newScreenProps.overdrag) { + [_scrollView setBounces: newScreenProps.overdrag]; } - if (oldScreenProps.keyboardDismissMode != newScreenProps.keyboardDismissMode) { - [self shouldDismissKeyboard: newScreenProps.keyboardDismissMode]; + if (_scrollView.scrollEnabled != newScreenProps.scrollEnabled) { + [_scrollView setScrollEnabled:newScreenProps.scrollEnabled]; } - if (newScreenProps.scrollEnabled != scrollView.scrollEnabled) { - scrollView.scrollEnabled = newScreenProps.scrollEnabled; - } - - if (newScreenProps.overdrag != _overdrag) { - _overdrag = newScreenProps.overdrag; - } [super updateProps:props oldProps:oldProps]; } -- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args { - RCTRNCViewPagerHandleCommand(self, commandName, args); -} - -#pragma mark - Internal methods - -- (void)setPage:(NSInteger)index { - [self goTo:index animated:YES]; -} - -- (void)setPageWithoutAnimation:(NSInteger)index { - [self goTo:index animated:NO]; -} - -- (void)disableSwipe { - self.nativePageViewController.view.userInteractionEnabled = NO; -} - -- (void)enableSwipe { - self.nativePageViewController.view.userInteractionEnabled = YES; +- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics + oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics +{ + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + if (layoutMetrics.layoutDirection != oldLayoutMetrics.layoutDirection) { + CGAffineTransform transform = (layoutMetrics.layoutDirection == LayoutDirection::LeftToRight) + ? CGAffineTransformIdentity + : CGAffineTransformMakeScale(-1, 1); + + _containerView.transform = transform; + _scrollView.transform = transform; + } } -- (void)goTo:(NSInteger)index animated:(BOOL)animated { - NSInteger numberOfPages = _nativeChildrenViewControllers.count; +- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState +{ + assert(std::dynamic_pointer_cast(state)); + _state = std::static_pointer_cast(state); + + const auto &props = *std::static_pointer_cast(_props); - [self disableSwipe]; + auto &data = _state->getData(); - _destinationIndex = index; + auto contentOffset = RCTCGPointFromPoint(data.contentOffset); + if (!oldState && !CGPointEqualToPoint(contentOffset, CGPointZero)) { + _scrollView.contentOffset = contentOffset; + } + CGSize contentSize = RCTCGSizeFromSize(data.getContentSize()); - if (numberOfPages == 0 || index < 0 || index > numberOfPages - 1) { + if (CGSizeEqualToSize(_contentSize, contentSize)) { return; } - BOOL isForward = (index > self.currentIndex && [self isLtrLayout]) || (index < self.currentIndex && ![self isLtrLayout]); - UIPageViewControllerNavigationDirection direction = isForward ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse; - - long diff = labs(index - _currentIndex); + _contentSize = contentSize; + _containerView.frame = CGRect{RCTCGPointFromPoint(data.contentBoundingRect.origin), contentSize}; - [self setPagerViewControllers:index - direction:direction - animated:diff == 0 ? NO : animated]; + _scrollView.contentSize = contentSize; -} - -- (void)setPagerViewControllers:(NSInteger)index - direction:(UIPageViewControllerNavigationDirection)direction - animated:(BOOL)animated{ - if (_nativePageViewController == nil) { - [self enableSwipe]; - return; + if (!CGSizeEqualToSize(_scrollView.frame.size, CGSizeZero) && _initialPage == -1) { + [self setPageWithoutAnimation: props.initialPage]; + _initialPage = props.initialPage; } - __weak RNCPagerViewComponentView *weakSelf = self; - [_nativePageViewController setViewControllers:@[[_nativeChildrenViewControllers objectAtIndex:index]] - direction:direction - animated:animated - completion:^(BOOL finished) { - __strong RNCPagerViewComponentView *strongSelf = weakSelf; - [strongSelf enableSwipe]; - if (strongSelf->_eventEmitter != nullptr ) { - const auto strongEventEmitter = *std::dynamic_pointer_cast(strongSelf->_eventEmitter); - int position = (int) index; - strongEventEmitter.onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(position)}); - strongSelf->_currentIndex = index; - } - }]; } +- (void)mountChildComponentView:(UIView *)childComponentView index:(NSInteger)index +{ + [_containerView insertSubview:childComponentView atIndex:index]; +} -- (UIViewController *)nextControllerForController:(UIViewController *)controller - inDirection:(UIPageViewControllerNavigationDirection)direction { - NSUInteger numberOfPages = _nativeChildrenViewControllers.count; - NSInteger index = [_nativeChildrenViewControllers indexOfObject:controller]; - - if (index == NSNotFound) { - return nil; - } - - direction == UIPageViewControllerNavigationDirectionForward ? index++ : index--; +- (void)unmountChildComponentView:(UIView *)childComponentView index:(NSInteger)index +{ + [childComponentView removeFromSuperview]; - if (index < 0 || (index > (numberOfPages - 1))) { - return nil; + NSInteger numberOfPages = _containerView.subviews.count; + + if ([self getCurrentPage] >= numberOfPages - 1) { + [self setPageWithoutAnimation: numberOfPages - 1]; } - - return [_nativeChildrenViewControllers objectAtIndex:index]; } -- (UIViewController *)currentlyDisplayed { - return _nativePageViewController.viewControllers.firstObject; +- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args { + RCTRNCViewPagerHandleCommand(self, commandName, args); +} + +- (void)prepareForRecycle +{ + _state.reset(); + [_scrollView setContentOffset:CGPointZero]; + + _initialPage = -1; + + [super prepareForRecycle]; } #pragma mark - UIScrollViewDelegate @@ -260,130 +157,75 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { - if (!_overdrag) { - NSInteger maxIndex = _nativeChildrenViewControllers.count - 1; - BOOL isFirstPage = [self isLtrLayout] ? _currentIndex == 0 : _currentIndex == maxIndex; - BOOL isLastPage = [self isLtrLayout] ? _currentIndex == maxIndex : _currentIndex == 0; - CGFloat contentOffset = [self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y; - CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height; - - if ((isFirstPage && contentOffset <= topBound) || (isLastPage && contentOffset >= topBound)) { - CGPoint croppedOffset = [self isHorizontal] ? CGPointMake(topBound, 0) : CGPointMake(0, topBound); - *targetContentOffset = croppedOffset; - } - } - const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); strongEventEmitter.onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState = RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Settling }); } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + int position = [self getCurrentPage]; + const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); + strongEventEmitter.onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState = RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Idle }); + + strongEventEmitter.onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(position)}); } -- (BOOL)isHorizontal { - return _nativePageViewController.navigationOrientation == UIPageViewControllerNavigationOrientationHorizontal; -} - -- (BOOL)isLtrLayout { - return [_layoutDirection isEqualToString: @"ltr"]; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - CGPoint point = scrollView.contentOffset; - - float offset = 0; - - if (self.isHorizontal) { - if (scrollView.frame.size.width != 0) { - offset = (point.x - scrollView.frame.size.width)/scrollView.frame.size.width; - } - } else { - if (scrollView.frame.size.height != 0) { - offset = (point.y - scrollView.frame.size.height)/scrollView.frame.size.height; - } - } - - float absoluteOffset = fabs(offset); +//Handles sending onPageSelected event on setPage method completion +-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + int position = [self getCurrentPage]; - NSInteger position = self.currentIndex; - - BOOL isAnimatingBackwards = offset<0; + const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); - if (scrollView.isDragging) { - _destinationIndex = isAnimatingBackwards ? _currentIndex - 1 : _currentIndex + 1; - } + strongEventEmitter.onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(position)}); +} + +-(void)scrollViewDidScroll:(UIScrollView *)scrollView { + int position = [self getCurrentPage]; - if (isAnimatingBackwards) { - position = _destinationIndex; - absoluteOffset = fmax(0, 1 - absoluteOffset); - } + double offset = [self isHorizontal] ? (scrollView.contentOffset.x - (scrollView.frame.size.width * position))/scrollView.frame.size.width : (scrollView.contentOffset.y - (scrollView.frame.size.height * position))/scrollView.frame.size.height; - if (!_overdrag) { - NSInteger maxIndex = _nativeChildrenViewControllers.count - 1; - NSInteger firstPageIndex = [self isLtrLayout] ? 0 : maxIndex; - NSInteger lastPageIndex = [self isLtrLayout] ? maxIndex : 0; - BOOL isFirstPage = _currentIndex == firstPageIndex; - BOOL isLastPage = _currentIndex == lastPageIndex; - CGFloat contentOffset =[self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y; - CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height; - - if ((isFirstPage && contentOffset <= topBound) || (isLastPage && contentOffset >= topBound)) { - CGPoint croppedOffset = [self isHorizontal] ? CGPointMake(topBound, 0) : CGPointMake(0, topBound); - scrollView.contentOffset = croppedOffset; - absoluteOffset=0; - position = isLastPage ? lastPageIndex : firstPageIndex; - } - } + const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); - float interpolatedOffset = absoluteOffset * labs(_destinationIndex - _currentIndex); + strongEventEmitter.onPageScroll(RNCViewPagerEventEmitter::OnPageScroll{.position = static_cast(position), .offset = offset}); - const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); - int eventPosition = (int) position; - strongEventEmitter.onPageScroll(RNCViewPagerEventEmitter::OnPageScroll{.position = static_cast(eventPosition), .offset = interpolatedOffset}); - //This is temporary workaround to allow animations based on onPageScroll event //until Fabric implements proper NativeAnimationDriver RCTBridge *bridge = [RCTBridge currentBridge]; if (bridge) { - [bridge.eventDispatcher sendEvent:[[RCTOnPageScrollEvent alloc] initWithReactTag:[NSNumber numberWithInt:self.tag] position:@(position) offset:@(interpolatedOffset)]]; + [bridge.eventDispatcher sendEvent:[[RCTOnPageScrollEvent alloc] initWithReactTag:[NSNumber numberWithInt:self.tag] position:@(position) offset:@(offset)]]; } - } +#pragma mark - Internal methods -#pragma mark - UIPageViewControllerDelegate - -- (void)pageViewController:(UIPageViewController *)pageViewController - didFinishAnimating:(BOOL)finished - previousViewControllers:(nonnull NSArray *)previousViewControllers - transitionCompleted:(BOOL)completed { - if (completed) { - UIViewController* currentVC = [self currentlyDisplayed]; - NSUInteger currentIndex = [_nativeChildrenViewControllers indexOfObject:currentVC]; - _currentIndex = currentIndex; - int position = (int) currentIndex; - const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); - strongEventEmitter.onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(position)}); - strongEventEmitter.onPageScroll(RNCViewPagerEventEmitter::OnPageScroll{.position = static_cast(position), .offset = 0.0}); - } +-(bool)isHorizontal { + return _scrollView.contentSize.width > _scrollView.contentSize.height; +} + +-(int)getCurrentPage { + return [self isHorizontal] ? _scrollView.contentOffset.x / _scrollView.frame.size.width : _scrollView.contentOffset.y / _scrollView.frame.size.height; } -#pragma mark - UIPageViewControllerDataSource +-(CGPoint)getPageOffset:(NSInteger)pageIndex { + return [self isHorizontal] ? CGPointMake(_scrollView.frame.size.width * pageIndex, 0) : CGPointMake(0, _scrollView.frame.size.height * pageIndex); +} -- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController - viewControllerAfterViewController:(UIViewController *)viewController { +- (void)setPage:(NSInteger)index { + CGPoint targetOffset = [self getPageOffset:index]; - UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse; - return [self nextControllerForController:viewController inDirection:direction]; + [_scrollView setContentOffset:targetOffset animated:YES]; } -- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController - viewControllerBeforeViewController:(UIViewController *)viewController { - UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionReverse : UIPageViewControllerNavigationDirectionForward; - return [self nextControllerForController:viewController inDirection:direction]; +- (void)setPageWithoutAnimation:(NSInteger)index { + CGPoint targetOffset = [self getPageOffset:index]; + + [_scrollView setContentOffset:targetOffset animated:NO]; + + const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); + + strongEventEmitter.onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(index)}); } #pragma mark - RCTComponentViewProtocol diff --git a/react-native-pager-view.podspec b/react-native-pager-view.podspec index 57138e9a..e331800f 100644 --- a/react-native-pager-view.podspec +++ b/react-native-pager-view.podspec @@ -31,6 +31,13 @@ Pod::Spec.new do |s| s.dependency "RCTRequired" s.dependency "RCTTypeSafety" s.dependency "ReactCommon/turbomodule/core" + + s.subspec "common" do |ss| + ss.source_files = "common/cpp/**/*.{cpp,h}" + ss.header_dir = "RNCViewPager" + ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" } + end + s.dependency "React-RCTFabric" end end diff --git a/src/PagerView.tsx b/src/PagerView.tsx index 9122b992..785c9791 100644 --- a/src/PagerView.tsx +++ b/src/PagerView.tsx @@ -149,13 +149,27 @@ export class PagerView extends React.Component { ref={(ref) => { this.pagerView = ref; }} - style={this.props.style} + style={[ + this.props.style, + this.props.pageMargin + ? { + marginHorizontal: -this.props.pageMargin / 2, + } + : null, + { + flexDirection: + this.props.orientation === 'vertical' ? 'column' : 'row', + }, + ]} layoutDirection={this.deducedLayoutDirection} onPageScroll={this._onPageScroll} onPageScrollStateChanged={this._onPageScrollStateChanged} onPageSelected={this._onPageSelected} onMoveShouldSetResponderCapture={this._onMoveShouldSetResponderCapture} - children={childrenWithOverriddenStyle(this.props.children)} + children={childrenWithOverriddenStyle( + this.props.children, + this.props.pageMargin + )} /> ); } diff --git a/src/utils.tsx b/src/utils.tsx index 6b5fbffe..0167992a 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -1,18 +1,21 @@ import React, { Children, ReactNode } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; -export const childrenWithOverriddenStyle = (children?: ReactNode) => { +export const childrenWithOverriddenStyle = ( + children?: ReactNode, + pageMargin = 0 +) => { return Children.map(children, (child) => { - const element = child as React.ReactElement; return ( - // Add a wrapper to ensure layout is calculated correctly - - {/* @ts-ignore */} - {React.cloneElement(element, { - ...element.props, - // Override styles so that each page will fill the parent. - style: [element.props.style, StyleSheet.absoluteFill], - })} + + {child} ); }); From 0af891420d5c75fadef81919705ea56ca18031d9 Mon Sep 17 00:00:00 2001 From: troZee <12766071+troZee@users.noreply.github.com> Date: Tue, 17 Jan 2023 18:02:39 +0100 Subject: [PATCH 02/16] chore: add GH actions (#680) Co-authored-by: Piotr Trocki --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f7494768..202500b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,9 +4,11 @@ on: pull_request: branches: - master + - next push: branches: - master + - next concurrency: group: ${{ github.ref }}-js From bdf7de445c894370a8d1c2e786c00420450a5b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwa=C5=9Bniewski?= Date: Wed, 25 Jan 2023 20:38:01 +0100 Subject: [PATCH 03/16] feat(iOS): rewrite old arch to use UIScrollView (#681) * feat: rewrite old arch to use UIScrollView * feat: update example styles * fix: sending event on scrollViewDidEndDecelerating * feat: properly calculate width using orientation * fix: change way of disabing scroll * feat: rename to RNCPagerView * fix: removing last page * fix: remove unused properties, set animated --- example/ios/Podfile.lock | 4 +- ios/{ReactNativePageView.h => RNCPagerView.h} | 12 +- ios/RNCPagerView.m | 188 +++++++ ...ewPagerManager.h => RNCPagerViewManager.h} | 4 +- ...ewPagerManager.m => RNCPagerViewManager.m} | 22 +- ios/ReactNativePageView.m | 464 ------------------ 6 files changed, 205 insertions(+), 489 deletions(-) rename ios/{ReactNativePageView.h => RNCPagerView.h} (60%) create mode 100644 ios/RNCPagerView.m rename ios/{ReactViewPagerManager.h => RNCPagerViewManager.h} (67%) rename ios/{ReactViewPagerManager.m => RNCPagerViewManager.m} (72%) delete mode 100644 ios/ReactNativePageView.m diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 798085e7..e75983d6 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -302,7 +302,7 @@ PODS: - React-jsinspector (0.70.5) - React-logger (0.70.5): - glog - - react-native-pager-view (6.1.1): + - react-native-pager-view (6.1.2): - React-Core - react-native-safe-area-context (3.4.1): - React-Core @@ -617,7 +617,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b React-jsinspector: badd81696361249893a80477983e697aab3c1a34 React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c - react-native-pager-view: 3c66c4e2f3ab423643d07b2c7041f8ac48395f72 + react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43 react-native-safe-area-context: 9e40fb181dac02619414ba1294d6c2a807056ab9 React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 diff --git a/ios/ReactNativePageView.h b/ios/RNCPagerView.h similarity index 60% rename from ios/ReactNativePageView.h rename to ios/RNCPagerView.h index 8887fd22..35325d9d 100644 --- a/ios/ReactNativePageView.h +++ b/ios/RNCPagerView.h @@ -5,23 +5,17 @@ NS_ASSUME_NONNULL_BEGIN -@interface ReactNativePageView: UIView +@interface RNCPagerView: UIView -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher; +- (instancetype)initWithEventDispatcher:(id )eventDispatcher; @property(nonatomic) NSInteger initialPage; -@property(nonatomic) NSInteger lastReportedIndex; -@property(nonatomic) NSInteger destinationIndex; -@property(nonatomic) NSInteger currentIndex; -@property(nonatomic) NSInteger pageMargin; -@property(nonatomic, readonly) BOOL scrollEnabled; +@property(nonatomic) NSString* orientation; @property(nonatomic, readonly) UIScrollViewKeyboardDismissMode dismissKeyboard; -@property(nonatomic) UIPageViewControllerNavigationOrientation orientation; @property(nonatomic, copy) RCTDirectEventBlock onPageSelected; @property(nonatomic, copy) RCTDirectEventBlock onPageScroll; @property(nonatomic, copy) RCTDirectEventBlock onPageScrollStateChanged; @property(nonatomic) BOOL overdrag; -@property(nonatomic) NSString* layoutDirection; @property(nonatomic, assign) BOOL animating; - (void)goTo:(NSInteger)index animated:(BOOL)animated; diff --git a/ios/RNCPagerView.m b/ios/RNCPagerView.m new file mode 100644 index 00000000..50db8621 --- /dev/null +++ b/ios/RNCPagerView.m @@ -0,0 +1,188 @@ + +#import "RNCPagerView.h" +#import "React/RCTLog.h" +#import + +#import "UIViewController+CreateExtension.h" +#import "RCTOnPageScrollEvent.h" +#import "RCTOnPageScrollStateChanged.h" +#import "React/RCTUIManagerObserverCoordinator.h" +#import "RCTOnPageSelected.h" +#import + +@interface RNCPagerView () + +@property(nonatomic, strong) id eventDispatcher; + +@property(nonatomic, strong) UIScrollView *scrollView; +@property(nonatomic, strong) UIView *containerView; + +- (void)goTo:(NSInteger)index animated:(BOOL)animated; +- (void)shouldScroll:(BOOL)scrollEnabled; +- (void)shouldDismissKeyboard:(NSString *)dismissKeyboard; + +@end + +@implementation RNCPagerView { + uint16_t _coalescingKey; +} + +- (instancetype)initWithEventDispatcher:(id)eventDispatcher { + if (self = [super init]) { + _initialPage = 0; + _dismissKeyboard = UIScrollViewKeyboardDismissModeNone; + _coalescingKey = 0; + _eventDispatcher = eventDispatcher; + _orientation = @"horizontal"; + [self embed]; + } + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + [self calculateContentSize]; +} + +- (void)didUpdateReactSubviews { + [self calculateContentSize]; +} + +-(void) insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex { + [super insertReactSubview:subview atIndex:atIndex]; + [_containerView insertSubview:subview atIndex:atIndex]; +} + +- (void)removeReactSubview:(UIView *)subview { + [super removeReactSubview:subview]; + [subview removeFromSuperview]; + + if ([self getCurrentPage] >= self.reactSubviews.count - 1) { + [self goTo:self.reactSubviews.count - 1 animated:false]; + } +} + +- (void)embed { + _scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; + _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _scrollView.delaysContentTouches = NO; + _scrollView.delegate = self; + _scrollView.pagingEnabled = YES; + _scrollView.showsHorizontalScrollIndicator = NO; + _scrollView.showsVerticalScrollIndicator = NO; + [self addSubview:_scrollView]; + + _containerView = [[UIView alloc] initWithFrame:self.bounds]; + [_scrollView addSubview:_containerView]; +} + +- (void)didSetProps:(NSArray *)changedProps { + if ([changedProps containsObject:@"overdrag"]) { + [_scrollView setBounces:_overdrag]; + } +} + +- (void)shouldScroll:(BOOL)scrollEnabled { + if (self.scrollView) { + self.scrollView.scrollEnabled = scrollEnabled; + } +} + +- (void)shouldDismissKeyboard:(NSString *)dismissKeyboard { + _dismissKeyboard = [dismissKeyboard isEqual: @"on-drag"] ? + UIScrollViewKeyboardDismissModeOnDrag : UIScrollViewKeyboardDismissModeNone; + self.scrollView.keyboardDismissMode = _dismissKeyboard; +} + +#pragma mark - Internal methods + +- (void) calculateContentSize { + UIView *initialView = self.containerView.subviews.firstObject; + if (!initialView) { + return; + } + + CGFloat totalSubviewsWidth = initialView.frame.size.width * self.containerView.subviews.count; + CGFloat totalSubviewsHeight = initialView.frame.size.height * self.containerView.subviews.count; + + + if ([self isHorizontal]) { + _scrollView.contentSize = CGSizeMake(totalSubviewsWidth, 0); + _containerView.frame = CGRectMake(0, 0, totalSubviewsWidth, initialView.bounds.size.height); + } else { + _scrollView.contentSize = CGSizeMake(0, totalSubviewsHeight); + _containerView.frame = CGRectMake(0, 0, initialView.bounds.size.width, totalSubviewsHeight); + } + + _scrollView.frame = self.bounds; + [self.scrollView layoutIfNeeded]; + + if (self.initialPage != 0) { + [self goTo:self.initialPage animated:false]; + } +} + +- (void)disableSwipe { + [_scrollView setScrollEnabled:false]; +} + +- (void)enableSwipe { + [_scrollView setScrollEnabled:true]; +} + +- (void)goTo:(NSInteger)index animated:(BOOL)animated { + CGPoint targetOffset = [self isHorizontal] ? CGPointMake(_scrollView.frame.size.width * index, 0) : CGPointMake(0, _scrollView.frame.size.height * index); + + if (animated) { + self.animating = true; + } + + [_scrollView setContentOffset:targetOffset animated:animated]; + + if (!animated) { + int position = [self getCurrentPage]; + [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; + } +} + +- (BOOL)isHorizontal { + return [_orientation isEqualToString:@"horizontal"]; +} + +-(int)getCurrentPage { + return [self isHorizontal] ? _scrollView.contentOffset.x / _scrollView.frame.size.width : _scrollView.contentOffset.y / _scrollView.frame.size.height; +} + +#pragma mark - UIScrollViewDelegate + + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"dragging" coalescingKey:_coalescingKey++]]; +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { + [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"settling" coalescingKey:_coalescingKey++]]; +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + int position = [self getCurrentPage]; + [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"idle" coalescingKey:_coalescingKey++]]; + + [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; +} + +-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + int position = [self getCurrentPage]; + self.animating = false; + [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + int position = [self getCurrentPage]; + + double offset = [self isHorizontal] ? (scrollView.contentOffset.x - (scrollView.frame.size.width * position))/scrollView.frame.size.width : (scrollView.contentOffset.y - (scrollView.frame.size.height * position))/scrollView.frame.size.height; + + [self.eventDispatcher sendEvent:[[RCTOnPageScrollEvent alloc] initWithReactTag:self.reactTag position:@(position) offset:@(offset)]]; +} +@end + diff --git a/ios/ReactViewPagerManager.h b/ios/RNCPagerViewManager.h similarity index 67% rename from ios/ReactViewPagerManager.h rename to ios/RNCPagerViewManager.h index cc5de3e0..f3be10b2 100644 --- a/ios/ReactViewPagerManager.h +++ b/ios/RNCPagerViewManager.h @@ -3,10 +3,10 @@ #import #import #import -#import "ReactNativePageView.h" +#import "RNCPagerView.h" NS_ASSUME_NONNULL_BEGIN -@interface ReactViewPagerManager : RCTViewManager +@interface RNCPagerViewManager : RCTViewManager @end diff --git a/ios/ReactViewPagerManager.m b/ios/RNCPagerViewManager.m similarity index 72% rename from ios/ReactViewPagerManager.m rename to ios/RNCPagerViewManager.m index 38656e19..6ace49b9 100644 --- a/ios/ReactViewPagerManager.m +++ b/ios/RNCPagerViewManager.m @@ -1,21 +1,19 @@ -#import "ReactViewPagerManager.h" +#import "RNCPagerViewManager.h" -@implementation ReactViewPagerManager +@implementation RNCPagerViewManager #pragma mark - RTC RCT_EXPORT_MODULE(RNCViewPager) RCT_EXPORT_VIEW_PROPERTY(initialPage, NSInteger) -RCT_EXPORT_VIEW_PROPERTY(pageMargin, NSInteger) +RCT_EXPORT_VIEW_PROPERTY(orientation, NSString) -RCT_EXPORT_VIEW_PROPERTY(orientation, UIPageViewControllerNavigationOrientation) RCT_EXPORT_VIEW_PROPERTY(onPageSelected, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPageScroll, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPageScrollStateChanged, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(overdrag, BOOL) -RCT_EXPORT_VIEW_PROPERTY(layoutDirection, NSString) - (void) goToPage @@ -25,8 +23,8 @@ - (void) goToPage [self.bridge.uiManager addUIBlock:^( RCTUIManager *uiManager, NSDictionary *viewRegistry) { - ReactNativePageView *view = (ReactNativePageView *)viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[ReactNativePageView class]]) { + RNCPagerView *view = (RNCPagerView *)viewRegistry[reactTag]; + if (!view || ![view isKindOfClass:[RNCPagerView class]]) { RCTLogError(@"Cannot find ReactNativePageView with tag #%@", reactTag); return; } @@ -42,8 +40,8 @@ - (void) changeScrollEnabled [self.bridge.uiManager addUIBlock:^( RCTUIManager *uiManager, NSDictionary *viewRegistry) { - ReactNativePageView *view = (ReactNativePageView *)viewRegistry[reactTag]; - if (!view || ![view isKindOfClass:[ReactNativePageView class]]) { + RNCPagerView *view = (RNCPagerView *)viewRegistry[reactTag]; + if (!view || ![view isKindOfClass:[RNCPagerView class]]) { RCTLogError(@"Cannot find ReactNativePageView with tag #%@", reactTag); return; } @@ -70,17 +68,17 @@ - (void) changeScrollEnabled [self changeScrollEnabled:reactTag enabled:isEnabled]; } -RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, ReactNativePageView) { +RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCPagerView) { [view shouldScroll:[RCTConvert BOOL:json]]; } -RCT_CUSTOM_VIEW_PROPERTY(keyboardDismissMode, NSString, ReactNativePageView) { +RCT_CUSTOM_VIEW_PROPERTY(keyboardDismissMode, NSString, RNCPagerView) { [view shouldDismissKeyboard:[RCTConvert NSString:json]]; } - (UIView *)view { - return [[ReactNativePageView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; + return [[RNCPagerView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; } @end diff --git a/ios/ReactNativePageView.m b/ios/ReactNativePageView.m deleted file mode 100644 index cf2bd57b..00000000 --- a/ios/ReactNativePageView.m +++ /dev/null @@ -1,464 +0,0 @@ - -#import "ReactNativePageView.h" -#import "React/RCTLog.h" -#import - -#import "UIViewController+CreateExtension.h" -#import "RCTOnPageScrollEvent.h" -#import "RCTOnPageScrollStateChanged.h" -#import "RCTOnPageSelected.h" -#import - -@interface ReactNativePageView () - -@property(nonatomic, strong) UIPageViewController *reactPageViewController; -@property(nonatomic, strong) RCTEventDispatcher *eventDispatcher; - -@property(nonatomic, weak) UIScrollView *scrollView; -@property(nonatomic, weak) UIView *currentView; - -@property(nonatomic, strong) NSHashTable *cachedControllers; -@property(nonatomic, assign) CGPoint lastContentOffset; - -- (void)goTo:(NSInteger)index animated:(BOOL)animated; -- (void)shouldScroll:(BOOL)scrollEnabled; -- (void)shouldDismissKeyboard:(NSString *)dismissKeyboard; - - -@end - -@implementation ReactNativePageView { - uint16_t _coalescingKey; -} - -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { - if (self = [super init]) { - _scrollEnabled = YES; - _pageMargin = 0; - _lastReportedIndex = -1; - _destinationIndex = -1; - _orientation = UIPageViewControllerNavigationOrientationHorizontal; - _currentIndex = 0; - _dismissKeyboard = UIScrollViewKeyboardDismissModeNone; - _coalescingKey = 0; - _eventDispatcher = eventDispatcher; - _cachedControllers = [NSHashTable hashTableWithOptions:NSHashTableStrongMemory]; - _overdrag = NO; - _layoutDirection = @"ltr"; - } - return self; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - if (self.reactPageViewController) { - [self shouldScroll:self.scrollEnabled]; - } -} - -- (void)didUpdateReactSubviews { - if (!self.reactPageViewController && self.reactViewController != nil) { - [self embed]; - [self setupInitialController]; - } else { - [self updateDataSource]; - } -} - -- (void)didMoveToSuperview { - [super didMoveToSuperview]; - if (!self.reactPageViewController && self.reactViewController != nil) { - [self embed]; - [self setupInitialController]; - } -} - -- (void)didMoveToWindow { - [super didMoveToWindow]; - if (!self.reactPageViewController && self.reactViewController != nil) { - [self embed]; - [self setupInitialController]; - } - - if (self.reactViewController.navigationController != nil && self.reactViewController.navigationController.interactivePopGestureRecognizer != nil) { - [self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:self.reactViewController.navigationController.interactivePopGestureRecognizer]; - } -} - -- (void)embed { - NSDictionary *options = @{ UIPageViewControllerOptionInterPageSpacingKey: @(self.pageMargin) }; - UIPageViewController *pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll - navigationOrientation:self.orientation - options:options]; - pageViewController.delegate = self; - pageViewController.dataSource = self; - - for (UIView *subview in pageViewController.view.subviews) { - if([subview isKindOfClass:UIScrollView.class]){ - ((UIScrollView *)subview).delegate = self; - ((UIScrollView *)subview).keyboardDismissMode = _dismissKeyboard; - ((UIScrollView *)subview).delaysContentTouches = YES; - self.scrollView = (UIScrollView *)subview; - } - } - - self.reactPageViewController = pageViewController; - - [self reactAddControllerToClosestParent:pageViewController]; - [self addSubview:pageViewController.view]; - - pageViewController.view.frame = self.bounds; - - [self shouldScroll:self.scrollEnabled]; - - [pageViewController.view layoutIfNeeded]; -} - -- (void)shouldScroll:(BOOL)scrollEnabled { - _scrollEnabled = scrollEnabled; - if (self.reactPageViewController.view) { - self.scrollView.scrollEnabled = scrollEnabled; - } -} - -- (void)shouldDismissKeyboard:(NSString *)dismissKeyboard { - _dismissKeyboard = [dismissKeyboard isEqual: @"on-drag"] ? - UIScrollViewKeyboardDismissModeOnDrag : UIScrollViewKeyboardDismissModeNone; - self.scrollView.keyboardDismissMode = _dismissKeyboard; -} - -- (void)setupInitialController { - UIView *initialView = self.reactSubviews[self.initialPage]; - if (initialView) { - UIViewController *initialController = nil; - if (initialView.reactViewController) { - initialController = initialView.reactViewController; - } else { - initialController = [[UIViewController alloc] initWithView:initialView]; - } - - [self.cachedControllers addObject:initialController]; - - [self setReactViewControllers:self.initialPage - with:initialController - direction:UIPageViewControllerNavigationDirectionForward - animated:YES - shouldCallOnPageSelected:YES]; - } -} - -- (void)setReactViewControllers:(NSInteger)index - with:(UIViewController *)controller - direction:(UIPageViewControllerNavigationDirection)direction - animated:(BOOL)animated - shouldCallOnPageSelected:(BOOL)shouldCallOnPageSelected { - if (self.reactPageViewController == nil) { - [self enableSwipe]; - return; - } - - NSArray *currentVCs = self.reactPageViewController.viewControllers; - if (currentVCs.count == 1 && [currentVCs.firstObject isEqual:controller]) { - [self enableSwipe]; - return; - } - - __weak ReactNativePageView *weakSelf = self; - uint16_t coalescingKey = _coalescingKey++; - - if (animated == YES) { - self.animating = YES; - } - - [self.reactPageViewController setViewControllers:@[controller] - direction:direction - animated:animated - completion:^(BOOL finished) { - __strong typeof(self) strongSelf = weakSelf; - strongSelf.currentIndex = index; - strongSelf.currentView = controller.view; - - [strongSelf enableSwipe]; - - if (finished) { - strongSelf.animating = NO; - } - - if (strongSelf.eventDispatcher) { - if (strongSelf.lastReportedIndex != strongSelf.currentIndex) { - if (shouldCallOnPageSelected) { - [strongSelf.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:strongSelf.reactTag position:@(index) coalescingKey:coalescingKey]]; - } - strongSelf.lastReportedIndex = strongSelf.currentIndex; - } - } - }]; -} - -- (UIViewController *)currentlyDisplayed { - return self.reactPageViewController.viewControllers.firstObject; -} - -- (UIViewController *)findCachedControllerForView:(UIView *)view { - for (UIViewController *controller in self.cachedControllers) { - if (controller.view.reactTag == view.reactTag) { - return controller; - } - } - return nil; -} - -- (void)updateDataSource { - if (!self.currentView && self.reactSubviews.count == 0) { - return; - } - - NSInteger newIndex = self.currentView ? [self.reactSubviews indexOfObject:self.currentView] : 0; - - if (newIndex == NSNotFound) { - //Current view was removed - NSInteger maxPage = self.reactSubviews.count - 1; - NSInteger fallbackIndex = self.currentIndex >= maxPage ? maxPage : self.currentIndex; - - [self goTo:fallbackIndex animated:NO]; - } else { - [self goTo:newIndex animated:NO]; - } -} - -- (void)disableSwipe { - self.reactPageViewController.view.userInteractionEnabled = NO; -} - -- (void)enableSwipe { - self.reactPageViewController.view.userInteractionEnabled = YES; -} - -- (void)goTo:(NSInteger)index animated:(BOOL)animated { - NSInteger numberOfPages = self.reactSubviews.count; - - [self disableSwipe]; - - _destinationIndex = index; - - if (numberOfPages == 0 || index < 0 || index > numberOfPages - 1) { - return; - } - - BOOL isRTL = ![self isLtrLayout]; - - BOOL isForward = (index > self.currentIndex && !isRTL) || (index < self.currentIndex && isRTL); - - - UIPageViewControllerNavigationDirection direction = isForward ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse; - - long diff = labs(index - _currentIndex); - - [self goToViewController:index direction:direction animated:(!self.animating && animated) shouldCallOnPageSelected: YES]; - - if (diff == 0) { - [self goToViewController:index direction:direction animated:NO shouldCallOnPageSelected:YES]; - } -} - -- (void)goToViewController:(NSInteger)index - direction:(UIPageViewControllerNavigationDirection)direction - animated:(BOOL)animated - shouldCallOnPageSelected:(BOOL)shouldCallOnPageSelected { - UIView *viewToDisplay = self.reactSubviews[index]; - UIViewController *controllerToDisplay = [self findAndCacheControllerForView:viewToDisplay]; - [self setReactViewControllers:index - with:controllerToDisplay - direction:direction - animated:animated - shouldCallOnPageSelected:shouldCallOnPageSelected]; -} - -- (UIViewController *)findAndCacheControllerForView:(UIView *)viewToDisplay { - if (!viewToDisplay) { return nil; } - - UIViewController *controllerToDisplay = [self findCachedControllerForView:viewToDisplay]; - UIViewController *current = [self currentlyDisplayed]; - - if (!controllerToDisplay && current.view.reactTag == viewToDisplay.reactTag) { - controllerToDisplay = current; - } - if (!controllerToDisplay) { - if (viewToDisplay.reactViewController) { - controllerToDisplay = viewToDisplay.reactViewController; - } else { - controllerToDisplay = [[UIViewController alloc] initWithView:viewToDisplay]; - } - } - [self.cachedControllers addObject:controllerToDisplay]; - - return controllerToDisplay; -} - -- (UIViewController *)nextControllerForController:(UIViewController *)controller - inDirection:(UIPageViewControllerNavigationDirection)direction { - NSUInteger numberOfPages = self.reactSubviews.count; - NSInteger index = [self.reactSubviews indexOfObject:controller.view]; - - if (index == NSNotFound) { - return nil; - } - - direction == UIPageViewControllerNavigationDirectionForward ? index++ : index--; - - if (index < 0 || (index > (numberOfPages - 1))) { - return nil; - } - - UIView *viewToDisplay = self.reactSubviews[index]; - - return [self findAndCacheControllerForView:viewToDisplay]; -} - -#pragma mark - UIPageViewControllerDelegate - -- (void)pageViewController:(UIPageViewController *)pageViewController - didFinishAnimating:(BOOL)finished - previousViewControllers:(nonnull NSArray *)previousViewControllers - transitionCompleted:(BOOL)completed { - - if (completed) { - UIViewController* currentVC = [self currentlyDisplayed]; - NSUInteger currentIndex = [self.reactSubviews indexOfObject:currentVC.view]; - - self.currentIndex = currentIndex; - self.currentView = currentVC.view; - [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:@(currentIndex) coalescingKey:_coalescingKey++]]; - [self.eventDispatcher sendEvent:[[RCTOnPageScrollEvent alloc] initWithReactTag:self.reactTag position:@(currentIndex) offset:@(0.0)]]; - self.lastReportedIndex = currentIndex; - } -} - -#pragma mark - UIPageViewControllerDataSource - -- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController - viewControllerAfterViewController:(UIViewController *)viewController { - UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse; - return [self nextControllerForController:viewController inDirection:direction]; -} - -- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController - viewControllerBeforeViewController:(UIViewController *)viewController { - UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionReverse : UIPageViewControllerNavigationDirectionForward; - return [self nextControllerForController:viewController inDirection:direction]; -} - -#pragma mark - UIPageControlDelegate - -- (void)pageControlValueChanged:(UIPageControl *)sender { - if (sender.currentPage != self.currentIndex) { - [self goTo:sender.currentPage animated:YES]; - } -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { - [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"dragging" coalescingKey:_coalescingKey++]]; -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { - [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"settling" coalescingKey:_coalescingKey++]]; - - if (!_overdrag) { - NSInteger maxIndex = self.reactSubviews.count - 1; - BOOL isFirstPage = [self isLtrLayout] ? _currentIndex == 0 : _currentIndex == maxIndex; - BOOL isLastPage = [self isLtrLayout] ? _currentIndex == maxIndex : _currentIndex == 0; - CGFloat contentOffset =[self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y; - CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height; - - if ((isFirstPage && contentOffset <= topBound) || (isLastPage && contentOffset >= topBound)) { - CGPoint croppedOffset = [self isHorizontal] ? CGPointMake(topBound, 0) : CGPointMake(0, topBound); - *targetContentOffset = croppedOffset; - } - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"idle" coalescingKey:_coalescingKey++]]; -} - -- (BOOL)isHorizontal { - return self.orientation == UIPageViewControllerNavigationOrientationHorizontal; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - CGPoint point = scrollView.contentOffset; - - float offset = 0; - - if (self.isHorizontal) { - if (scrollView.frame.size.width != 0) { - offset = (point.x - scrollView.frame.size.width)/scrollView.frame.size.width; - } - } else { - if (scrollView.frame.size.height != 0) { - offset = (point.y - scrollView.frame.size.height)/scrollView.frame.size.height; - } - } - - float absoluteOffset = fabs(offset); - - NSInteger position = self.currentIndex; - - BOOL isAnimatingBackwards = ([self isLtrLayout] && offset<0) || (![self isLtrLayout] && offset > 0.05f); - - if (scrollView.isDragging) { - _destinationIndex = isAnimatingBackwards ? _currentIndex - 1 : _currentIndex + 1; - } - - if(isAnimatingBackwards){ - position = _destinationIndex; - absoluteOffset = fmax(0, 1 - absoluteOffset); - } - - if (!_overdrag) { - NSInteger maxIndex = self.reactSubviews.count - 1; - NSInteger firstPageIndex = [self isLtrLayout] ? 0 : maxIndex; - NSInteger lastPageIndex = [self isLtrLayout] ? maxIndex : 0; - BOOL isFirstPage = _currentIndex == firstPageIndex; - BOOL isLastPage = _currentIndex == lastPageIndex; - CGFloat contentOffset =[self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y; - CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height; - - if ((isFirstPage && contentOffset <= topBound) || (isLastPage && contentOffset >= topBound)) { - CGPoint croppedOffset = [self isHorizontal] ? CGPointMake(topBound, 0) : CGPointMake(0, topBound); - scrollView.contentOffset = croppedOffset; - absoluteOffset=0; - position = isLastPage ? lastPageIndex : firstPageIndex; - } - } - - float interpolatedOffset = absoluteOffset * labs(_destinationIndex - _currentIndex); - - self.lastContentOffset = scrollView.contentOffset; - [self.eventDispatcher sendEvent:[[RCTOnPageScrollEvent alloc] initWithReactTag:self.reactTag position:@(position) offset:@(interpolatedOffset)]]; -} - -- (NSString *)determineScrollDirection:(UIScrollView *)scrollView { - NSString *scrollDirection; - if (self.isHorizontal) { - if (self.lastContentOffset.x > scrollView.contentOffset.x) { - scrollDirection = @"left"; - } else if (self.lastContentOffset.x < scrollView.contentOffset.x) { - scrollDirection = @"right"; - } - } else { - if (self.lastContentOffset.y > scrollView.contentOffset.y) { - scrollDirection = @"up"; - } else if (self.lastContentOffset.y < scrollView.contentOffset.y) { - scrollDirection = @"down"; - } - } - return scrollDirection; -} - -- (BOOL)isLtrLayout { - return [_layoutDirection isEqualToString:@"ltr"]; -} -@end From 5926edf42be6fca1f6b93cc766bb19a0519c9d78 Mon Sep 17 00:00:00 2001 From: Piotr Trocki Date: Wed, 25 Jan 2023 20:45:36 +0100 Subject: [PATCH 04/16] chore: update release script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f9a7ce3a..9ddbaea3 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "lint": "eslint \"**/*.{js,ts,tsx}\"", "prepare": "bob build", "release": "release-it", - "releaseNext": "release-it --npm.tag=newarch --preRelease=rc", + "releaseNext": "release-it --npm.tag=next --preRelease=rc", "example": "yarn --cwd example", "bootstrap": "yarn example && yarn && yarn example pods", "fabricexample": "yarn --cwd fabricexample", From 54c113d034e9bba9876a136aa6fe2ddc02f6eb9c Mon Sep 17 00:00:00 2001 From: Piotr Trocki Date: Wed, 25 Jan 2023 20:56:10 +0100 Subject: [PATCH 05/16] Release 7.0.0-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ddbaea3..d6471f15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-pager-view", - "version": "6.1.2", + "version": "7.0.0-rc.0", "description": "React Native wrapper for Android and iOS ViewPager", "main": "lib/commonjs/index", "module": "lib/module/index", From 4e5c511cad5f92ea2ff59eb8eefe14e065c300f5 Mon Sep 17 00:00:00 2001 From: krozniata <56474758+krozniata@users.noreply.github.com> Date: Mon, 13 Feb 2023 23:29:51 +0100 Subject: [PATCH 06/16] feat: make pager controlled --- fabricexample/ios/Podfile.lock | 8 +++---- ios/Fabric/RNCPagerViewComponentView.mm | 29 ++++++++++++++++++++----- ios/RNCPagerViewManager.m | 3 +++ src/PagerViewNativeComponent.ts | 2 ++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/fabricexample/ios/Podfile.lock b/fabricexample/ios/Podfile.lock index 8db0c2c0..8f10d6b7 100644 --- a/fabricexample/ios/Podfile.lock +++ b/fabricexample/ios/Podfile.lock @@ -625,16 +625,16 @@ PODS: - React-jsinspector (0.70.5) - React-logger (0.70.5): - glog - - react-native-pager-view (6.1.2): + - react-native-pager-view (7.0.0-rc.0): - RCT-Folly - RCTRequired - RCTTypeSafety - React-Codegen - React-Core - - react-native-pager-view/common (= 6.1.2) + - react-native-pager-view/common (= 7.0.0-rc.0) - React-RCTFabric - ReactCommon/turbomodule/core - - react-native-pager-view/common (6.1.2): + - react-native-pager-view/common (7.0.0-rc.0): - RCT-Folly - RCTRequired - RCTTypeSafety @@ -1022,7 +1022,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b React-jsinspector: badd81696361249893a80477983e697aab3c1a34 React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c - react-native-pager-view: 991c947924d48f1232a98ba6e6d3466eaf51034d + react-native-pager-view: cf8a16a2f84215f444431f2fea40649e9d05504c react-native-safe-area-context: 2f75b317784a1a8e224562941e50ecbc932d2097 React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 diff --git a/ios/Fabric/RNCPagerViewComponentView.mm b/ios/Fabric/RNCPagerViewComponentView.mm index 9bb1a230..e05d8037 100644 --- a/ios/Fabric/RNCPagerViewComponentView.mm +++ b/ios/Fabric/RNCPagerViewComponentView.mm @@ -24,6 +24,10 @@ @implementation RNCPagerViewComponentView { UIView *_containerView; CGSize _contentSize; + + NSInteger _page; + BOOL _animated; + NSInteger _initialPage; } @@ -34,6 +38,9 @@ - (instancetype)initWithFrame:(CGRect)frame _props = defaultProps; _initialPage = -1; + _page = -1; + _animated = true; + _scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; @@ -68,6 +75,14 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(cons [_scrollView setScrollEnabled:newScreenProps.scrollEnabled]; } + if (newScreenProps.animated != _animated) { + _animated = newScreenProps.animated; + } + + if (newScreenProps.page != _page && newScreenProps.page >= 0 && newScreenProps.page < _containerView.subviews.count) { + _page = newScreenProps.page; + [self goTo:_page animated:_animated]; + } [super updateProps:props oldProps:oldProps]; } @@ -114,6 +129,7 @@ - (void)updateState:(State::Shared const &)state oldState:(State::Shared const & if (!CGSizeEqualToSize(_scrollView.frame.size, CGSizeZero) && _initialPage == -1) { [self setPageWithoutAnimation: props.initialPage]; _initialPage = props.initialPage; + _page = _initialPage; } } @@ -212,19 +228,20 @@ -(CGPoint)getPageOffset:(NSInteger)pageIndex { return [self isHorizontal] ? CGPointMake(_scrollView.frame.size.width * pageIndex, 0) : CGPointMake(0, _scrollView.frame.size.height * pageIndex); } -- (void)setPage:(NSInteger)index { +-(void)goTo:(NSInteger)index animated:(BOOL)animated { CGPoint targetOffset = [self getPageOffset:index]; - [_scrollView setContentOffset:targetOffset animated:YES]; + [_scrollView setContentOffset:targetOffset animated:animated]; +} + +- (void)setPage:(NSInteger)index { + [self goTo:index animated:YES]; } - (void)setPageWithoutAnimation:(NSInteger)index { - CGPoint targetOffset = [self getPageOffset:index]; - - [_scrollView setContentOffset:targetOffset animated:NO]; + [self goTo:index animated:NO]; const auto strongEventEmitter = *std::dynamic_pointer_cast(_eventEmitter); - strongEventEmitter.onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(index)}); } diff --git a/ios/RNCPagerViewManager.m b/ios/RNCPagerViewManager.m index 6ace49b9..df0ff70e 100644 --- a/ios/RNCPagerViewManager.m +++ b/ios/RNCPagerViewManager.m @@ -7,6 +7,9 @@ @implementation RNCPagerViewManager RCT_EXPORT_MODULE(RNCViewPager) +RCT_EXPORT_VIEW_PROPERTY(page, NSInteger) +RCT_EXPORT_VIEW_PROPERTY(animated, BOOL) + RCT_EXPORT_VIEW_PROPERTY(initialPage, NSInteger) RCT_EXPORT_VIEW_PROPERTY(orientation, NSString) diff --git a/src/PagerViewNativeComponent.ts b/src/PagerViewNativeComponent.ts index af9e655e..ab707733 100644 --- a/src/PagerViewNativeComponent.ts +++ b/src/PagerViewNativeComponent.ts @@ -24,6 +24,8 @@ export type OnPageScrollStateChangedEventData = Readonly<{ }>; export interface NativeProps extends ViewProps { + page?: Int32; + animated?: WithDefault; scrollEnabled?: WithDefault; layoutDirection?: WithDefault<'ltr' | 'rtl', 'ltr'>; initialPage?: Int32; From 4e835cf1ad536fa4341cdc92eb3bb7b64aa86939 Mon Sep 17 00:00:00 2001 From: krozniata <56474758+krozniata@users.noreply.github.com> Date: Tue, 14 Feb 2023 00:45:14 +0100 Subject: [PATCH 07/16] feat: make pager controlled (old arch) --- ios/RNCPagerView.h | 2 ++ ios/RNCPagerView.m | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/ios/RNCPagerView.h b/ios/RNCPagerView.h index 35325d9d..61849c18 100644 --- a/ios/RNCPagerView.h +++ b/ios/RNCPagerView.h @@ -9,6 +9,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithEventDispatcher:(id )eventDispatcher; +@property(nonatomic) NSInteger page; +@property(nonatomic) BOOL animated; @property(nonatomic) NSInteger initialPage; @property(nonatomic) NSString* orientation; @property(nonatomic, readonly) UIScrollViewKeyboardDismissMode dismissKeyboard; diff --git a/ios/RNCPagerView.m b/ios/RNCPagerView.m index 50db8621..410e8dfc 100644 --- a/ios/RNCPagerView.m +++ b/ios/RNCPagerView.m @@ -30,6 +30,8 @@ @implementation RNCPagerView { - (instancetype)initWithEventDispatcher:(id)eventDispatcher { if (self = [super init]) { _initialPage = 0; + _page = 0; + _animated = YES; _dismissKeyboard = UIScrollViewKeyboardDismissModeNone; _coalescingKey = 0; _eventDispatcher = eventDispatcher; @@ -80,6 +82,12 @@ - (void)didSetProps:(NSArray *)changedProps { if ([changedProps containsObject:@"overdrag"]) { [_scrollView setBounces:_overdrag]; } + + if ([changedProps containsObject:@"page"]) { + if (_page >= 0 && _page < _containerView.subviews.count) { + [self goTo:_page animated:_animated]; + } + } } - (void)shouldScroll:(BOOL)scrollEnabled { From b3879071f7bb3d204c8decda5bd2a2665db24479 Mon Sep 17 00:00:00 2001 From: krozniata <56474758+krozniata@users.noreply.github.com> Date: Thu, 16 Feb 2023 13:27:19 +0100 Subject: [PATCH 08/16] feat: rename setPage to setPageWithAnimation --- .../com/reactnativepagerview/PagerViewViewManager.kt | 12 +++++++++++- .../com/reactnativepagerview/PagerViewViewManager.kt | 2 +- ios/Fabric/RNCPagerViewComponentView.h | 2 +- ios/Fabric/RNCPagerViewComponentView.mm | 4 ++-- ios/RNCPagerViewManager.m | 2 +- src/PagerView.tsx | 2 +- src/PagerViewNativeComponent.ts | 4 ++-- 7 files changed, 19 insertions(+), 9 deletions(-) diff --git a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt index f6a284bf..99c44f1a 100644 --- a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -105,6 +105,16 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa return PagerViewViewManagerImpl.needsCustomLayoutForChildren() } + @ReactProp(name = "page") + override fun setPage(view: NestedScrollableHost?, value: Int) { + return + } + + @ReactProp(name = "animated") + override fun setAnimated(view: NestedScrollableHost?, value: Boolean) { + return + } + @ReactProp(name = "scrollEnabled", defaultBoolean = true) override fun setScrollEnabled(view: NestedScrollableHost?, value: Boolean) { if (view != null) { @@ -181,7 +191,7 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa } } - override fun setPage(view: NestedScrollableHost?, selectedPage: Int) { + override fun setPageWithAnimation(view: NestedScrollableHost?, selectedPage: Int) { goTo(view, selectedPage, true) } diff --git a/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt index 2a093a63..22be7b23 100644 --- a/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -165,7 +165,7 @@ class PagerViewViewManager : ViewGroupManager() { } companion object { - private const val COMMAND_SET_PAGE = "setPage" + private const val COMMAND_SET_PAGE = "setPageWithAnimation" private const val COMMAND_SET_PAGE_WITHOUT_ANIMATION = "setPageWithoutAnimation" private const val COMMAND_SET_SCROLL_ENABLED = "setScrollEnabledImperatively" } diff --git a/ios/Fabric/RNCPagerViewComponentView.h b/ios/Fabric/RNCPagerViewComponentView.h index 6e4187fa..c8e772f4 100644 --- a/ios/Fabric/RNCPagerViewComponentView.h +++ b/ios/Fabric/RNCPagerViewComponentView.h @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) BOOL overdrag; -- (void)setPage:(NSInteger)number; +- (void)setPageWithAnimation:(NSInteger)number; - (void)setPageWithoutAnimation:(NSInteger)number; @end diff --git a/ios/Fabric/RNCPagerViewComponentView.mm b/ios/Fabric/RNCPagerViewComponentView.mm index e05d8037..038dec6a 100644 --- a/ios/Fabric/RNCPagerViewComponentView.mm +++ b/ios/Fabric/RNCPagerViewComponentView.mm @@ -187,7 +187,7 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { strongEventEmitter.onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast(position)}); } -//Handles sending onPageSelected event on setPage method completion +//Handles sending onPageSelected event on setPageWithAnimation method completion -(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { int position = [self getCurrentPage]; @@ -234,7 +234,7 @@ -(void)goTo:(NSInteger)index animated:(BOOL)animated { [_scrollView setContentOffset:targetOffset animated:animated]; } -- (void)setPage:(NSInteger)index { +- (void)setPageWithAnimation:(NSInteger)index { [self goTo:index animated:YES]; } diff --git a/ios/RNCPagerViewManager.m b/ios/RNCPagerViewManager.m index df0ff70e..846edbce 100644 --- a/ios/RNCPagerViewManager.m +++ b/ios/RNCPagerViewManager.m @@ -52,7 +52,7 @@ - (void) changeScrollEnabled }]; } -RCT_EXPORT_METHOD(setPage +RCT_EXPORT_METHOD(setPageWithAnimation : (nonnull NSNumber *)reactTag index : (nonnull NSNumber *)index) { [self goToPage:reactTag index:index animated:true]; diff --git a/src/PagerView.tsx b/src/PagerView.tsx index 785c9791..005afa99 100644 --- a/src/PagerView.tsx +++ b/src/PagerView.tsx @@ -98,7 +98,7 @@ export class PagerView extends React.Component { */ public setPage = (selectedPage: number) => { if (this.pagerView) { - PagerViewCommands.setPage(this.pagerView, selectedPage); + PagerViewCommands.setPageWithAnimation(this.pagerView, selectedPage); } }; diff --git a/src/PagerViewNativeComponent.ts b/src/PagerViewNativeComponent.ts index ab707733..13e828a4 100644 --- a/src/PagerViewNativeComponent.ts +++ b/src/PagerViewNativeComponent.ts @@ -43,7 +43,7 @@ export interface NativeProps extends ViewProps { type PagerViewViewType = HostComponent; export interface NativeCommands { - setPage: ( + setPageWithAnimation: ( viewRef: React.ElementRef, selectedPage: Int32 ) => void; @@ -59,7 +59,7 @@ export interface NativeCommands { export const Commands: NativeCommands = codegenNativeCommands({ supportedCommands: [ - 'setPage', + 'setPageWithAnimation', 'setPageWithoutAnimation', 'setScrollEnabledImperatively', ], From 70fac2acc63a4df2ddf68b2cfb4b554b0b5a5b30 Mon Sep 17 00:00:00 2001 From: krozniata <56474758+krozniata@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:33:15 +0100 Subject: [PATCH 09/16] feat(android): make pager controlled --- .../PagerViewViewManager.kt | 7 ++++--- .../PagerViewViewManagerImpl.kt | 2 ++ .../PagerViewViewManager.kt | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt index 99c44f1a..57a77acd 100644 --- a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -107,12 +107,13 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa @ReactProp(name = "page") override fun setPage(view: NestedScrollableHost?, value: Int) { - return + val animated = PagerViewViewManagerImpl.animated + goTo(view, value, animated) } - @ReactProp(name = "animated") + @ReactProp(name = "animated", defaultBoolean = true) override fun setAnimated(view: NestedScrollableHost?, value: Boolean) { - return + PagerViewViewManagerImpl.animated = value } @ReactProp(name = "scrollEnabled", defaultBoolean = true) diff --git a/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt b/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt index 0abf6682..ab6d14f4 100644 --- a/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt +++ b/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt @@ -7,6 +7,8 @@ import com.facebook.react.uimanager.PixelUtil object PagerViewViewManagerImpl { const val NAME = "RNCViewPager" + var animated = true + fun getViewPager(view: NestedScrollableHost): ViewPager2 { if (view.getChildAt(0) is ViewPager2) { return view.getChildAt(0) as ViewPager2 diff --git a/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt index 22be7b23..89f9b2ab 100644 --- a/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/paper/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -95,6 +95,25 @@ class PagerViewViewManager : ViewGroupManager() { return PagerViewViewManagerImpl.needsCustomLayoutForChildren() } + @ReactProp(name = "page") + fun setPage(host: NestedScrollableHost, pageIndex: Int) { + val view = PagerViewViewManagerImpl.getViewPager(host) + Assertions.assertNotNull(view) + Assertions.assertNotNull(pageIndex) + val childCount = view.adapter?.itemCount + val animated = PagerViewViewManagerImpl.animated + val canScroll = childCount != null && childCount > 0 && pageIndex >= 0 && pageIndex < childCount + if (canScroll) { + PagerViewViewManagerImpl.setCurrentItem(view, pageIndex, animated) + eventDispatcher.dispatchEvent(PageSelectedEvent(host.id, pageIndex)) + } + } + + @ReactProp(name = "animated", defaultBoolean = true) + fun setAnimated(host: NestedScrollableHost, value: Boolean) { + PagerViewViewManagerImpl.animated = value + } + @ReactProp(name = "scrollEnabled", defaultBoolean = true) fun setScrollEnabled(host: NestedScrollableHost, value: Boolean) { PagerViewViewManagerImpl.setScrollEnabled(host, value) From 032cc2fb64984a92c7174c1754fa598faa61a92e Mon Sep 17 00:00:00 2001 From: krozniata <56474758+krozniata@users.noreply.github.com> Date: Thu, 16 Feb 2023 23:17:21 +0100 Subject: [PATCH 10/16] chore: update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5e301f2..827cecf9 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,8 @@ For advanced usage please take a look into our [example project](https://github. | Prop | Description | Platform | | -------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | +| `page: number` | Index of page to be displayed | both | +| `animated: boolean` | Should transition between pages be animated | both | | `initialPage` | Index of initial page that should be selected | both | | `scrollEnabled: boolean` | Should pager view scroll, when scroll enabled | both | | `onPageScroll: (e: PageScrollEvent) => void` | Executed when transitioning between pages (ether because the animation for the requested page has changed or when the user is swiping/dragging between pages) | both | @@ -184,8 +186,8 @@ For advanced usage please take a look into our [example project](https://github. | Method | Description | Platform | | ------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | -| `setPage(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both | -| `setPageWithoutAnimation(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both | +| `setPage(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. The recommended way is using the `page` and `animated` props | both | +| `setPageWithoutAnimation(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. The recommended way is using the `page` and `animated` props | both | | `setScrollEnabled(scrollEnabled: boolean)` | A helper function to enable/disable scroll imperatively. The recommended way is using the scrollEnabled prop, however, there might be a case where a imperative solution is more useful (e.g. for not blocking an animation) | both | ## Contributing From 3c073342b7194e67c5cda10d11fb141529f5f9ec Mon Sep 17 00:00:00 2001 From: krozniata <56474758+krozniata@users.noreply.github.com> Date: Fri, 17 Feb 2023 14:10:53 +0100 Subject: [PATCH 11/16] chore(android): remove unnecessary val --- .../java/com/reactnativepagerview/PagerViewViewManager.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt index 57a77acd..1145a5c1 100644 --- a/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt +++ b/android/src/fabric/java/com/reactnativepagerview/PagerViewViewManager.kt @@ -107,8 +107,7 @@ class PagerViewViewManager : ViewGroupManager(), RNCViewPa @ReactProp(name = "page") override fun setPage(view: NestedScrollableHost?, value: Int) { - val animated = PagerViewViewManagerImpl.animated - goTo(view, value, animated) + goTo(view, value, PagerViewViewManagerImpl.animated) } @ReactProp(name = "animated", defaultBoolean = true) From 26cd84c4605cab4f7001db778e049ede80aa9082 Mon Sep 17 00:00:00 2001 From: krozniata <56474758+krozniata@users.noreply.github.com> Date: Sun, 19 Feb 2023 22:25:13 +0100 Subject: [PATCH 12/16] feat: make all examples controlled --- example/src/BasicPagerViewExample.tsx | 2 ++ example/src/OnPageScrollExample.tsx | 4 +-- .../NavigationPanel/ControlPanel.tsx | 26 +++++++------------ example/src/hook/useNavigationPanel.ts | 20 +++++--------- fabricexample/src/BasicPagerViewExample.tsx | 2 ++ fabricexample/src/OnPageScrollExample.tsx | 4 +-- .../NavigationPanel/ControlPanel.tsx | 26 +++++++------------ fabricexample/src/hook/useNavigationPanel.ts | 20 +++++--------- 8 files changed, 40 insertions(+), 64 deletions(-) diff --git a/example/src/BasicPagerViewExample.tsx b/example/src/BasicPagerViewExample.tsx index 6c20a89e..b54ca421 100644 --- a/example/src/BasicPagerViewExample.tsx +++ b/example/src/BasicPagerViewExample.tsx @@ -18,6 +18,8 @@ export function BasicPagerViewExample() { //@ts-ignore testID="pager-view" ref={ref} + page={navigationPanel.page} + animated={navigationPanel.animated} style={styles.PagerView} initialPage={0} layoutDirection="ltr" diff --git a/example/src/OnPageScrollExample.tsx b/example/src/OnPageScrollExample.tsx index cbe22917..18db856b 100644 --- a/example/src/OnPageScrollExample.tsx +++ b/example/src/OnPageScrollExample.tsx @@ -10,7 +10,7 @@ const AnimatedPagerView = Animated.createAnimatedComponent(PagerView); export function OnPageScrollExample() { const { ref, ...navigationPanel } = useNavigationPanel(5); - const { activePage, setPage, progress, pages } = navigationPanel; + const { page, setPage, progress, pages } = navigationPanel; return ( @@ -22,7 +22,7 @@ export function OnPageScrollExample() { Page {index} diff --git a/example/src/component/NavigationPanel/ControlPanel.tsx b/example/src/component/NavigationPanel/ControlPanel.tsx index 3d3ca71c..fb526637 100644 --- a/example/src/component/NavigationPanel/ControlPanel.tsx +++ b/example/src/component/NavigationPanel/ControlPanel.tsx @@ -5,8 +5,8 @@ import { ProgressBar } from '../ProgressBar'; import type { NavigationPanelProps } from './types'; export function ControlsPanel({ - activePage, - isAnimated, + page, + animated, pages, scrollState, scrollEnabled, @@ -21,14 +21,8 @@ export function ControlsPanel({ toggleOverdrag, }: NavigationPanelProps) { const firstPage = useCallback(() => setPage(0), [setPage]); - const prevPage = useCallback(() => setPage(activePage - 1), [ - activePage, - setPage, - ]); - const nextPage = useCallback(() => setPage(activePage + 1), [ - setPage, - activePage, - ]); + const prevPage = useCallback(() => setPage(page - 1), [page, setPage]); + const nextPage = useCallback(() => setPage(page + 1), [setPage, page]); const lastPage = useCallback(() => setPage(pages.length - 1), [ pages.length, setPage, @@ -64,7 +58,7 @@ export function ControlsPanel({