From 4275621bf6e617c3f449a477deb2d6359c3120b8 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 1 Apr 2013 15:09:29 +0200 Subject: [PATCH 1/4] Add currentView to collectionView.superview instead of collectionView With this change we don't need to update currentView.center on each pan. This will allow us to change the page with a nice animation when collectionView.pagingEnabled. --- .../LXReorderableCollectionViewFlowLayout.m | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m index 07a1235..924df42 100755 --- a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m +++ b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m @@ -126,7 +126,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]) { @@ -236,7 +237,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 +274,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 +328,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,8 +355,9 @@ - (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); + const CGPoint viewCenter = [self.collectionView convertPoint:self.currentView.center fromView:self.collectionView.superview]; [self invalidateLayoutIfNecessary]; From 95c3a49ce57855a80788ab4c77dc4e30042ff5da Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 1 Apr 2013 16:08:24 +0200 Subject: [PATCH 2/4] Springboard-like page scrolling --- .../en.lproj/MainStoryboard.storyboard | 4 +- .../LXReorderableCollectionViewFlowLayout.m | 160 ++++++++++++++---- 2 files changed, 129 insertions(+), 35 deletions(-) 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 924df42..c409907 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; @@ -160,6 +207,81 @@ - (void)invalidatesScrollTimer { self.scrollingTimer = nil; } +- (void)scrollIfNecessary { + 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]; + _pageScrollingDisabled = YES; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + _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]; @@ -356,41 +478,13 @@ - (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer { case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateChanged: { self.panTranslationInCollectionView = [gestureRecognizer translationInView:self.collectionView.superview]; - self.currentView.center = LXS_CGPointAdd(self.currentViewCenter, self.panTranslationInCollectionView); - const CGPoint viewCenter = [self.collectionView convertPoint:self.currentView.center fromView: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; } } From c0afbbc8e9aba5d525ed6e780d6f7b06036e3b93 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 1 Apr 2013 18:26:04 +0200 Subject: [PATCH 3/4] Prevent scrollToPageIndex to continue scrolling after the drag ended --- .../LXReorderableCollectionViewFlowLayout.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m index c409907..a630c26 100755 --- a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m +++ b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m @@ -208,6 +208,8 @@ - (void)invalidatesScrollTimer { } - (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: { From 9f6b6152f412d031c2ec5225222a4344d69a1fd5 Mon Sep 17 00:00:00 2001 From: Hermes Pique Date: Mon, 1 Apr 2013 18:29:48 +0200 Subject: [PATCH 4/4] Add comments --- .../LXReorderableCollectionViewFlowLayout.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m index a630c26..1e0cbdf 100755 --- a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m +++ b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m @@ -257,8 +257,10 @@ - (void)scrollToNextPage { - (void)scrollToPageIndex:(NSInteger)pageIndex forward:(BOOL)forward { const CGPoint offset = [self LX_contentOffsetForPageIndex:pageIndex]; [self.collectionView setContentOffset:offset animated:YES]; - _pageScrollingDisabled = YES; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + // 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]; });