diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 3a51eed154..6531a2ff60 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -156,6 +156,59 @@ } } +- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths +{ + for (NSString *kind in [self supplementaryKinds]) { + LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); + NSMutableArray *contexts = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts]; + } + _pendingContexts[kind] = contexts; + } +} + +- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths +{ + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + }]; + [_pendingContexts removeObjectForKey:kind]; + }]; +} + +- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths +{ + for (NSString *kind in [self supplementaryKinds]) { + NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths); + [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil]; + } +} + +- (void)prepareForReloadRowsAtIndexPaths:(NSArray *)indexPaths +{ + for (NSString *kind in [self supplementaryKinds]) { + NSMutableArray *contexts = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts]; + } + _pendingContexts[kind] = contexts; + } +} + +- (void)willReloadRowsAtIndexPaths:(NSArray *)indexPaths +{ + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *contexts, BOOL *stop) { + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + // reinsert the elements + [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + }]; + [_pendingContexts removeObjectForKey:kind]; + }]; +} + - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray *)contexts { id environment = [self.environmentDelegate dataControllerEnvironment]; @@ -167,21 +220,12 @@ NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i]; for (NSUInteger j = 0; j < rowCount; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts]; + + + + - 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 - constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]; - [contexts addObject:context]; } } } @@ -196,7 +240,13 @@ NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts]; + } + }]; +} +- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts +{ ASCellNodeBlock supplementaryCellBlock; if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; @@ -211,8 +261,6 @@ constrainedSize:constrainedSize environmentTraitCollection:environmentTraitCollection]; [contexts addObject:context]; - } - }]; } #pragma mark - Sizing query @@ -264,4 +312,4 @@ ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); } -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 51b7548f50..865bc72d70 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -160,4 +160,70 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS */ - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection; +/** + * Notifies the subclass to perform setup before rows are inserted in the data controller. + * + * @discussion This method will be performed before the data controller enters its editing queue. + * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or + * data stores before entering into editing the backing store on a background thread. + * + * @param indexPaths Index paths for the rows to be inserted. + */ +- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Notifies the subclass that the data controller will insert new rows at the given index paths. + * + * @discussion This method will be performed on the data controller's editing background queue before the parent's + * concrete implementation. This is a great place to perform any additional transformations like supplementary views + * or header/footer nodes. + * + * @param indexPaths Index paths for the rows to be inserted. + */ +- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Notifies the subclass to perform setup before rows are deleted in the data controller. + * + * @discussion This method will be performed before the data controller enters its editing queue. + * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or + * data stores before entering into editing the backing store on a background thread. + * + * @param indexPaths Index paths for the rows to be deleted. + */ +- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Notifies the subclass that the data controller will delete rows at the given index paths. + * + * @discussion This method will be performed before the data controller enters its editing queue. + * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or + * data stores before entering into editing the backing store on a background thread. + * + * @param indexPaths Index paths for the rows to be deleted. + */ +- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Notifies the subclass to perform any work needed before the given rows will be reloaded. + * + * @discussion This method will be performed before the data controller enters its editing queue, usually on the main + * thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or + * data stores before entering into editing the backing store on a background thread. + * + * @param indexPaths Index paths for the rows to be reloaded. + */ +- (void)prepareForReloadRowsAtIndexPaths:(NSArray *)indexPaths; + +/** + * Notifies the subclass that the data controller will reload the rows at the given index paths. + * + * @discussion This method will be performed on the data controller's editing background queue before the parent's + * concrete implementation. This is a great place to perform any additional transformations like supplementary views + * or header/footer nodes. + * + * @param indexPaths Index paths for the rows to be reloaded. + */ +- (void)willReloadRowsAtIndexPaths:(NSArray *)indexPaths; + @end diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index af750bb4a2..3983cabbcd 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -772,6 +772,36 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Optional template hook for subclasses (See ASDataController+Subclasses.h) } +- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths +{ + // Optional template hook for subclasses (See ASDataController+Subclasses.h) +} + +- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths +{ + // Optional template hook for subclasses (See ASDataController+Subclasses.h) +} + +- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths +{ + // Optional template hook for subclasses (See ASDataController+Subclasses.h) +} + +- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths +{ + // Optional template hook for subclasses (See ASDataController+Subclasses.h) +} + +- (void)prepareForReloadRowsAtIndexPaths:(NSArray *)indexPaths +{ + // Optional template hook for subclasses (See ASDataController+Subclasses.h) +} + +- (void)willReloadRowsAtIndexPaths:(NSArray *)indexPaths +{ + // Optional template hook for subclasses (See ASDataController+Subclasses.h) +} + #pragma mark - Row Editing (External API) - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -799,7 +829,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; environmentTraitCollection:environmentTraitCollection]]; } + [self prepareForInsertRowsAtIndexPaths:indexPaths]; + [_editingTransactionQueue addOperationWithBlock:^{ + [self willInsertRowsAtIndexPaths:indexPaths]; + LOG(@"Edit Transaction - insertRows: %@", indexPaths); [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; @@ -819,7 +853,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // FIXME: Shouldn't deletes be sorted in descending order? NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; + [_editingTransactionQueue addOperationWithBlock:^{ + [self willDeleteRowsAtIndexPaths:sortedIndexPaths]; + LOG(@"Edit Transaction - deleteRows: %@", indexPaths); [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; }]; @@ -853,8 +891,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; constrainedSize:constrainedSize environmentTraitCollection:environmentTraitCollection]]; } + + [self prepareForReloadRowsAtIndexPaths:indexPaths]; [_editingTransactionQueue addOperationWithBlock:^{ + [self willReloadRowsAtIndexPaths:indexPaths]; + LOG(@"Edit Transaction - reloadRows: %@", indexPaths); [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h index 756b1cc775..36685428d8 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h @@ -47,7 +47,12 @@ extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray /** * Return all the index paths of mutable multidimensional array at given index set, in ascending order. */ -extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *MultidimensionalArray, NSIndexSet *indexSet); +extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet); + +/** + * Return the index paths of the given multidimensional array that are present in the given index paths array. + */ +extern NSArray *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray *indexPaths); /** * Return all the index paths of a two-dimensional array, in ascending order. diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm index f939410d6d..9cbed6a025 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm @@ -53,6 +53,23 @@ static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, N } } +static BOOL ASElementExistsAtIndexPath(NSMutableArray *mutableArray, NSIndexPath *indexPath) { + NSUInteger indexLength = indexPath.length; + ASDisplayNodeCAssert(indexLength != 0, @"Must have a non-zero indexPath length"); + NSUInteger firstIndex = [indexPath indexAtPosition:0]; + BOOL elementExists = firstIndex < mutableArray.count; + + if (indexLength == 1) { + return elementExists; + } + + if (!elementExists) { + return NO; + } + + return ASElementExistsAtIndexPath(mutableArray[firstIndex], [indexPath indexPathByRemovingLastIndex]); +} + #pragma mark - Public Methods NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj) @@ -142,6 +159,18 @@ NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensiona return res; } +NSArray *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray *indexPaths) +{ + NSMutableArray *res = [NSMutableArray array]; + for (NSIndexPath *indexPath in indexPaths) { + if (ASElementExistsAtIndexPath(multidimensionalArray, indexPath)) { + [res addObject:indexPath]; + } + } + + return res; +} + NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray) { NSMutableArray *result = [NSMutableArray array];