Fix fetch call won't occur for content smaller than bounds unless user scrolls

This commit is contained in:
Michael Schneider 2016-04-04 20:51:24 -07:00
parent 0fbc77dd25
commit c25a252e1c
7 changed files with 160 additions and 53 deletions

View File

@ -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];
}];
}, ^{
});
}
}

View File

@ -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]];
}
}

View File

@ -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.
*

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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];
});
}