From 3e8ea64a1b1b47e4aa925ef85973e1e40ac8c718 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 29 Apr 2016 07:50:53 -0700 Subject: [PATCH] Update to latest state --- AsyncDisplayKit/ASPagerFlowLayout.h | 11 ---- AsyncDisplayKit/ASPagerFlowLayout.m | 93 ++++++++++++++-------------- AsyncDisplayKit/ASPagerNode.h | 1 - AsyncDisplayKit/ASPagerNode.m | 94 +++++++---------------------- 4 files changed, 72 insertions(+), 127 deletions(-) diff --git a/AsyncDisplayKit/ASPagerFlowLayout.h b/AsyncDisplayKit/ASPagerFlowLayout.h index c689cad85d..9b784107a6 100644 --- a/AsyncDisplayKit/ASPagerFlowLayout.h +++ b/AsyncDisplayKit/ASPagerFlowLayout.h @@ -8,17 +8,6 @@ #import -@class ASPagerNode; - -@protocol ASPagerFlowLayoutPageProvider - -/// Provides the current page index to the ASPagerFlowLayout -- (NSInteger)currentPageIndex; - -@end - @interface ASPagerFlowLayout : UICollectionViewFlowLayout -- (instancetype)initWithPageProvider:(id)pageProvider; - @end diff --git a/AsyncDisplayKit/ASPagerFlowLayout.m b/AsyncDisplayKit/ASPagerFlowLayout.m index 5c359db0b9..40a7970e50 100644 --- a/AsyncDisplayKit/ASPagerFlowLayout.m +++ b/AsyncDisplayKit/ASPagerFlowLayout.m @@ -7,73 +7,78 @@ // #import "ASPagerFlowLayout.h" -#import "ASPagerNode.h" @interface ASPagerFlowLayout () @property (strong, nonatomic) NSIndexPath *currentIndexPath; -@property (weak, nonatomic) id pageProvider; @end -@implementation ASPagerFlowLayout - -#pragma mark - Lifecycle - -- (instancetype)initWithPageProvider:(id)pageProvider -{ - self = [super init]; - if (self == nil) { return self; } - _pageProvider = pageProvider; - return self; +@implementation ASPagerFlowLayout { + BOOL _didRotate; + CGRect _cachedCollectionViewBounds; } -#pragma mark - UICollectionViewFlowLayout - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds +- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { - CGRect oldBounds = self.collectionView.bounds; - if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) { - return YES; - } + NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width); + self.currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0]; - return NO; + return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity]; } + +- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds +{ + // Cache the current page if a rotation did happen. This happens before the rotation animation + // is occuring and the bounds changed so we use this as an opportunity to cache the current index path + if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) { + _cachedCollectionViewBounds = self.collectionView.bounds; + + // Figurring out current page based on the old bounds visible space + CGRect visibleRect = oldBounds; + + CGFloat visibleXCenter = CGRectGetMidX(visibleRect); + NSArray *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect]; + for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { + if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) { + self.currentIndexPath = attributes.indexPath; + break; + } + } + + _didRotate = YES; + } + + [super prepareForAnimatedBoundsChange:oldBounds]; +} - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset +{ + // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should + // try to use the current index path to not end up setting the target content offset to something in between pages + if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) { + _didRotate = NO; + if (self.currentIndexPath) { + return [self _targetContentOffsetForItemAtIndexPath:self.currentIndexPath proposedContentOffset:proposedContentOffset]; + } + } + + return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; +} + +- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset { if ([self _dataSourceIsEmpty]) { return proposedContentOffset; } - if (_pageProvider == nil || [self _visibleRectIsInvalid]) { - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; - } - - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:_pageProvider.currentPageIndex inSection:0]; - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; - CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2; - return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); + CGFloat currentPageXOffset = self.currentIndexPath.item * CGRectGetWidth(self.collectionView.bounds); + return CGPointMake(currentPageXOffset, proposedContentOffset.y); } -#pragma mark - Helper - - (BOOL)_dataSourceIsEmpty { - return ([self.collectionView numberOfSections] == 0 || [self.collectionView numberOfItemsInSection:0] == 0); -} - -- (CGRect)_visibleRect -{ - CGRect visibleRect; - visibleRect.origin = self.collectionView.contentOffset; - visibleRect.size = self.collectionView.bounds.size; - return visibleRect; -} - -- (BOOL)_visibleRectIsInvalid -{ - return CGRectEqualToRect([self _visibleRect], CGRectZero); + return ([self.collectionView numberOfSections] == 0 || [self.collectionView numberOfItemsInSection:0] == 0); } @end diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 4fae1d117e..f12499ce91 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -96,4 +96,3 @@ - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; @end - diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 52009ef607..e8a51ee7bd 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -12,26 +12,25 @@ #import "ASPagerFlowLayout.h" #import "UICollectionViewLayout+ASConvenience.h" -@interface ASPagerNode () +@interface ASPagerNode () { ASPagerFlowLayout *_flowLayout; - ASPagerNodeProxy *_dataSourceProxy; - ASPagerNodeProxy *_delegateProxy; + ASPagerNodeProxy *_proxy; __weak id _pagerDataSource; BOOL _pagerDataSourceImplementsNodeBlockAtIndex; BOOL _pagerDataSourceImplementsConstrainedSizeForNode; } -@property (nonatomic, assign, readonly) NSInteger numberOfPages; - @end @implementation ASPagerNode @dynamic view, delegate, dataSource; +#pragma mark - Lifecycle + - (instancetype)init { - ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] initWithPageProvider:self]; + ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] init]; flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; flowLayout.minimumInteritemSpacing = 0; flowLayout.minimumLineSpacing = 0; @@ -45,11 +44,12 @@ self = [super initWithCollectionViewLayout:flowLayout]; if (self != nil) { _flowLayout = flowLayout; - _currentPageIndex = 0; } return self; } +#pragma mark - ASDisplayNode + - (void)didLoad { [super didLoad]; @@ -67,10 +67,6 @@ // our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning. // From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets). cv.zeroContentInsets = YES; - - // Set the super delegate to the pager for now to inject the scroll delegate calls. If the API consumer - // set's the delegate on the ASPagerNode we add an ASPagerNodeProxy in between in setDelegate: - super.delegate = self; ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 }; ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; @@ -83,34 +79,23 @@ [self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeFetchData]; } -#pragma mark - Getter / Setter +#pragma mark - Getters / Setters -- (NSInteger)numberOfPages +- (NSInteger)currentPageIndex { - return [_pagerDataSource numberOfPagesInPagerNode:self]; + return (self.view.contentOffset.x / CGRectGetWidth(self.view.bounds)); } #pragma mark - Helpers - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated { - // Prevent an exception to scroll to an index path that is invalid - if (index >= 0 && index < self.numberOfPages) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; - [self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated]; - - _currentPageIndex = index; - } + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; + [self.view scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated]; } #pragma mark - ASCollectionViewDataSource -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section -{ - ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); - return self.numberOfPages; -} - - (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); @@ -121,6 +106,12 @@ return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; } +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); + return [_pagerDataSource numberOfPagesInPagerNode:self]; +} + - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { if (_pagerDataSourceImplementsConstrainedSizeForNode) { @@ -129,7 +120,7 @@ return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); } -#pragma mark - Proxies +#pragma mark - Data Source Proxy - (id )dataSource { @@ -142,59 +133,20 @@ _pagerDataSource = pagerDataSource; _pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; - _pagerDataSourceImplementsConstrainedSizeForNode = [_pagerDataSource respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)]; - // Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex: ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]); - _dataSourceProxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; + _pagerDataSourceImplementsConstrainedSizeForNode = [_pagerDataSource respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)]; - super.dataSource = (id )_dataSourceProxy; + _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; + + super.dataSource = (id )_proxy; } } -- (void)setDelegate:(id)delegate -{ - _delegateProxy = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; - - super.delegate = (id )_delegateProxy; -} - - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy { [self setDataSource:nil]; - [self setDelegate:nil]; -} - -#pragma mark - - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - CGFloat pageWidth = CGRectGetWidth(self.view.frame); - _currentPageIndex = floor((self.view.contentOffset.x - pageWidth / 2) / pageWidth) + 1; -} - -- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint*)targetContentOffset -{ - CGFloat pageWidth = CGRectGetWidth(self.view.frame); - NSInteger newPageIndex = _currentPageIndex; - - if (velocity.x == 0) { - // Handle slow dragging not lifting finger - newPageIndex = floor((targetContentOffset->x - pageWidth / 2) / pageWidth) + 1; - } else { - newPageIndex = velocity.x > 0 ? _currentPageIndex + 1 : _currentPageIndex - 1; - - if (newPageIndex < 0) { - newPageIndex = 0; - } - if (newPageIndex > self.view.contentSize.width / pageWidth) { - newPageIndex = ceil(self.view.contentSize.width / pageWidth) - 1.0; - } - } - _currentPageIndex = newPageIndex; - - *targetContentOffset = CGPointMake(newPageIndex * pageWidth, targetContentOffset->y); } @end