diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 385ca7faf7..c16715b4eb 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -735,7 +735,6 @@ static BOOL _isInterceptedSelector(SEL sel) { ASCellNode *node = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; ASDisplayNodeAssert(node != nil, @"A node must be returned for a supplementary node"); - ASDisplayNodeAssert(!node.nodeLoaded, @"The supplementary node must not be loaded"); return node; } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 081d44585d..0bbc2c502c 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -41,6 +41,9 @@ [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; }]; } @@ -66,8 +69,8 @@ [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - _pendingNodes[kind] = nil; - _pendingIndexPaths[kind] = nil; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; } @@ -81,6 +84,9 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; }]; } @@ -119,6 +125,9 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; + + // Measure loaded nodes before leaving the main thread + [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; }]; } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 6f930ca23b..018e58348a 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -58,6 +58,14 @@ */ - (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +/* + * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. + * + * @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance + * to do so immediately on the main thread. + */ +- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; + /** * Provides the size range for a specific node during the layout process. */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index f3901a7ae5..24dcd68d2c 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -103,30 +103,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize -{ - [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); -} - -/* - * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. - * - * @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance - * to do so immediately on the main thread. - */ -- (void)_layoutNodesWithMainThreadAffinity:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths { - NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread."); - - [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) { - ASCellNode *node = nodes[idx]; - if (node.isNodeLoaded) { - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [self _layoutNode:node withConstrainedSize:constrainedSize]; - } - }]; -} - - (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; @@ -141,6 +117,27 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } } +- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { + NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread."); + + [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) { + ASCellNode *node = nodes[idx]; + if (node.isNodeLoaded) { + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + [self _layoutNode:node withConstrainedSize:constrainedSize]; + } + }]; +} + +/** + * Measure and layout the given node with the constrained size range. + */ +- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize +{ + [node measureWithSizeRange:constrainedSize]; + node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); +} + /** * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. */ @@ -176,7 +173,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger k = j; k < j + batchCount; k++) { ASCellNode *node = nodes[k]; // Only measure nodes whose views aren't loaded, since we're in the background. - // We should already have measured loaded nodes before we left the main thread, using _layoutNodesWithMainThreadAffinity: + // We should already have measured loaded nodes before we left the main thread, using layoutLoadedNodes:ofKind:atIndexPaths: if (!node.isNodeLoaded) { [self _layoutNode:node withConstrainedSize:nodeBoundSizes[k]]; } @@ -371,7 +368,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; // Measure nodes whose views are loaded before we leave the main thread - [self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths]; + [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; @@ -550,14 +547,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - [self prepareForInsertSections:sections]; - NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; // Measure nodes whose views are loaded before we leave the main thread - [self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths]; + [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; + + [self prepareForInsertSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willInsertSections:sections]; @@ -604,8 +601,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - [self prepareForReloadSections:sections]; - NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; @@ -615,7 +610,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...] // Measure nodes whose views are loaded before we leave the main thread - [self _layoutNodesWithMainThreadAffinity:updatedNodes atIndexPaths:updatedIndexPaths]; + [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; + + [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; @@ -727,7 +724,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } // Measure nodes whose views are loaded before we leave the main thread - [self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths]; + [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); @@ -777,7 +774,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } // Measure nodes whose views are loaded before we leave the main thread - [self _layoutNodesWithMainThreadAffinity:nodes atIndexPaths:indexPaths]; + [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index d17929e7ae..01350da1a3 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -92,7 +92,12 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return 300; + return 10; +} + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView +{ + return 100; } - (void)collectionViewLockDataSource:(ASCollectionView *)collectionView