Merge commit 'd9209e69c25657586b43a72def4c5ac537956e40' into debug-drawrect

# Conflicts:
#	AsyncDisplayKit/ASCollectionNode.mm
#	AsyncDisplayKit/ASDisplayNode.mm
#	AsyncDisplayKit/ASTableNode.mm
#	AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h
#	AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h
#	AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m
This commit is contained in:
Peter
2016-12-12 01:02:23 +03:00
122 changed files with 3055 additions and 785 deletions

View File

@@ -29,6 +29,25 @@
#import "ASSectionContext.h"
#import "ASCollectionView+Undeprecated.h"
/**
* A macro to get self.collectionNode and assign it to a local variable, or return
* the given value if nil.
*
* Previously we would set ASCollectionNode's dataSource & delegate to nil
* during dealloc. However, our asyncDelegate & asyncDataSource must be set on the
* main thread, so if the node is deallocated off-main, we won't learn about the change
* until later on. Since our @c collectionNode parameter to delegate methods (e.g.
* collectionNode:didEndDisplayingItemWithNode:) is nonnull, it's important that we never
* unintentionally pass nil (this will crash in Swift, in production). So we can use
* this macro to ensure that our node is still alive before calling out to the user
* on its behalf.
*/
#define GET_COLLECTIONNODE_OR_RETURN(__var, __val) \
ASCollectionNode *__var = self.collectionNode; \
if (__var == nil) { \
return __val; \
}
/// What, if any, invalidation should we perform during the next -layoutSubviews.
typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) {
/// Perform no invalidation.
@@ -157,6 +176,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
* The collection view never queried your data source before the update to see that it actually had 0 items.
*/
BOOL _superIsPendingDataLoad;
/**
* It's important that we always check for batch fetching at least once, but also
* that we do not check for batch fetching for empty updates (as that may cause an infinite
* loop of batch fetching, where the batch completes and performBatchUpdates: is called without
* actually making any changes.) So to handle the case where a collection is completely empty
* (0 sections) we always check at least once after each update (initial reload is the first update.)
*/
BOOL _hasEverCheckedForBatchFetchingDueToUpdate;
struct {
unsigned int scrollViewDidScroll:1;
@@ -418,6 +446,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|| _asyncDataSourceFlags.collectionViewNodeForItem, @"Data source must implement collectionNode:nodeBlockForItemAtIndexPath: or collectionNode:nodeForItemAtIndexPath:");
}
_dataController.validationErrorSource = asyncDataSource;
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
//Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one.
@@ -827,7 +856,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath);
if (_asyncDelegateFlags.collectionNodeWillDisplayItem) {
[_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode];
if (ASCollectionNode *collectionNode = self.collectionNode) {
[_asyncDelegate collectionNode:collectionNode willDisplayItemWithNode:cellNode];
}
} else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -850,7 +881,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) {
[_asyncDelegate collectionNode:self.collectionNode didEndDisplayingItemWithNode:cellNode];
if (ASCollectionNode *collectionNode = self.collectionNode) {
[_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode];
}
} else if (_asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -869,27 +902,30 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
[_asyncDelegate collectionNode:self.collectionNode willDisplaySupplementaryElementWithNode:node];
[_asyncDelegate collectionNode:collectionNode willDisplaySupplementaryElementWithNode:node];
}
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
[_asyncDelegate collectionNode:self.collectionNode didEndDisplayingSupplementaryElementWithNode:node];
[_asyncDelegate collectionNode:collectionNode didEndDisplayingSupplementaryElementWithNode:node];
}
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldSelectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldSelectItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldSelectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewShouldSelectItem) {
#pragma clang diagnostic push
@@ -903,9 +939,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidSelectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didSelectItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didSelectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidSelectItem) {
#pragma clang diagnostic push
@@ -918,9 +955,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldDeselectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldDeselectItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldDeselectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewShouldDeselectItem) {
#pragma clang diagnostic push
@@ -934,9 +972,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidDeselectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didDeselectItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didDeselectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidDeselectItem) {
#pragma clang diagnostic push
@@ -949,9 +988,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldHighlightItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldHighlightItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldHighlightItemAtIndexPath:indexPath];
} else {
return YES;
}
@@ -967,9 +1007,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidHighlightItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didHighlightItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didHighlightItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidHighlightItem) {
#pragma clang diagnostic push
@@ -982,9 +1023,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidUnhighlightItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didUnhighlightItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didUnhighlightItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidUnhighlightItem) {
#pragma clang diagnostic push
@@ -997,9 +1039,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldShowMenuForItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldShowMenuForItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldShowMenuForItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewShouldShowMenuForItem) {
#pragma clang diagnostic push
@@ -1013,9 +1056,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
{
if (_asyncDelegateFlags.collectionNodeCanPerformActionForItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender];
return [_asyncDelegate collectionNode:collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender];
}
} else if (_asyncDelegateFlags.collectionViewCanPerformActionForItem) {
#pragma clang diagnostic push
@@ -1029,9 +1073,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView performAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
{
if (_asyncDelegateFlags.collectionNodePerformActionForItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender];
[_asyncDelegate collectionNode:collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender];
}
} else if (_asyncDelegateFlags.collectionViewPerformActionForItem) {
#pragma clang diagnostic push
@@ -1047,6 +1092,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
if (ASInterfaceStateIncludesVisible(interfaceState)) {
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
[self _checkForBatchFetching];
}
for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
@@ -1070,7 +1116,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
if (targetContentOffset != NULL) {
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset];
[self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset];
}
if (_asyncDelegateFlags.scrollViewWillEndDragging) {
@@ -1196,7 +1242,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
// if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = _asyncDelegateFlags.collectionNodeWillBeginBatchFetch || _asyncDelegateFlags.collectionViewWillBeginBatchFetch;
if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionNode) {
return [_asyncDelegate shouldBatchFetchForCollectionNode:self.collectionNode];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
return [_asyncDelegate shouldBatchFetchForCollectionNode:collectionNode];
} else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionView) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1210,9 +1257,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes
{
// Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided
if (changes == 0) {
if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) {
return;
}
_hasEverCheckedForBatchFetchingDueToUpdate = YES;
// Push this to the next runloop to be sure the scroll view has the right content size
dispatch_async(dispatch_get_main_queue(), ^{
@@ -1227,12 +1275,12 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return;
}
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset];
[self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset];
}
- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView<ASBatchFetchingScrollView> *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset
- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset
{
if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) {
if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset)) {
[self _beginBatchFetching];
}
}
@@ -1242,7 +1290,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
[_batchContext beginBatchFetching];
if (_asyncDelegateFlags.collectionNodeWillBeginBatchFetch) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate collectionNode:self.collectionNode willBeginBatchFetchWithContext:_batchContext];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
[_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:_batchContext];
});
} else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@@ -1261,9 +1310,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASCellNodeBlock block = nil;
if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) {
block = [_asyncDataSource collectionNode:self.collectionNode nodeBlockForItemAtIndexPath:indexPath];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; });
block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath];
} else if (_asyncDataSourceFlags.collectionNodeNodeForItem) {
ASCellNode *node = [_asyncDataSource collectionNode:self.collectionNode nodeForItemAtIndexPath:indexPath];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; });
ASCellNode *node = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath];
if ([node isKindOfClass:[ASCellNode class]]) {
block = ^{
return node;
@@ -1317,7 +1368,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
{
if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) {
return [_asyncDataSource collectionNode:self.collectionNode numberOfItemsInSection:section];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0);
return [_asyncDataSource collectionNode:collectionNode numberOfItemsInSection:section];
} else if (_asyncDataSourceFlags.collectionViewNumberOfItemsInSection) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1330,7 +1382,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
if (_asyncDataSourceFlags.numberOfSectionsInCollectionNode) {
return [_asyncDataSource numberOfSectionsInCollectionNode:self.collectionNode];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0);
return [_asyncDataSource numberOfSectionsInCollectionNode:collectionNode];
} else if (_asyncDataSourceFlags.numberOfSectionsInCollectionView) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1352,7 +1405,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{
ASCellNode *node = nil;
if (_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) {
node = [_asyncDataSource collectionNode:self.collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, [[ASCellNode alloc] init] );
node = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
} else if (_asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1389,7 +1443,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
id<ASSectionContext> context = nil;
if (_asyncDataSourceFlags.collectionNodeContextForSection) {
context = [_asyncDataSource collectionNode:self.collectionNode contextForSection:section];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil);
context = [_asyncDataSource collectionNode:collectionNode contextForSection:section];
}
if (context != nil) {
@@ -1480,11 +1535,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_performingBatchUpdates = NO;
}
- (void)didCompleteUpdatesInRangeController:(ASRangeController *)rangeController
{
[self _checkForBatchFetching];
}
- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
@@ -1634,18 +1684,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_nextLayoutInvalidationStyle = invalidationStyle;
}
#pragma mark - Memory Management
- (void)clearContents
{
[_rangeController clearContents];
}
- (void)clearFetchedData
{
[_rangeController clearFetchedData];
}
#pragma mark - _ASDisplayView behavior substitutions
// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element.
// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.