From c25a252e1c62256e40b08d3f1b6d9c730c1515a1 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 4 Apr 2016 20:51:24 -0700 Subject: [PATCH 01/10] Fix fetch call won't occur for content smaller than bounds unless user scrolls --- AsyncDisplayKit/ASCollectionView.mm | 40 ++++++-- AsyncDisplayKit/ASTableView.mm | 108 +++++++++++++++----- AsyncDisplayKit/Details/ASBatchContext.h | 8 ++ AsyncDisplayKit/Details/ASBatchContext.mm | 23 +++-- AsyncDisplayKit/Private/ASBatchFetching.m | 6 +- AsyncDisplayKit/Private/ASInternalHelpers.h | 26 ++++- examples/Kittens/Sample/ViewController.m | 2 +- 7 files changed, 160 insertions(+), 53 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ae1f5792..784ef0b4 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -703,6 +703,19 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - #pragma mark Batch Fetching +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { + return; + } + + // Check if we should batch fetch + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { + [self _beginBatchFetching]; + } +} + - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { _deceleratingVelocity = CGPointMake( @@ -711,7 +724,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ); if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -738,7 +751,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (BOOL)shouldBatchFetch +- (BOOL)_shouldBatchFetch { // 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,22 +762,27 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_handleBatchFetchScrollingToOffset:(CGPoint)targetOffset { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - if (![self shouldBatchFetch]) { + if (![self _shouldBatchFetch]) { return; } if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; - }); + [self _beginBatchFetching]; } } +- (void)_beginBatchFetching +{ + [_batchContext beginBatchFetching]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; + }); +} + #pragma mark - ASDataControllerSource @@ -975,9 +993,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; }]; } else { [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; - [UIView performWithoutAnimation:^{ + ASPerformBlockWithoutAnimationCompletion(YES, ^{ [super insertItemsAtIndexPaths:indexPaths]; - }]; + }, ^{ + + }); } } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index bf9a3522..9a6e7b77 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -587,18 +587,46 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } else { scrollVelocity = _deceleratingVelocity; } + ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; return ASScrollDirectionApplyTransform(scrollDirection, self.transform); } -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)velocity +- (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; +} + +- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity { ASScrollDirection direction = ASScrollDirectionNone; - if (velocity.y < 0.0) { - direction = ASScrollDirectionDown; - } else if (velocity.y > 0.0) { - direction = ASScrollDirectionUp; + ASScrollDirection scrollableDirections = [self scrollableDirections]; + + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. + if (scrollVelocity.x < 0.0) { + direction |= ASScrollDirectionRight; + } else if (scrollVelocity.x > 0.0) { + direction |= ASScrollDirectionLeft; + } } + if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. + if (scrollVelocity.y < 0.0) { + direction |= ASScrollDirectionDown; + } else if (scrollVelocity.y > 0.0) { + direction |= ASScrollDirectionUp; + } + } + return direction; } @@ -631,7 +659,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 +678,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."); @@ -675,6 +703,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark Batch Fetching +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { + return; + } + + // Check if we should batch fetch + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { + [self _beginBatchFetching]; + } +} + - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { _deceleratingVelocity = CGPointMake( @@ -683,7 +724,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ); if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -691,7 +732,20 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (BOOL)shouldBatchFetch +- (void)_handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +{ + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + + if (![self _shouldBatchFetch]) { + return; + } + + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { + [self _beginBatchFetching]; + } +} + +- (BOOL)_shouldBatchFetch { // 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,20 +756,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_beginBatchFetching { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - - if (![self shouldBatchFetch]) { - return; - } - - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; - }); - } + [_batchContext beginBatchFetching]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; + }); } #pragma mark - ASRangeControllerDataSource @@ -853,10 +899,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (!self.asyncDataSource) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ + ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }, ^{ + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); if (_automaticallyAdjustsContentOffset) { @@ -874,8 +925,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ + ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }, ^{ + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); if (_automaticallyAdjustsContentOffset) { @@ -1077,7 +1133,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.h b/AsyncDisplayKit/Details/ASBatchContext.h index dc1986a7..aace1fac 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.h +++ b/AsyncDisplayKit/Details/ASBatchContext.h @@ -33,6 +33,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)completeBatchFetching:(BOOL)didComplete; +/** + * Let the context object know that a batch fetch was completed. + * + * @discussion For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context + * object thinks that it is still fetching. + */ +- (void)completeBatchFetching; + /** * Ask the context object if the batch fetching process was cancelled by the context owner. * diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/AsyncDisplayKit/Details/ASBatchContext.mm index 4833cbd8..f86449d6 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/AsyncDisplayKit/Details/ASBatchContext.mm @@ -45,24 +45,31 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { return _state == ASBatchContextStateCancelled; } -- (void)completeBatchFetching:(BOOL)didComplete -{ - if (didComplete) { - ASDN::MutexLocker l(_propertyLock); - _state = ASBatchContextStateCompleted; - } -} - - (void)beginBatchFetching { ASDN::MutexLocker l(_propertyLock); _state = ASBatchContextStateFetching; } +- (void)completeBatchFetching +{ + ASDN::MutexLocker l(_propertyLock); + _state = ASBatchContextStateCompleted; +} + - (void)cancelBatchFetching { ASDN::MutexLocker l(_propertyLock); _state = ASBatchContextStateCancelled; } +#pragma mark - Deprecated + +- (void)completeBatchFetching:(BOOL)didComplete +{ + if (didComplete) { + [self completeBatchFetching]; + } +} + @end diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index b2a471e2..f25408e3 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -20,7 +20,7 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, } // only Down and Right scrolls are currently supported (tail loading) - if (scrollDirection != ASScrollDirectionDown && scrollDirection != ASScrollDirectionRight) { + if (!ASScrollDirectionContainsDown(scrollDirection) && !ASScrollDirectionContainsRight(scrollDirection)) { return NO; } @@ -31,11 +31,11 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, 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; diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 79f500e7..8cfc7713 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -32,6 +32,26 @@ BOOL ASRunningOnOS7(); ASDISPLAYNODE_EXTERN_C_END +/** + @summary Conditionally performs UIView geometry changes in the given block without animation and call completion block afterwards. + + Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via + `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 + + @param withoutAnimation Set to `YES` to perform given block without animation + @param block Perform UIView geometry changes within the passed block + @param completion Call completion block if UIView geometry changes within the passed block did complete + */ +ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimationCompletion(BOOL withoutAnimation, void (^block)(), void (^completion)()) { + [CATransaction begin]; + [CATransaction setDisableActions:withoutAnimation]; + if (completion != nil) { + [CATransaction setCompletionBlock:completion]; + } + block(); + [CATransaction commit]; +} + /** @summary Conditionally performs UIView geometry changes in the given block without animation. @@ -42,11 +62,7 @@ ASDISPLAYNODE_EXTERN_C_END @param block Perform UIView geometry changes within the passed block */ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - if (withoutAnimation) { - [UIView performWithoutAnimation:block]; - } else { - block(); - } + ASPerformBlockWithoutAnimationCompletion(withoutAnimation, block, nil); } ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position) diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 9ec66fa4..699e17fd 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -177,7 +177,7 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell [_kittenDataSource addObjectsFromArray:moarKittens]; [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; - [context completeBatchFetching:YES]; + [context completeBatchFetching]; }); } From f92e7d5a29bac51dece1cbf2bd4efeeab8bff2d5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 14:50:31 -0700 Subject: [PATCH 02/10] Move completeBatchFetching: to deprecated location in header --- AsyncDisplayKit/Details/ASBatchContext.h | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/Details/ASBatchContext.h b/AsyncDisplayKit/Details/ASBatchContext.h index aace1fac..f65c4060 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.h +++ b/AsyncDisplayKit/Details/ASBatchContext.h @@ -22,17 +22,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)isFetching; -/** - * Let the context object know that a batch fetch was completed. - * - * @param didComplete A boolean that states whether or not the batch fetch completed. - * - * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. - * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context - * object thinks that it is still fetching. - */ -- (void)completeBatchFetching:(BOOL)didComplete; - /** * Let the context object know that a batch fetch was completed. * @@ -67,6 +56,20 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)beginBatchFetching; + +#pragma mark - Deprecated + +/** + * Let the context object know that a batch fetch was completed. + * + * @param didComplete A boolean that states whether or not the batch fetch completed. + * + * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. + * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context + * object thinks that it is still fetching. + */ +- (void)completeBatchFetching:(BOOL)didComplete; + @end NS_ASSUME_NONNULL_END From ab8928c140946c86eca4119b9fbb26c67866164f Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 14:56:09 -0700 Subject: [PATCH 03/10] Consolidate methods for batch fetching in ASTableView --- AsyncDisplayKit/ASTableView.mm | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 9a6e7b77..3fa56073 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -700,20 +700,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } -#pragma mark - -#pragma mark Batch Fetching +#pragma mark - Batch Fetching - (void)_checkForBatchFetching { // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { + if ([self isDragging] || [self isTracking]) { return; } - // Check if we should batch fetch - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { - [self _beginBatchFetching]; - } + [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset @@ -724,7 +720,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ); if (targetContentOffset != NULL) { - [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -732,15 +729,14 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)_handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_beginBatchFetchingIfNeededForScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - if (![self _shouldBatchFetch]) { return; } - - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { + + // Check if we should batch fetch + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { [self _beginBatchFetching]; } } From 40fe1f3ac738db1750f44eebdc81bec6b790f861 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 14:56:55 -0700 Subject: [PATCH 04/10] Schedule _checkForBatchFetching call in animation block --- AsyncDisplayKit/ASTableView.mm | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 3fa56073..18004ae8 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -895,11 +895,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (!self.asyncDataSource) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ + + ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }, ^{ // Push this to the next runloop to be sure the UITableView has the right content size dispatch_async(dispatch_get_main_queue(), ^{ [self _checkForBatchFetching]; @@ -921,9 +921,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ + ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }, ^{ // Push this to the next runloop to be sure the UITableView has the right content size dispatch_async(dispatch_get_main_queue(), ^{ [self _checkForBatchFetching]; @@ -948,6 +947,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); } @@ -963,6 +966,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); } From 24ca09ee6caf0595fb701c007f700462dba3d1b5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 19:42:14 -0700 Subject: [PATCH 05/10] Move most of the batch fetching logic to a central place for ASTableView and ASCollectionView usage --- AsyncDisplayKit/ASCollectionView.mm | 142 +++++++------- AsyncDisplayKit/ASTableView.mm | 219 +++++++++++----------- AsyncDisplayKit/Private/ASBatchFetching.h | 27 ++- AsyncDisplayKit/Private/ASBatchFetching.m | 30 +-- 4 files changed, 219 insertions(+), 199 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 784ef0b4..13e7c743 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,58 +737,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } -#pragma mark - -#pragma mark Batch Fetching +#pragma mark - Batch Fetching -- (void)_checkForBatchFetching +- (ASBatchContext *)batchContext { - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { - return; - } - - // Check if we should batch fetch - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { - [self _beginBatchFetching]; - } + return _batchContext; } -- (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) { - [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; - } - - if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } -} - -- (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:)]; @@ -762,25 +755,41 @@ 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 UITableView 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)) { + [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]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; - }); + if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; + }); + } } @@ -972,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]; @@ -993,11 +1005,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; }]; } else { [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; - ASPerformBlockWithoutAnimationCompletion(YES, ^{ + [UIView performWithoutAnimation:^{ [super insertItemsAtIndexPaths:indexPaths]; - }, ^{ - - }); + [self _scheduleCheckForBatchFetching]; + }]; } } @@ -1017,6 +1028,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super deleteItemsAtIndexPaths:indexPaths]; + [self _scheduleCheckForBatchFetching]; }]; } } @@ -1037,6 +1049,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super insertSections:indexSet]; + [self _scheduleCheckForBatchFetching]; }]; } } @@ -1057,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 18004ae8..9be26beb 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,75 +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)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; -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - ASScrollDirection scrollableDirections = [self scrollableDirections]; - - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x < 0.0) { - direction |= ASScrollDirectionRight; - } else if (scrollVelocity.x > 0.0) { - direction |= ASScrollDirectionLeft; - } - } - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y < 0.0) { - direction |= ASScrollDirectionDown; - } else if (scrollVelocity.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; @@ -700,16 +629,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } -#pragma mark - Batch Fetching - -- (void)_checkForBatchFetching +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if ([self isDragging] || [self isTracking]) { - return; + // If a scroll happenes the current range mode needs to go to full + ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; + if (ASInterfaceStateIncludesVisible(interfaceState)) { + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; } - [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; + 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 @@ -721,7 +656,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (targetContentOffset != NULL) { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -729,19 +664,69 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)_beginBatchFetchingIfNeededForScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset + +#pragma mark - Scroll Direction + +- (ASScrollDirection)scrollDirection { - if (![self _shouldBatchFetch]) { - return; + CGPoint scrollVelocity; + if (self.isTracking) { + scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; + } else { + scrollVelocity = _deceleratingVelocity; } - // Check if we should batch fetch - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { - [self _beginBatchFetching]; - } + ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; + return ASScrollDirectionApplyTransform(scrollDirection, self.transform); } -- (BOOL)_shouldBatchFetch +- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity +{ + ASScrollDirection direction = ASScrollDirectionNone; + ASScrollDirection scrollableDirections = [self scrollableDirections]; + + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. + if (scrollVelocity.x < 0.0) { + direction |= ASScrollDirectionRight; + } else if (scrollVelocity.x > 0.0) { + direction |= ASScrollDirectionLeft; + } + } + 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:)]; @@ -752,12 +737,39 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (void)_scheduleCheckForBatchFetching +{ + // Push this to the next runloop to be sure the UITableView 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; + } + + [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 { [_batchContext beginBatchFetching]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; - }); + if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; + }); + } } #pragma mark - ASRangeControllerDataSource @@ -897,13 +909,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); if (_automaticallyAdjustsContentOffset) { @@ -923,10 +931,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); if (_automaticallyAdjustsContentOffset) { @@ -947,10 +952,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); } @@ -966,10 +968,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); } diff --git a/AsyncDisplayKit/Private/ASBatchFetching.h b/AsyncDisplayKit/Private/ASBatchFetching.h index 9aeea5ad..6b0c1be5 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.h +++ b/AsyncDisplayKit/Private/ASBatchFetching.h @@ -10,27 +10,26 @@ #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. - @param context The batch fetching context that contains knowledge about in-flight fetches. - @param scrollDirection The current scrolling direction of the scroll view. - @param bounds The bounds of the scrollview. - @param contentSize The content size of the scrollview. - @param targetOffset The offset that the scrollview will scroll to. - @param leadingScreens How many screens in the remaining distance will trigger batch fetching. - @return Whether or not the current state should proceed with batch fetching. @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. */ -extern BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens); +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset); ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index f25408e3..9158c30b 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -8,23 +8,31 @@ #import "ASBatchFetching.h" -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 +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; + + // 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) + // 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; } @@ -33,11 +41,11 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, if (ASScrollDirectionContainsDown(scrollDirection)) { viewLength = bounds.size.height; - offset = targetOffset.y; + offset = contentOffset.y; contentLength = contentSize.height; } else { // horizontal / right viewLength = bounds.size.width; - offset = targetOffset.x; + offset = contentOffset.x; contentLength = contentSize.width; } From bb110ca6e8c1ba1fb2fd42e2dbe33902868e6305 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 20:02:20 -0700 Subject: [PATCH 06/10] Readd ASDisplayShouldFetchBatchForContext --- AsyncDisplayKit/Private/ASBatchFetching.h | 20 ++++++++++++++++++++ AsyncDisplayKit/Private/ASBatchFetching.m | 16 ++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/Private/ASBatchFetching.h b/AsyncDisplayKit/Private/ASBatchFetching.h index 6b0c1be5..094a2c6c 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.h +++ b/AsyncDisplayKit/Private/ASBatchFetching.h @@ -32,4 +32,24 @@ ASDISPLAYNODE_EXTERN_C_BEGIN */ 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. + @param scrollDirection The current scrolling direction of the scroll view. + @param bounds The bounds of the scrollview. + @param contentSize The content size of the scrollview. + @param targetOffset The offset that the scrollview will scroll to. + @param leadingScreens How many screens in the remaining distance will trigger batch fetching. + @return Whether or not the current state should proceed with batch fetching. + @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and + * ASCollectionView batch fetching API. + */ +extern BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, + ASScrollDirection scrollDirection, + CGRect bounds, + CGSize contentSize, + CGPoint targetOffset, + CGFloat leadingScreens); + ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index 9158c30b..6a8d9fc1 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -10,7 +10,6 @@ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset) { - // Don't fetch if the scroll view does not allow if (![scrollView canBatchFetch]) { return NO; @@ -21,7 +20,16 @@ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView Date: Sat, 9 Apr 2016 10:53:44 -0700 Subject: [PATCH 07/10] Remove deprecation of completeBatchFetching: --- AsyncDisplayKit/Details/ASBatchContext.h | 21 +++++---------------- AsyncDisplayKit/Details/ASBatchContext.mm | 17 +++++------------ examples/Kittens/Sample/ViewController.m | 2 +- 3 files changed, 11 insertions(+), 29 deletions(-) diff --git a/AsyncDisplayKit/Details/ASBatchContext.h b/AsyncDisplayKit/Details/ASBatchContext.h index f65c4060..dc1986a7 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.h +++ b/AsyncDisplayKit/Details/ASBatchContext.h @@ -25,10 +25,13 @@ NS_ASSUME_NONNULL_BEGIN /** * Let the context object know that a batch fetch was completed. * - * @discussion For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context + * @param didComplete A boolean that states whether or not the batch fetch completed. + * + * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. + * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context * object thinks that it is still fetching. */ -- (void)completeBatchFetching; +- (void)completeBatchFetching:(BOOL)didComplete; /** * Ask the context object if the batch fetching process was cancelled by the context owner. @@ -56,20 +59,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)beginBatchFetching; - -#pragma mark - Deprecated - -/** - * Let the context object know that a batch fetch was completed. - * - * @param didComplete A boolean that states whether or not the batch fetch completed. - * - * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. - * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context - * object thinks that it is still fetching. - */ -- (void)completeBatchFetching:(BOOL)didComplete; - @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/AsyncDisplayKit/Details/ASBatchContext.mm index f86449d6..4cb5b96f 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/AsyncDisplayKit/Details/ASBatchContext.mm @@ -51,10 +51,12 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { _state = ASBatchContextStateFetching; } -- (void)completeBatchFetching +- (void)completeBatchFetching:(BOOL)didComplete { - ASDN::MutexLocker l(_propertyLock); - _state = ASBatchContextStateCompleted; + if (didComplete) { + ASDN::MutexLocker l(_propertyLock); + _state = ASBatchContextStateCompleted; + } } - (void)cancelBatchFetching @@ -63,13 +65,4 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { _state = ASBatchContextStateCancelled; } -#pragma mark - Deprecated - -- (void)completeBatchFetching:(BOOL)didComplete -{ - if (didComplete) { - [self completeBatchFetching]; - } -} - @end diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 699e17fd..9ec66fa4 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -177,7 +177,7 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell [_kittenDataSource addObjectsFromArray:moarKittens]; [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; - [context completeBatchFetching]; + [context completeBatchFetching:YES]; }); } From c19c2da2eef1fa75eca1be14a8230de382d36419 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 10:54:07 -0700 Subject: [PATCH 08/10] Remove horizontal scrolling behavior detection for ASTableView --- AsyncDisplayKit/ASTableView.mm | 7 ------- 1 file changed, 7 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 9be26beb..ac079a1f 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -685,13 +685,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASScrollDirection direction = ASScrollDirectionNone; ASScrollDirection scrollableDirections = [self scrollableDirections]; - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x < 0.0) { - direction |= ASScrollDirectionRight; - } else if (scrollVelocity.x > 0.0) { - direction |= ASScrollDirectionLeft; - } - } if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. if (scrollVelocity.y < 0.0) { direction |= ASScrollDirectionDown; From be26f0c2e549429a2c06e77fca3083bfe8ad739b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 10:54:45 -0700 Subject: [PATCH 09/10] Revert back to use UIKit version to prevent animations --- AsyncDisplayKit/Private/ASInternalHelpers.h | 26 ++++----------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 8cfc7713..79f500e7 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -32,26 +32,6 @@ BOOL ASRunningOnOS7(); ASDISPLAYNODE_EXTERN_C_END -/** - @summary Conditionally performs UIView geometry changes in the given block without animation and call completion block afterwards. - - Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via - `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 - - @param withoutAnimation Set to `YES` to perform given block without animation - @param block Perform UIView geometry changes within the passed block - @param completion Call completion block if UIView geometry changes within the passed block did complete - */ -ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimationCompletion(BOOL withoutAnimation, void (^block)(), void (^completion)()) { - [CATransaction begin]; - [CATransaction setDisableActions:withoutAnimation]; - if (completion != nil) { - [CATransaction setCompletionBlock:completion]; - } - block(); - [CATransaction commit]; -} - /** @summary Conditionally performs UIView geometry changes in the given block without animation. @@ -62,7 +42,11 @@ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimationCompletion(BOOL withoutA @param block Perform UIView geometry changes within the passed block */ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - ASPerformBlockWithoutAnimationCompletion(withoutAnimation, block, nil); + if (withoutAnimation) { + [UIView performWithoutAnimation:block]; + } else { + block(); + } } ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position) From e9fe92444f141337a04c412160661cd876371d4e Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 10:55:04 -0700 Subject: [PATCH 10/10] Small comment fix --- AsyncDisplayKit/ASCollectionView.mm | 2 +- AsyncDisplayKit/ASTableView.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 13e7c743..26dfc15c 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -757,7 +757,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)_scheduleCheckForBatchFetching { - // Push this to the next runloop to be sure the UITableView has the right content size + // 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]; }); diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index ac079a1f..1a372084 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -732,7 +732,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)_scheduleCheckForBatchFetching { - // Push this to the next runloop to be sure the UITableView has the right content size + // 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]; });