// // ASCollectionDataController.mm // AsyncDisplayKit // // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. An additional grant // of patent rights can be found in the PATENTS file in the same directory. // #import "ASCollectionDataController.h" #import "ASAssert.h" #import "ASMultidimensionalArrayUtils.h" #import "ASCellNode.h" #import "ASDataController+Subclasses.h" #import "ASIndexedNodeContext.h" #import "ASSection.h" #import "ASSectionContext.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @interface ASCollectionDataController () { BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath; NSInteger _nextSectionID; NSMutableArray *_sections; NSArray *_pendingSections; } - (id)collectionDataSource; @end @implementation ASCollectionDataController { NSMutableDictionary *> *_pendingNodeContexts; } - (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog { self = [super initWithDataSource:dataSource eventLog:eventLog]; if (self != nil) { _pendingNodeContexts = [NSMutableDictionary dictionary]; _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); _nextSectionID = 0; _sections = [NSMutableArray array]; } return self; } - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount { ASDisplayNodeAssertMainThread(); NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; [_sections removeAllObjects]; [self _populatePendingSectionsFromDataSource:sections]; for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@", kind); NSMutableArray *contexts = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; _pendingNodeContexts[kind] = contexts; } } - (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount { NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; [self applyPendingSections:sectionIndexes]; [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) { // Remove everything that existed before the reload, now that we're ready to insert replacements NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; NSArray *editingNodes = [self editingNodesOfKind:kind]; NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; // Insert each section NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount]; for (int i = 0; i < newSectionCount; i++) { [sections addObject:[NSMutableArray array]]; } [self insertSections:sections ofKind:kind atIndexSet:sectionIndexes completion:nil]; [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; }]; [_pendingNodeContexts removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); [self _populatePendingSectionsFromDataSource:sections]; for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); NSMutableArray *contexts = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; _pendingNodeContexts[kind] = contexts; } } - (void)willInsertSections:(NSIndexSet *)sections { [self applyPendingSections:sections]; [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; }]; [_pendingNodeContexts removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { [_sections removeObjectsAtIndexes:sections]; for (NSString *kind in [self supplementaryKinds]) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; } } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { ASSection *movedSection = [_sections objectAtIndex:section]; [_sections removeObjectAtIndex:section]; [_sections insertObject:movedSection atIndex:newSection]; NSIndexSet *sectionAsIndexSet = [NSIndexSet indexSetWithIndex:section]; for (NSString *kind in [self supplementaryKinds]) { NSMutableArray *editingNodes = [self editingNodesOfKind:kind]; NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingNodes, sectionAsIndexSet); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // update the section of indexpaths NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; for (NSIndexPath *indexPath in indexPaths) { NSUInteger newItem = [indexPath indexAtPosition:indexPath.length - 1]; NSIndexPath *mappedIndexPath = [NSIndexPath indexPathForItem:newItem inSection:newSection]; [updatedIndexPaths addObject:mappedIndexPath]; } [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); NSMutableArray *contexts = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; _pendingNodeContexts[kind] = contexts; } } - (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths { [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; }]; [_pendingNodeContexts removeAllObjects]; } - (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); for (NSString *kind in [self supplementaryKinds]) { NSMutableArray *contexts = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; _pendingNodeContexts[kind] = contexts; } } - (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths { for (NSString *kind in [self supplementaryKinds]) { NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths); [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil]; // If any of the contexts remain after the deletion, re-insert them, e.g. // UICollectionElementKindSectionHeader remains even if item 0 is deleted. NSMutableArray *reinsertedContexts = [NSMutableArray array]; for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) { if ([deletedIndexPaths containsObject:context.indexPath]) { [reinsertedContexts addObject:context]; } } [self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; } [_pendingNodeContexts removeAllObjects]; } - (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes { ASDisplayNodeAssertMainThread(); NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count]; [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { id context = [self.collectionDataSource dataController:self contextForSection:idx]; [sections addObject:[[ASSection alloc] initWithSectionID:_nextSectionID context:context]]; _nextSectionID++; }]; _pendingSections = sections; } - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts { __weak id environment = [self.environmentDelegate dataControllerEnvironment]; [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; for (NSUInteger i = 0; i < itemCount; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; } } }]; } - (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths mutableContexts:(NSMutableArray *)contexts { __weak id environment = [self.environmentDelegate dataControllerEnvironment]; NSMutableIndexSet *sections = [NSMutableIndexSet indexSet]; for (NSIndexPath *indexPath in indexPaths) { [sections addIndex:indexPath.section]; } [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; for (NSUInteger i = 0; i < itemCount; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; } } }]; } - (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environment:(id)environment { ASCellNodeBlock supplementaryCellBlock; if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; } else { ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; supplementaryCellBlock = ^{ return supplementaryNode; }; } ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock indexPath:indexPath supplementaryElementKind:kind constrainedSize:constrainedSize environment:environment]; [contexts addObject:context]; } #pragma mark - Sizing query - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if ([kind isEqualToString:ASDataControllerRowNodeKind]) { return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; } else { ASDisplayNodeAssertMainThread(); return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; } } #pragma mark - External supplementary store and section context querying - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); NSArray *nodesOfKind = [self completedNodesOfKind:kind]; NSInteger section = indexPath.section; if (section < nodesOfKind.count) { NSArray *nodesOfKindInSection = nodesOfKind[section]; NSInteger itemIndex = indexPath.item; if (itemIndex < nodesOfKindInSection.count) { return nodesOfKindInSection[itemIndex]; } } return nil; } - (id)contextForSection:(NSInteger)section { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertTrue(section >= 0 && section < _sections.count); return _sections[section].context; } #pragma mark - Private Helpers - (NSArray *)supplementaryKinds { return [self.collectionDataSource supplementaryNodeKindsInDataController:self]; } - (id)collectionDataSource { return (id)self.dataSource; } - (void)applyPendingSections:(NSIndexSet *)sectionIndexes { [_sections insertObjects:_pendingSections atIndexes:sectionIndexes]; _pendingSections = nil; } @end