diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ae1f5792..26dfc15c 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -91,7 +91,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDelegate; @@ -605,8 +605,45 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; cellNode.scrollView = nil; } -#pragma mark - -#pragma mark Scroll Direction. + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + // If a scroll happenes the current range mode needs to go to full + ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; + if (ASInterfaceStateIncludesVisible(interfaceState)) { + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + } + + for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { + // Only nodes that respond to the selector are added to _cellsForVisibilityUpdates + [[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged + inScrollView:scrollView + withCellFrame:collectionCell.frame]; + } + if (_asyncDelegateImplementsScrollviewDidScroll) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset +{ + _deceleratingVelocity = CGPointMake( + scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), + scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) + ); + + if (targetContentOffset != NULL) { + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; + } + + if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { + [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; + } +} + + +#pragma mark - Scroll Direction. - (ASScrollDirection)scrollDirection { @@ -700,45 +737,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } -#pragma mark - -#pragma mark Batch Fetching +#pragma mark - Batch Fetching -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset +- (ASBatchContext *)batchContext { - _deceleratingVelocity = CGPointMake( - scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) - ); - - if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; - } - - if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } + return _batchContext; } -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - // If a scroll happenes the current range mode needs to go to full - ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; - } - - for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) { - // Only nodes that respond to the selector are added to _cellsForVisibilityUpdates - [[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged - inScrollView:scrollView - withCellFrame:collectionCell.frame]; - } - if (_asyncDelegateImplementsScrollviewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - -- (BOOL)shouldBatchFetch +- (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; @@ -749,16 +755,37 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_scheduleCheckForBatchFetching { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - - if (![self shouldBatchFetch]) { + // Push this to the next runloop to be sure the scroll view has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); +} + +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if (self.isDragging || self.isTracking) { return; } - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; +} + +- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +{ + if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { + [self _beginBatchFetching]; + } +} + +- (void)_beginBatchFetching +{ + NSLog(@"begin batch fetching"); + + [_batchContext beginBatchFetching]; + if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; }); @@ -954,7 +981,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; for (dispatch_block_t block in _batchUpdateBlocks) { block(); } - } completion:completion]; + } completion:^(BOOL finished){ + [self _scheduleCheckForBatchFetching]; + if (completion) { completion(finished); } + }]; }); [_batchUpdateBlocks removeAllObjects]; @@ -977,6 +1007,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super insertItemsAtIndexPaths:indexPaths]; + [self _scheduleCheckForBatchFetching]; }]; } } @@ -997,6 +1028,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super deleteItemsAtIndexPaths:indexPaths]; + [self _scheduleCheckForBatchFetching]; }]; } } @@ -1017,6 +1049,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super insertSections:indexSet]; + [self _scheduleCheckForBatchFetching]; }]; } } @@ -1037,6 +1070,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super deleteSections:indexSet]; + [self _scheduleCheckForBatchFetching]; }]; } } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index bf9a3522..1a372084 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -88,9 +88,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)_initWithTableView:(ASTableView *)tableView; @end -@interface ASTableView () +@interface ASTableView () { ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; @@ -524,8 +522,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -#pragma mark - -#pragma mark Intercepted selectors + +#pragma mark - Intercepted selectors - (void)setTableHeaderView:(UIView *)tableHeaderView { @@ -579,47 +577,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return [_dataController numberOfRowsInSection:section]; } -- (ASScrollDirection)scrollDirection -{ - CGPoint scrollVelocity; - if (self.isTracking) { - scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; - } else { - scrollVelocity = _deceleratingVelocity; - } - ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; - return ASScrollDirectionApplyTransform(scrollDirection, self.transform); -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)velocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - if (velocity.y < 0.0) { - direction = ASScrollDirectionDown; - } else if (velocity.y > 0.0) { - direction = ASScrollDirectionUp; - } - return direction; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - // If a scroll happenes the current range mode needs to go to full - ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; - } - - for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { - [[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged - inScrollView:scrollView - withCellFrame:tableCell.frame]; - } - if (_asyncDelegateImplementsScrollviewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { _pendingVisibleIndexPath = indexPath; @@ -631,7 +588,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; } - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; if (cellNode.neverShowPlaceholders) { [cellNode recursivelyEnsureDisplaySynchronously:YES]; @@ -650,7 +607,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASCellNode *cellNode = [cell node]; - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]) { ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); @@ -672,8 +629,23 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } -#pragma mark - -#pragma mark Batch Fetching +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + // If a scroll happenes the current range mode needs to go to full + ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; + if (ASInterfaceStateIncludesVisible(interfaceState)) { + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + } + + for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { + [[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged + inScrollView:scrollView + withCellFrame:tableCell.frame]; + } + if (_asyncDelegateImplementsScrollviewDidScroll) { + [_asyncDelegate scrollViewDidScroll:scrollView]; + } +} - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { @@ -683,7 +655,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ); if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -691,7 +664,62 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (BOOL)shouldBatchFetch + +#pragma mark - Scroll Direction + +- (ASScrollDirection)scrollDirection +{ + CGPoint scrollVelocity; + if (self.isTracking) { + scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; + } else { + scrollVelocity = _deceleratingVelocity; + } + + ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; + return ASScrollDirectionApplyTransform(scrollDirection, self.transform); +} + +- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity +{ + ASScrollDirection direction = ASScrollDirectionNone; + ASScrollDirection scrollableDirections = [self scrollableDirections]; + + if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. + if (scrollVelocity.y < 0.0) { + direction |= ASScrollDirectionDown; + } else if (scrollVelocity.y > 0.0) { + direction |= ASScrollDirectionUp; + } + } + + return direction; +} + +- (ASScrollDirection)scrollableDirections +{ + ASScrollDirection scrollableDirection = ASScrollDirectionNone; + CGFloat totalContentWidth = self.contentSize.width + self.contentInset.left + self.contentInset.right; + CGFloat totalContentHeight = self.contentSize.height + self.contentInset.top + self.contentInset.bottom; + + if (self.alwaysBounceHorizontal || totalContentWidth > self.bounds.size.width) { // Can scroll horizontally. + scrollableDirection |= ASScrollDirectionHorizontalDirections; + } + if (self.alwaysBounceVertical || totalContentHeight > self.bounds.size.height) { // Can scroll vertically. + scrollableDirection |= ASScrollDirectionVerticalDirections; + } + return scrollableDirection; +} + + +#pragma mark - Batch Fetching + +- (ASBatchContext *)batchContext +{ + return _batchContext; +} + +- (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; @@ -702,16 +730,35 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_scheduleCheckForBatchFetching { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + // Push this to the next runloop to be sure the scroll view has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); +} - if (![self shouldBatchFetch]) { +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if (self.isDragging || self.isTracking) { return; } + + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; +} - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; +- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +{ + if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { + [self _beginBatchFetching]; + } +} + +- (void)_beginBatchFetching +{ + [_batchContext beginBatchFetching]; + if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; }); @@ -857,6 +904,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetching]; }); if (_automaticallyAdjustsContentOffset) { @@ -876,6 +924,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetching]; }); if (_automaticallyAdjustsContentOffset) { @@ -896,6 +945,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetching]; }); } @@ -911,6 +961,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + [self _scheduleCheckForBatchFetching]; }); } @@ -1077,7 +1128,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass if (![node supportsRangeManagedInterfaceState]) { - [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; + [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]]; } } diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/AsyncDisplayKit/Details/ASBatchContext.mm index 4833cbd8..4cb5b96f 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/AsyncDisplayKit/Details/ASBatchContext.mm @@ -45,6 +45,12 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { return _state == ASBatchContextStateCancelled; } +- (void)beginBatchFetching +{ + ASDN::MutexLocker l(_propertyLock); + _state = ASBatchContextStateFetching; +} + - (void)completeBatchFetching:(BOOL)didComplete { if (didComplete) { @@ -53,12 +59,6 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { } } -- (void)beginBatchFetching -{ - ASDN::MutexLocker l(_propertyLock); - _state = ASBatchContextStateFetching; -} - - (void)cancelBatchFetching { ASDN::MutexLocker l(_propertyLock); diff --git a/AsyncDisplayKit/Private/ASBatchFetching.h b/AsyncDisplayKit/Private/ASBatchFetching.h index 9aeea5ad..094a2c6c 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.h +++ b/AsyncDisplayKit/Private/ASBatchFetching.h @@ -10,10 +10,29 @@ #import "ASBatchContext.h" #import "ASScrollDirection.h" -#import "ASBaseDefines.h" ASDISPLAYNODE_EXTERN_C_BEGIN +@protocol ASBatchFetchingScrollView + +- (BOOL)canBatchFetch; +- (ASBatchContext *)batchContext; +- (CGFloat)leadingScreensForBatching; + +@end + +/** + @abstract Determine if batch fetching should begin based on the state of the parameters. + @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and + * ASCollectionView batch fetching API. + @param context The scroll view that in-flight fetches are happening. + @param scrollDirection The current scrolling direction of the scroll view. + @param targetOffset The offset that the scrollview will scroll to. + @return Whether or not the current state should proceed with batch fetching. + */ +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset); + + /** @abstract Determine if batch fetching should begin based on the state of the parameters. @param context The batch fetching context that contains knowledge about in-flight fetches. diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index b2a471e2..6a8d9fc1 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -8,34 +8,50 @@ #import "ASBatchFetching.h" +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset) +{ + // Don't fetch if the scroll view does not allow + if (![scrollView canBatchFetch]) { + return NO; + } + + // Check if we should batch fetch + ASBatchContext *context = scrollView.batchContext; + CGRect bounds = scrollView.bounds; + CGSize contentSize = scrollView.contentSize; + CGFloat leadingScreens = scrollView.leadingScreensForBatching; + return ASDisplayShouldFetchBatchForContext(context, scrollDirection, bounds, contentSize, contentOffset, leadingScreens); +} + BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens) { - // do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled + ASScrollDirection scrollDirection, + CGRect bounds, + CGSize contentSize, + CGPoint targetOffset, + CGFloat leadingScreens) +{ + // Do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled if ([context isFetching]) { return NO; } - // only Down and Right scrolls are currently supported (tail loading) - if (scrollDirection != ASScrollDirectionDown && scrollDirection != ASScrollDirectionRight) { + // Only Down and Right scrolls are currently supported (tail loading) + if (!ASScrollDirectionContainsDown(scrollDirection) && !ASScrollDirectionContainsRight(scrollDirection)) { return NO; } - // no fetching for null states + // No fetching for null states if (leadingScreens <= 0.0 || CGRectEqualToRect(bounds, CGRectZero)) { return NO; } CGFloat viewLength, offset, contentLength; - if (scrollDirection == ASScrollDirectionDown) { + if (ASScrollDirectionContainsDown(scrollDirection)) { viewLength = bounds.size.height; offset = targetOffset.y; contentLength = contentSize.height; - } else { // horizontal + } else { // horizontal / right viewLength = bounds.size.width; offset = targetOffset.x; contentLength = contentSize.width;