diff --git a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard.xcodeproj/project.pbxproj b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard.xcodeproj/project.pbxproj index 485eb33..f9bd4ed 100644 --- a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard.xcodeproj/project.pbxproj +++ b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 927A97F41736CA0E00539F5E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 927A97F31736CA0E00539F5E /* QuartzCore.framework */; }; AB0E97C7161B4BEC005498A0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB0E97C6161B4BEC005498A0 /* UIKit.framework */; }; AB0E97C9161B4BEC005498A0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB0E97C8161B4BEC005498A0 /* Foundation.framework */; }; AB0E97CB161B4BEC005498A0 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB0E97CA161B4BEC005498A0 /* CoreGraphics.framework */; }; @@ -25,6 +26,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 927A97F31736CA0E00539F5E /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; AB0E97C2161B4BEC005498A0 /* LXRCVFL Example using Storyboard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LXRCVFL Example using Storyboard.app"; sourceTree = BUILT_PRODUCTS_DIR; }; AB0E97C6161B4BEC005498A0 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; AB0E97C8161B4BEC005498A0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -55,6 +57,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 927A97F41736CA0E00539F5E /* QuartzCore.framework in Frameworks */, AB0E97C7161B4BEC005498A0 /* UIKit.framework in Frameworks */, AB0E97C9161B4BEC005498A0 /* Foundation.framework in Frameworks */, AB0E97CB161B4BEC005498A0 /* CoreGraphics.framework in Frameworks */, @@ -86,6 +89,7 @@ AB0E97C5161B4BEC005498A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 927A97F31736CA0E00539F5E /* QuartzCore.framework */, AB0E97C6161B4BEC005498A0 /* UIKit.framework */, AB0E97C8161B4BEC005498A0 /* Foundation.framework */, AB0E97CA161B4BEC005498A0 /* CoreGraphics.framework */, diff --git a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.h b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.h index c39915b..6657ba1 100644 --- a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.h +++ b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.h @@ -9,7 +9,7 @@ #import #import "LXReorderableCollectionViewFlowLayout.h" -@interface LXCollectionViewController : UICollectionViewController +@interface LXCollectionViewController : UICollectionViewController @property (strong, nonatomic) NSMutableArray *deck; diff --git a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.m b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.m index afef591..a899026 100644 --- a/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.m +++ b/LXRCVFL Example using Storyboard/LXRCVFL Example using Storyboard/LXCollectionViewController.m @@ -78,7 +78,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell return playingCardCell; } -#pragma mark - LXReorderableCollectionViewDatasource methods +#pragma mark - LXReorderableCollectionViewDataSource methods - (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath { PlayingCard *playingCard = [self.deck objectAtIndex:fromIndexPath.item]; diff --git a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.h b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.h index e54ce58..2574d00 100755 --- a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.h +++ b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.h @@ -11,19 +11,20 @@ @property (assign, nonatomic) CGFloat scrollingSpeed; @property (assign, nonatomic) UIEdgeInsets scrollingTriggerEdgeInsets; -@property (weak, nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer; -@property (weak, nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer; +@property (strong, nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer; +@property (strong, nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer; - (void)setUpGestureRecognizersOnCollectionView __attribute__((deprecated("Calls to setUpGestureRecognizersOnCollectionView method are not longer needed as setup are done automatically through KVO."))); @end -@protocol LXReorderableCollectionViewDatasource - -- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath; +@protocol LXReorderableCollectionViewDataSource @optional +- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath; +- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath didMoveToIndexPath:(NSIndexPath *)toIndexPath; + - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath; - (BOOL)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath canMoveToIndexPath:(NSIndexPath *)toIndexPath; @@ -37,4 +38,7 @@ - (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout willEndDraggingItemAtIndexPath:(NSIndexPath *)indexPath; - (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout didEndDraggingItemAtIndexPath:(NSIndexPath *)indexPath; +- (void)collectionViewScrollToNextPage:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout; +- (void)collectionViewScrollToPreviousPage:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout; + @end \ No newline at end of file diff --git a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m index 1e0cbdf..aa2b995 100755 --- a/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m +++ b/LXReorderableCollectionViewFlowLayout/LXReorderableCollectionViewFlowLayout.m @@ -7,6 +7,7 @@ #import "LXReorderableCollectionViewFlowLayout.h" #import +#import #define LX_FRAMES_PER_SECOND 60.0 @@ -73,6 +74,20 @@ - (CGPoint)LX_contentOffsetForPageIndex:(NSInteger)pageIndex { @end +@interface CADisplayLink (LX_userInfo) +@property (nonatomic, copy) NSDictionary *LX_userInfo; +@end + +@implementation CADisplayLink (LX_userInfo) +- (void) setLX_userInfo:(NSDictionary *) LX_userInfo { + objc_setAssociatedObject(self, "LX_userInfo", LX_userInfo, OBJC_ASSOCIATION_COPY); +} + +- (NSDictionary *) LX_userInfo { + return objc_getAssociatedObject(self, "LX_userInfo"); +} +@end + @interface UICollectionViewCell (LXReorderableCollectionViewFlowLayout) - (UIImage *)LX_rasterizedImage; @@ -97,9 +112,9 @@ @interface LXReorderableCollectionViewFlowLayout () @property (strong, nonatomic) UIView *currentView; @property (assign, nonatomic) CGPoint currentViewCenter; @property (assign, nonatomic) CGPoint panTranslationInCollectionView; -@property (strong, nonatomic) NSTimer *scrollingTimer; +@property (strong, nonatomic) CADisplayLink *displayLink; -@property (assign, nonatomic, readonly) id dataSource; +@property (assign, nonatomic, readonly) id dataSource; @property (assign, nonatomic, readonly) id delegate; @end @@ -114,25 +129,24 @@ - (void)setDefaults { } - (void)setupCollectionView { - UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self - action:@selector(handleLongPressGesture:)]; - longPressGestureRecognizer.delegate = self; - [self.collectionView addGestureRecognizer:longPressGestureRecognizer]; + _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self + action:@selector(handleLongPressGesture:)]; + _longPressGestureRecognizer.delegate = self; // Links the default long press gesture recognizer to the custom long press gesture recognizer we are creating now // by enforcing failure dependency so that they doesn't clash. for (UIGestureRecognizer *gestureRecognizer in self.collectionView.gestureRecognizers) { if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { - [gestureRecognizer requireGestureRecognizerToFail:longPressGestureRecognizer]; + [gestureRecognizer requireGestureRecognizerToFail:_longPressGestureRecognizer]; } } - _longPressGestureRecognizer = longPressGestureRecognizer; - UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self - action:@selector(handlePanGesture:)]; - panGestureRecognizer.delegate = self; - [self.collectionView addGestureRecognizer:panGestureRecognizer]; - _panGestureRecognizer = panGestureRecognizer; + [self.collectionView addGestureRecognizer:_longPressGestureRecognizer]; + + _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self + action:@selector(handlePanGesture:)]; + _panGestureRecognizer.delegate = self; + [self.collectionView addGestureRecognizer:_panGestureRecognizer]; } - (id)init { @@ -164,8 +178,8 @@ - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttribut } } -- (id)dataSource { - return (id)self.collectionView.dataSource; +- (id)dataSource { + return (id)self.collectionView.dataSource; } - (id)delegate { @@ -188,8 +202,10 @@ - (void)invalidateLayoutIfNecessary { self.selectedItemIndexPath = newIndexPath; - [self.dataSource collectionView:self.collectionView itemAtIndexPath:previousIndexPath willMoveToIndexPath:newIndexPath]; - + if ([self.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:willMoveToIndexPath:)]) { + [self.dataSource collectionView:self.collectionView itemAtIndexPath:previousIndexPath willMoveToIndexPath:newIndexPath]; + } + __weak typeof(self) weakSelf = self; [self.collectionView performBatchUpdates:^{ __strong typeof(self) strongSelf = weakSelf; @@ -197,14 +213,19 @@ - (void)invalidateLayoutIfNecessary { [strongSelf.collectionView deleteItemsAtIndexPaths:@[ previousIndexPath ]]; [strongSelf.collectionView insertItemsAtIndexPaths:@[ newIndexPath ]]; } - } completion:nil]; + } completion:^(BOOL finished) { + __strong typeof(self) strongSelf = weakSelf; + if ([strongSelf.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:didMoveToIndexPath:)]) { + [strongSelf.dataSource collectionView:strongSelf.collectionView itemAtIndexPath:previousIndexPath didMoveToIndexPath:newIndexPath]; + } + }]; } - (void)invalidatesScrollTimer { - if (self.scrollingTimer.isValid) { - [self.scrollingTimer invalidate]; + if (!self.displayLink.paused) { + [self.displayLink invalidate]; } - self.scrollingTimer = nil; + self.displayLink = nil; } - (void)scrollIfNecessary { @@ -271,11 +292,17 @@ - (void)scrollWithDirection:(LXScrollingDirection)direction { switch(direction) { case LXScrollingDirectionUp: case LXScrollingDirectionLeft: { - [self scrollToPreviousPage]; + if([self.delegate respondsToSelector:@selector(collectionViewScrollToPreviousPage:layout:)]) + [self.delegate collectionViewScrollToPreviousPage:self.collectionView layout:self]; + else + [self scrollToPreviousPage]; } break; case LXScrollingDirectionDown: case LXScrollingDirectionRight: { - [self scrollToNextPage]; + if([self.delegate respondsToSelector:@selector(collectionViewScrollToNextPage:layout:)]) + [self.delegate collectionViewScrollToNextPage:self.collectionView layout:self]; + else + [self scrollToNextPage]; } break; default: { // Do nothing... @@ -287,28 +314,27 @@ - (void)scrollWithDirection:(LXScrollingDirection)direction { } - (void)setupScrollTimerInDirection:(LXScrollingDirection)direction { - if (self.scrollingTimer.isValid) { - LXScrollingDirection oldDirection = [self.scrollingTimer.userInfo[kLXScrollingDirectionKey] integerValue]; - + if (!self.displayLink.paused) { + LXScrollingDirection oldDirection = [self.displayLink.LX_userInfo[kLXScrollingDirectionKey] integerValue]; + if (direction == oldDirection) { return; } } [self invalidatesScrollTimer]; - - self.scrollingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / LX_FRAMES_PER_SECOND - target:self - selector:@selector(handleScroll:) - userInfo:@{ kLXScrollingDirectionKey : @(direction) } - repeats:YES]; + + self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleScroll:)]; + self.displayLink.LX_userInfo = @{ kLXScrollingDirectionKey : @(direction) }; + + [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } #pragma mark - Target/Action methods // Tight loop, allocate memory sparely, even if they are stack allocation. -- (void)handleScroll:(NSTimer *)timer { - LXScrollingDirection direction = (LXScrollingDirection)[timer.userInfo[kLXScrollingDirectionKey] integerValue]; +- (void)handleScroll:(CADisplayLink *)displayLink { + LXScrollingDirection direction = (LXScrollingDirection)[displayLink.LX_userInfo[kLXScrollingDirectionKey] integerValue]; if (direction == LXScrollingDirectionUnknown) { return; } diff --git a/README.md b/README.md index 5ab50ef..ef79823 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,12 @@ Credits - Refactored by [Luke Scott](https://github.com/lukescott), with some help from [mulle-nat's fork](https://github.com/mulle-nat/LXReorderableCollectionViewFlowLayout). - Playing cards in the demo are downloaded from [http://www.jfitz.com/cards/](http://www.jfitz.com/cards/). +Alternatives +============ + +- [DraggableCollectionView](https://github.com/lukescott/DraggableCollectionView) by [Luke Scott](https://github.com/lukescott). + License ======= -LXReorderableCollectionViewFlowLayout is available under the MIT license. +LXReorderableCollectionViewFlowLayout is available under [the MIT license](LICENSE).