From 8ca8b0df1a2b70236213c18601d77c3bab7d37fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Tue, 3 Jan 2023 18:01:37 +0100 Subject: [PATCH 1/8] feat: rewrite old arch to use UIScrollView --- example/ios/Podfile.lock | 4 +- example/src/utils.ts | 1 + ios/ReactNativePageView.h | 7 +- ios/ReactNativePageView.m | 422 ++++++------------------------------ ios/ReactViewPagerManager.m | 2 - 5 files changed, 73 insertions(+), 363 deletions(-) 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/example/src/utils.ts b/example/src/utils.ts index a3de68b3..72b330c9 100644 --- a/example/src/utils.ts +++ b/example/src/utils.ts @@ -27,6 +27,7 @@ export const createPage = (key: number): CreatePage => { backgroundColor: BGCOLOR[key % BGCOLOR.length], alignItems: 'center', padding: 20, + height: '100%', }, imgSource: { uri: IMAGE_URIS[key % BGCOLOR.length] || '' }, }; diff --git a/ios/ReactNativePageView.h b/ios/ReactNativePageView.h index 8887fd22..1c29fd48 100644 --- a/ios/ReactNativePageView.h +++ b/ios/ReactNativePageView.h @@ -7,21 +7,16 @@ NS_ASSUME_NONNULL_BEGIN @interface ReactNativePageView: 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, 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/ReactNativePageView.m b/ios/ReactNativePageView.m index cf2bd57b..b111906c 100644 --- a/ios/ReactNativePageView.m +++ b/ios/ReactNativePageView.m @@ -6,18 +6,17 @@ #import "UIViewController+CreateExtension.h" #import "RCTOnPageScrollEvent.h" #import "RCTOnPageScrollStateChanged.h" +#import "React/RCTUIManagerObserverCoordinator.h" #import "RCTOnPageSelected.h" #import -@interface ReactNativePageView () +@interface ReactNativePageView () -@property(nonatomic, strong) UIPageViewController *reactPageViewController; -@property(nonatomic, strong) RCTEventDispatcher *eventDispatcher; +@property(nonatomic, strong) id eventDispatcher; -@property(nonatomic, weak) UIScrollView *scrollView; -@property(nonatomic, weak) UIView *currentView; +@property(nonatomic, strong) UIScrollView *scrollView; +@property(nonatomic, strong) UIView *containerView; -@property(nonatomic, strong) NSHashTable *cachedControllers; @property(nonatomic, assign) CGPoint lastContentOffset; - (void)goTo:(NSInteger)index animated:(BOOL)animated; @@ -31,92 +30,59 @@ @implementation ReactNativePageView { uint16_t _coalescingKey; } -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher { +- (instancetype)initWithEventDispatcher:(id)eventDispatcher { if (self = [super init]) { - _scrollEnabled = YES; - _pageMargin = 0; - _lastReportedIndex = -1; - _destinationIndex = -1; - _orientation = UIPageViewControllerNavigationOrientationHorizontal; - _currentIndex = 0; + _initialPage = 0; _dismissKeyboard = UIScrollViewKeyboardDismissModeNone; _coalescingKey = 0; _eventDispatcher = eventDispatcher; - _cachedControllers = [NSHashTable hashTableWithOptions:NSHashTableStrongMemory]; _overdrag = NO; - _layoutDirection = @"ltr"; + [self embed]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; - if (self.reactPageViewController) { - [self shouldScroll:self.scrollEnabled]; - } + [self calculateContentSize]; } - (void)didUpdateReactSubviews { - if (!self.reactPageViewController && self.reactViewController != nil) { - [self embed]; - [self setupInitialController]; - } else { - [self updateDataSource]; - } + [self calculateContentSize]; } -- (void)didMoveToSuperview { - [super didMoveToSuperview]; - if (!self.reactPageViewController && self.reactViewController != nil) { - [self embed]; - [self setupInitialController]; - } +-(void) insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex { + [super insertReactSubview:subview atIndex:atIndex]; + [_containerView insertSubview:subview atIndex:atIndex]; } -- (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)removeReactSubview:(UIView *)subview { + [super removeReactSubview:subview]; + [subview removeFromSuperview]; } - (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]; + _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]; +} + +- (void)didSetProps:(NSArray *)changedProps { + if ([changedProps containsObject:@"overdrag"]) { + [_scrollView setBounces:_overdrag]; + } } - (void)shouldScroll:(BOOL)scrollEnabled { - _scrollEnabled = scrollEnabled; - if (self.reactPageViewController.view) { + if (self.scrollView) { self.scrollView.scrollEnabled = scrollEnabled; } } @@ -127,338 +93,88 @@ - (void)shouldDismissKeyboard:(NSString *)dismissKeyboard { 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; - } +#pragma mark - Internal methods - NSArray *currentVCs = self.reactPageViewController.viewControllers; - if (currentVCs.count == 1 && [currentVCs.firstObject isEqual:controller]) { - [self enableSwipe]; +- (void) calculateContentSize { + UIView *initialView = self.containerView.subviews.firstObject; + if (!initialView) { return; } - - __weak ReactNativePageView *weakSelf = self; - uint16_t coalescingKey = _coalescingKey++; - if (animated == YES) { - self.animating = YES; - } + CGFloat width = initialView.frame.size.width * self.containerView.subviews.count; - [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) { + if (_scrollView.contentSize.width == width) { return; } - NSInteger newIndex = self.currentView ? [self.reactSubviews indexOfObject:self.currentView] : 0; + _scrollView.contentSize = CGSizeMake(width, 0); + _containerView.frame = CGRectMake(0, 0, width, initialView.bounds.size.height); + + _scrollView.frame = self.bounds; + [self.scrollView layoutIfNeeded]; - 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]; + if (self.initialPage != 0) { + [self goTo:self.initialPage animated:false]; } } - (void)disableSwipe { - self.reactPageViewController.view.userInteractionEnabled = NO; + self.scrollView.userInteractionEnabled = NO; } - (void)enableSwipe { - self.reactPageViewController.view.userInteractionEnabled = YES; + self.scrollView.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]; + CGPoint targetOffset = [self isHorizontal] ? CGPointMake(_scrollView.frame.size.width * index, 0) : CGPointMake(0, _scrollView.frame.size.height * index); - return [self findAndCacheControllerForView:viewToDisplay]; -} - -#pragma mark - UIPageViewControllerDelegate - -- (void)pageViewController:(UIPageViewController *)pageViewController - didFinishAnimating:(BOOL)finished - previousViewControllers:(nonnull NSArray *)previousViewControllers - transitionCompleted:(BOOL)completed { + [_scrollView setContentOffset:targetOffset animated:animated]; - 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; + if (!animated) { + int position = [self getCurrentPage]; + [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; } } -#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]; +- (BOOL)isHorizontal { + return _scrollView.contentSize.width > _scrollView.contentSize.height; } -#pragma mark - UIPageControlDelegate - -- (void)pageControlValueChanged:(UIPageControl *)sender { - if (sender.currentPage != self.currentIndex) { - [self goTo:sender.currentPage animated:YES]; - } +-(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 { + int position = [self getCurrentPage]; [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; - } - } + [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"idle" coalescingKey:_coalescingKey++]]; } -- (BOOL)isHorizontal { - return self.orientation == UIPageViewControllerNavigationOrientationHorizontal; +-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + int position = [self getCurrentPage]; + + [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; } - (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); + int position = [self getCurrentPage]; - 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); + 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.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"]; + [self.eventDispatcher sendEvent:[[RCTOnPageScrollEvent alloc] initWithReactTag:self.reactTag position:@(position) offset:@(offset)]]; } @end + diff --git a/ios/ReactViewPagerManager.m b/ios/ReactViewPagerManager.m index 38656e19..1e029527 100644 --- a/ios/ReactViewPagerManager.m +++ b/ios/ReactViewPagerManager.m @@ -10,12 +10,10 @@ @implementation ReactViewPagerManager RCT_EXPORT_VIEW_PROPERTY(initialPage, NSInteger) RCT_EXPORT_VIEW_PROPERTY(pageMargin, NSInteger) -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 From 6509db8459eec2ec46d5ddd96f60c4a3e9a61dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 18 Jan 2023 11:12:15 +0100 Subject: [PATCH 2/8] feat: update example styles --- example/src/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/example/src/utils.ts b/example/src/utils.ts index 72b330c9..a3de68b3 100644 --- a/example/src/utils.ts +++ b/example/src/utils.ts @@ -27,7 +27,6 @@ export const createPage = (key: number): CreatePage => { backgroundColor: BGCOLOR[key % BGCOLOR.length], alignItems: 'center', padding: 20, - height: '100%', }, imgSource: { uri: IMAGE_URIS[key % BGCOLOR.length] || '' }, }; From 0575bcb6ed260f4c1e931db6f0a3dfd8016fc114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 18 Jan 2023 12:14:22 +0100 Subject: [PATCH 3/8] fix: sending event on scrollViewDidEndDecelerating --- ios/ReactNativePageView.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/ReactNativePageView.m b/ios/ReactNativePageView.m index b111906c..01587a4b 100644 --- a/ios/ReactNativePageView.m +++ b/ios/ReactNativePageView.m @@ -36,7 +36,6 @@ - (instancetype)initWithEventDispatcher:(id)eventDis _dismissKeyboard = UIScrollViewKeyboardDismissModeNone; _coalescingKey = 0; _eventDispatcher = eventDispatcher; - _overdrag = NO; [self embed]; } return self; @@ -153,18 +152,19 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { - int position = [self getCurrentPage]; [self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"settling" coalescingKey:_coalescingKey++]]; - - [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] 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]; + NSLog(@"position scrollViewDidEndScrollingAnimation %d", position); [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; } From 6554ce9ea8be466745e9aeb0773c1edd920bb007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 18 Jan 2023 18:17:47 +0100 Subject: [PATCH 4/8] feat: properly calculate width using orientation --- ios/ReactNativePageView.h | 1 + ios/ReactNativePageView.m | 26 ++++++++++++++------------ ios/ReactViewPagerManager.m | 1 + 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/ios/ReactNativePageView.h b/ios/ReactNativePageView.h index 1c29fd48..6a0b8a96 100644 --- a/ios/ReactNativePageView.h +++ b/ios/ReactNativePageView.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithEventDispatcher:(id )eventDispatcher; @property(nonatomic) NSInteger initialPage; +@property(nonatomic) NSString* orientation; @property(nonatomic) NSInteger currentIndex; @property(nonatomic) NSInteger pageMargin; @property(nonatomic, readonly) UIScrollViewKeyboardDismissMode dismissKeyboard; diff --git a/ios/ReactNativePageView.m b/ios/ReactNativePageView.m index 01587a4b..a6a58726 100644 --- a/ios/ReactNativePageView.m +++ b/ios/ReactNativePageView.m @@ -23,7 +23,6 @@ - (void)goTo:(NSInteger)index animated:(BOOL)animated; - (void)shouldScroll:(BOOL)scrollEnabled; - (void)shouldDismissKeyboard:(NSString *)dismissKeyboard; - @end @implementation ReactNativePageView { @@ -36,6 +35,7 @@ - (instancetype)initWithEventDispatcher:(id)eventDis _dismissKeyboard = UIScrollViewKeyboardDismissModeNone; _coalescingKey = 0; _eventDispatcher = eventDispatcher; + _orientation = @"horizontal"; [self embed]; } return self; @@ -69,15 +69,15 @@ - (void)embed { _scrollView.showsHorizontalScrollIndicator = NO; _scrollView.showsVerticalScrollIndicator = NO; [self addSubview:_scrollView]; - - _containerView = [[UIView alloc] initWithFrame:CGRectZero]; + + _containerView = [[UIView alloc] initWithFrame:self.bounds]; [_scrollView addSubview:_containerView]; } - (void)didSetProps:(NSArray *)changedProps { if ([changedProps containsObject:@"overdrag"]) { [_scrollView setBounces:_overdrag]; - } + } } - (void)shouldScroll:(BOOL)scrollEnabled { @@ -100,14 +100,17 @@ - (void) calculateContentSize { return; } - CGFloat width = initialView.frame.size.width * self.containerView.subviews.count; + CGFloat totalSubviewsWidth = initialView.frame.size.width * self.containerView.subviews.count; + CGFloat totalSubviewsHeight = initialView.frame.size.height * self.containerView.subviews.count; - if (_scrollView.contentSize.width == width) { - return; - } - _scrollView.contentSize = CGSizeMake(width, 0); - _containerView.frame = CGRectMake(0, 0, width, initialView.bounds.size.height); + 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]; @@ -137,7 +140,7 @@ - (void)goTo:(NSInteger)index animated:(BOOL)animated { } - (BOOL)isHorizontal { - return _scrollView.contentSize.width > _scrollView.contentSize.height; + return [_orientation isEqualToString:@"horizontal"]; } -(int)getCurrentPage { @@ -164,7 +167,6 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { -(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { int position = [self getCurrentPage]; - NSLog(@"position scrollViewDidEndScrollingAnimation %d", position); [self.eventDispatcher sendEvent:[[RCTOnPageSelected alloc] initWithReactTag:self.reactTag position:[NSNumber numberWithInt:position] coalescingKey:_coalescingKey++]]; } diff --git a/ios/ReactViewPagerManager.m b/ios/ReactViewPagerManager.m index 1e029527..e495dc1a 100644 --- a/ios/ReactViewPagerManager.m +++ b/ios/ReactViewPagerManager.m @@ -9,6 +9,7 @@ @implementation ReactViewPagerManager RCT_EXPORT_VIEW_PROPERTY(initialPage, NSInteger) RCT_EXPORT_VIEW_PROPERTY(pageMargin, NSInteger) +RCT_EXPORT_VIEW_PROPERTY(orientation, NSString) RCT_EXPORT_VIEW_PROPERTY(onPageSelected, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPageScroll, RCTDirectEventBlock) From 8286ec975234581b8d1fc9540ae829f506e7c498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Wed, 18 Jan 2023 18:20:57 +0100 Subject: [PATCH 5/8] fix: change way of disabing scroll --- ios/ReactNativePageView.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/ReactNativePageView.m b/ios/ReactNativePageView.m index a6a58726..15b4f97e 100644 --- a/ios/ReactNativePageView.m +++ b/ios/ReactNativePageView.m @@ -121,11 +121,11 @@ - (void) calculateContentSize { } - (void)disableSwipe { - self.scrollView.userInteractionEnabled = NO; + [_scrollView setScrollEnabled:false]; } - (void)enableSwipe { - self.scrollView.userInteractionEnabled = YES; + [_scrollView setScrollEnabled:true]; } - (void)goTo:(NSInteger)index animated:(BOOL)animated { From ba1bc80cda0debdc279613ab889d6bf3914a7443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 19 Jan 2023 11:58:14 +0100 Subject: [PATCH 6/8] feat: rename to RNCPagerView --- ios/{ReactNativePageView.h => RNCPagerView.h} | 2 +- ios/{ReactNativePageView.m => RNCPagerView.m} | 8 ++++---- ...iewPagerManager.h => RNCPagerViewManager.h} | 4 ++-- ...iewPagerManager.m => RNCPagerViewManager.m} | 18 +++++++++--------- 4 files changed, 16 insertions(+), 16 deletions(-) rename ios/{ReactNativePageView.h => RNCPagerView.h} (96%) rename ios/{ReactNativePageView.m => RNCPagerView.m} (97%) rename ios/{ReactViewPagerManager.h => RNCPagerViewManager.h} (67%) rename ios/{ReactViewPagerManager.m => RNCPagerViewManager.m} (78%) diff --git a/ios/ReactNativePageView.h b/ios/RNCPagerView.h similarity index 96% rename from ios/ReactNativePageView.h rename to ios/RNCPagerView.h index 6a0b8a96..93335b7e 100644 --- a/ios/ReactNativePageView.h +++ b/ios/RNCPagerView.h @@ -5,7 +5,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface ReactNativePageView: UIView +@interface RNCPagerView: UIView - (instancetype)initWithEventDispatcher:(id )eventDispatcher; diff --git a/ios/ReactNativePageView.m b/ios/RNCPagerView.m similarity index 97% rename from ios/ReactNativePageView.m rename to ios/RNCPagerView.m index 15b4f97e..b28ba9fc 100644 --- a/ios/ReactNativePageView.m +++ b/ios/RNCPagerView.m @@ -1,5 +1,5 @@ -#import "ReactNativePageView.h" +#import "RNCPagerView.h" #import "React/RCTLog.h" #import @@ -10,7 +10,7 @@ #import "RCTOnPageSelected.h" #import -@interface ReactNativePageView () +@interface RNCPagerView () @property(nonatomic, strong) id eventDispatcher; @@ -25,7 +25,7 @@ - (void)shouldDismissKeyboard:(NSString *)dismissKeyboard; @end -@implementation ReactNativePageView { +@implementation RNCPagerView { uint16_t _coalescingKey; } @@ -130,7 +130,7 @@ - (void)enableSwipe { - (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); - + [_scrollView setContentOffset:targetOffset animated:animated]; if (!animated) { 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 78% rename from ios/ReactViewPagerManager.m rename to ios/RNCPagerViewManager.m index e495dc1a..c169c23e 100644 --- a/ios/ReactViewPagerManager.m +++ b/ios/RNCPagerViewManager.m @@ -1,7 +1,7 @@ -#import "ReactViewPagerManager.h" +#import "RNCPagerViewManager.h" -@implementation ReactViewPagerManager +@implementation RNCPagerViewManager #pragma mark - RTC @@ -24,8 +24,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; } @@ -41,8 +41,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; } @@ -69,17 +69,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 From 0986a9beb5c665b056edcdbd2070c6db23d3c5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 19 Jan 2023 13:48:22 +0100 Subject: [PATCH 7/8] fix: removing last page --- ios/RNCPagerView.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ios/RNCPagerView.m b/ios/RNCPagerView.m index b28ba9fc..3d4c22dd 100644 --- a/ios/RNCPagerView.m +++ b/ios/RNCPagerView.m @@ -58,6 +58,10 @@ -(void) insertReactSubview:(UIView *)subview atIndex:(NSInteger)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 { From 0455cf073e98074b1ac11d2bc1f3971ef1d29fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Thu, 19 Jan 2023 18:37:25 +0100 Subject: [PATCH 8/8] fix: remove unused properties, set animated --- ios/RNCPagerView.h | 2 -- ios/RNCPagerView.m | 8 +++++--- ios/RNCPagerViewManager.m | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ios/RNCPagerView.h b/ios/RNCPagerView.h index 93335b7e..35325d9d 100644 --- a/ios/RNCPagerView.h +++ b/ios/RNCPagerView.h @@ -11,8 +11,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) NSInteger initialPage; @property(nonatomic) NSString* orientation; -@property(nonatomic) NSInteger currentIndex; -@property(nonatomic) NSInteger pageMargin; @property(nonatomic, readonly) UIScrollViewKeyboardDismissMode dismissKeyboard; @property(nonatomic, copy) RCTDirectEventBlock onPageSelected; @property(nonatomic, copy) RCTDirectEventBlock onPageScroll; diff --git a/ios/RNCPagerView.m b/ios/RNCPagerView.m index 3d4c22dd..50db8621 100644 --- a/ios/RNCPagerView.m +++ b/ios/RNCPagerView.m @@ -17,8 +17,6 @@ @interface RNCPagerView () @property(nonatomic, strong) UIScrollView *scrollView; @property(nonatomic, strong) UIView *containerView; -@property(nonatomic, assign) CGPoint lastContentOffset; - - (void)goTo:(NSInteger)index animated:(BOOL)animated; - (void)shouldScroll:(BOOL)scrollEnabled; - (void)shouldDismissKeyboard:(NSString *)dismissKeyboard; @@ -135,6 +133,10 @@ - (void)enableSwipe { - (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) { @@ -171,7 +173,7 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { -(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++]]; } diff --git a/ios/RNCPagerViewManager.m b/ios/RNCPagerViewManager.m index c169c23e..6ace49b9 100644 --- a/ios/RNCPagerViewManager.m +++ b/ios/RNCPagerViewManager.m @@ -8,7 +8,6 @@ @implementation RNCPagerViewManager 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(onPageSelected, RCTDirectEventBlock)