diff --git a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/en.lproj/MainStoryboard.storyboard b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/en.lproj/MainStoryboard.storyboard index a8a4805..0d54a12 100644 --- a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/en.lproj/MainStoryboard.storyboard +++ b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/en.lproj/MainStoryboard.storyboard @@ -1,5 +1,5 @@ - + @@ -8,7 +8,7 @@ - + diff --git a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m index 07a1235..1e0cbdf 100755 --- a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m +++ b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m @@ -28,6 +28,51 @@ typedef NS_ENUM(NSInteger, LXScrollingDirection) { static NSString * const kLXScrollingDirectionKey = @"LXScrollingDirection"; static NSString * const kLXCollectionViewKeyPath = @"collectionView"; +@interface UICollectionViewFlowLayout (LXPaging) + +- (CGPoint)LX_contentOffsetForPageIndex:(NSInteger)pageIndex; +- (NSInteger)LX_currentPageIndex; +- (NSInteger)LX_pageCount; + +@end + +@implementation UICollectionViewFlowLayout (LXPaging) + +- (NSInteger)LX_currentPageIndex { + switch (self.scrollDirection) { + case UICollectionViewScrollDirectionHorizontal: { + return round(self.collectionView.contentOffset.x / self.collectionView.frame.size.width); + } + case UICollectionViewScrollDirectionVertical: { + return round(self.collectionView.contentOffset.y / self.collectionView.frame.size.height); + } + } +} + +- (NSInteger)LX_pageCount { + switch (self.scrollDirection) { + case UICollectionViewScrollDirectionHorizontal: { + return ceil(self.collectionViewContentSize.width / self.collectionView.frame.size.width); + } + case UICollectionViewScrollDirectionVertical: { + return ceil(self.collectionViewContentSize.height / self.collectionView.frame.size.height); + } + } +} + +- (CGPoint)LX_contentOffsetForPageIndex:(NSInteger)pageIndex { + switch (self.scrollDirection) { + case UICollectionViewScrollDirectionHorizontal: { + return CGPointMake(self.collectionView.frame.size.width * pageIndex, 0); + } + case UICollectionViewScrollDirectionVertical: { + return CGPointMake(0, self.collectionView.frame.size.height * pageIndex); + } + } +} + +@end + @interface UICollectionViewCell (LXReorderableCollectionViewFlowLayout) - (UIImage *)LX_rasterizedImage; @@ -59,7 +104,9 @@ @interface LXReorderableCollectionViewFlowLayout () @end -@implementation LXReorderableCollectionViewFlowLayout +@implementation LXReorderableCollectionViewFlowLayout { + BOOL _pageScrollingDisabled; +} - (void)setDefaults { _scrollingSpeed = 300.0f; @@ -126,7 +173,8 @@ - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttribut } - (void)invalidateLayoutIfNecessary { - NSIndexPath *newIndexPath = [self.collectionView indexPathForItemAtPoint:self.currentView.center]; + const CGPoint point = [self.collectionView convertPoint:self.currentView.center fromView:self.collectionView.superview]; + NSIndexPath *newIndexPath = [self.collectionView indexPathForItemAtPoint:point]; NSIndexPath *previousIndexPath = self.selectedItemIndexPath; if ((newIndexPath == nil) || [newIndexPath isEqual:previousIndexPath]) { @@ -159,6 +207,85 @@ - (void)invalidatesScrollTimer { self.scrollingTimer = nil; } +- (void)scrollIfNecessary { + if (!self.currentView) return; // Prevent scrollToPageIndex to continue scrolling after the drag ended + + const CGPoint viewCenter = [self.collectionView convertPoint:self.currentView.center fromView:self.collectionView.superview]; + switch (self.scrollDirection) { + case UICollectionViewScrollDirectionVertical: { + if (viewCenter.y < (CGRectGetMinY(self.collectionView.bounds) + self.scrollingTriggerEdgeInsets.top)) { + [self scrollWithDirection:LXScrollingDirectionUp]; + } else { + if (viewCenter.y > (CGRectGetMaxY(self.collectionView.bounds) - self.scrollingTriggerEdgeInsets.bottom)) { + [self scrollWithDirection:LXScrollingDirectionDown]; + } else { + [self invalidatesScrollTimer]; + } + } + } break; + case UICollectionViewScrollDirectionHorizontal: { + if (viewCenter.x < (CGRectGetMinX(self.collectionView.bounds) + self.scrollingTriggerEdgeInsets.left)) { + [self scrollWithDirection:LXScrollingDirectionLeft]; + } else { + if (viewCenter.x > (CGRectGetMaxX(self.collectionView.bounds) - self.scrollingTriggerEdgeInsets.right)) { + [self scrollWithDirection:LXScrollingDirectionRight]; + } else { + [self invalidatesScrollTimer]; + } + } + } break; + } +} + +- (void)scrollToPreviousPage { + if (_pageScrollingDisabled) return; + const NSInteger currentPage = [self LX_currentPageIndex]; + if (currentPage <= 0) return; + const NSInteger newPage = currentPage - 1; + [self scrollToPageIndex:newPage forward:NO]; +} + +- (void)scrollToNextPage { + if (_pageScrollingDisabled) return; + const NSInteger currentPage = [self LX_currentPageIndex]; + const NSInteger pageCount = [self LX_pageCount]; + if (currentPage >= pageCount - 1) return; + const NSInteger newPage = currentPage + 1; + [self scrollToPageIndex:newPage forward:YES]; +} + +- (void)scrollToPageIndex:(NSInteger)pageIndex forward:(BOOL)forward { + const CGPoint offset = [self LX_contentOffsetForPageIndex:pageIndex]; + [self.collectionView setContentOffset:offset animated:YES]; + + // Wait a little bit before changing page again + _pageScrollingDisabled = YES; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ // Delay must be bigger than the contentOffset animation + _pageScrollingDisabled = NO; + [self scrollIfNecessary]; + }); +} + +- (void)scrollWithDirection:(LXScrollingDirection)direction { + if (self.collectionView.pagingEnabled) { + switch(direction) { + case LXScrollingDirectionUp: + case LXScrollingDirectionLeft: { + [self scrollToPreviousPage]; + } break; + case LXScrollingDirectionDown: + case LXScrollingDirectionRight: { + [self scrollToNextPage]; + } break; + default: { + // Do nothing... + } break; + } + } else { + [self setupScrollTimerInDirection:direction]; + } +} + - (void)setupScrollTimerInDirection:(LXScrollingDirection)direction { if (self.scrollingTimer.isValid) { LXScrollingDirection oldDirection = [self.scrollingTimer.userInfo[kLXScrollingDirectionKey] integerValue]; @@ -236,7 +363,6 @@ - (void)handleScroll:(NSTimer *)timer { } break; } - self.currentViewCenter = LXS_CGPointAdd(self.currentViewCenter, translation); self.currentView.center = LXS_CGPointAdd(self.currentViewCenter, self.panTranslationInCollectionView); self.collectionView.contentOffset = LXS_CGPointAdd(contentOffset, translation); } @@ -274,7 +400,9 @@ - (void)handleLongPressGesture:(UILongPressGestureRecognizer *)gestureRecognizer [self.currentView addSubview:imageView]; [self.currentView addSubview:highlightedImageView]; - [self.collectionView addSubview:self.currentView]; + const CGPoint center = [self.collectionView.superview convertPoint:collectionViewCell.center fromView:self.collectionView]; + self.currentView.center = center; + [self.collectionView.superview addSubview:self.currentView]; self.currentViewCenter = self.currentView.center; @@ -326,7 +454,8 @@ - (void)handleLongPressGesture:(UILongPressGestureRecognizer *)gestureRecognizer __strong typeof(self) strongSelf = weakSelf; if (strongSelf) { strongSelf.currentView.transform = CGAffineTransformMakeScale(1.0f, 1.0f); - strongSelf.currentView.center = layoutAttributes.center; + const CGPoint center = [strongSelf.collectionView.superview convertPoint:layoutAttributes.center fromView:strongSelf.collectionView]; + strongSelf.currentView.center = center; } } completion:^(BOOL finished) { @@ -352,41 +481,14 @@ - (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer { switch (gestureRecognizer.state) { case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateChanged: { - self.panTranslationInCollectionView = [gestureRecognizer translationInView:self.collectionView]; - CGPoint viewCenter = self.currentView.center = LXS_CGPointAdd(self.currentViewCenter, self.panTranslationInCollectionView); - + self.panTranslationInCollectionView = [gestureRecognizer translationInView:self.collectionView.superview]; + self.currentView.center = LXS_CGPointAdd(self.currentViewCenter, self.panTranslationInCollectionView); [self invalidateLayoutIfNecessary]; - - switch (self.scrollDirection) { - case UICollectionViewScrollDirectionVertical: { - if (viewCenter.y < (CGRectGetMinY(self.collectionView.bounds) + self.scrollingTriggerEdgeInsets.top)) { - [self setupScrollTimerInDirection:LXScrollingDirectionUp]; - } else { - if (viewCenter.y > (CGRectGetMaxY(self.collectionView.bounds) - self.scrollingTriggerEdgeInsets.bottom)) { - [self setupScrollTimerInDirection:LXScrollingDirectionDown]; - } else { - [self invalidatesScrollTimer]; - } - } - } break; - case UICollectionViewScrollDirectionHorizontal: { - if (viewCenter.x < (CGRectGetMinX(self.collectionView.bounds) + self.scrollingTriggerEdgeInsets.left)) { - [self setupScrollTimerInDirection:LXScrollingDirectionLeft]; - } else { - if (viewCenter.x > (CGRectGetMaxX(self.collectionView.bounds) - self.scrollingTriggerEdgeInsets.right)) { - [self setupScrollTimerInDirection:LXScrollingDirectionRight]; - } else { - [self invalidatesScrollTimer]; - } - } - } break; - } - } break; - case UIGestureRecognizerStateEnded: { - [self invalidatesScrollTimer]; + [self scrollIfNecessary]; } break; + case UIGestureRecognizerStateEnded: default: { - // Do nothing... + [self invalidatesScrollTimer]; } break; } }