Merge pull request #1629 from erichoracek/recalculate-supplementaries-on-item-changes

[ASCollectionView] Repopulate supplementary views on item-level changes
This commit is contained in:
appleguy
2016-06-05 22:36:54 -07:00
5 changed files with 231 additions and 19 deletions

View File

@@ -156,6 +156,61 @@
}
}
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
for (NSString *kind in [self supplementaryKinds]) {
LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths);
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
_pendingContexts[kind] = contexts;
}
}
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
NSArray *keys = _pendingContexts.allKeys;
for (NSString *kind in keys) {
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
[_pendingContexts removeObjectForKey:kind];
}
}
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
for (NSString *kind in [self supplementaryKinds]) {
NSArray<NSIndexPath *> *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths);
[self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil];
}
}
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
for (NSString *kind in [self supplementaryKinds]) {
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
_pendingContexts[kind] = contexts;
}
}
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
NSArray *keys = _pendingContexts.allKeys;
for (NSString *kind in keys) {
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// reinsert the elements
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
[_pendingContexts removeObjectForKey:kind];
}
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
{
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
@@ -167,21 +222,7 @@
NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i];
for (NSUInteger j = 0; j < rowCount; j++) {
NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j];
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];
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection];
}
}
}
@@ -196,7 +237,33 @@
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 environmentTraitCollection:environmentTraitCollection];
}
}];
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
{
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
NSMutableIndexSet *sections = [NSMutableIndexSet indexSet];
for (NSIndexPath *indexPath in indexPaths) {
[sections addIndex:indexPath.section];
}
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx];
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 environmentTraitCollection:environmentTraitCollection];
}
}];
}
- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection
{
ASCellNodeBlock supplementaryCellBlock;
if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) {
supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath];
@@ -211,8 +278,6 @@
constrainedSize:constrainedSize
environmentTraitCollection:environmentTraitCollection];
[contexts addObject:context];
}
}];
}
#pragma mark - Sizing query
@@ -264,4 +329,4 @@
ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]);
}
@end
@end

View File

@@ -160,4 +160,70 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *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<NSIndexPath *> *)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<NSIndexPath *> *)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<NSIndexPath *> *)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<NSIndexPath *> *)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<NSIndexPath *> *)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<NSIndexPath *> *)indexPaths;
@end

View File

@@ -772,6 +772,36 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)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];

View File

@@ -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<NSIndexPath *> *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray<NSIndexPath *> *indexPaths);
/**
* Return all the index paths of a two-dimensional array, in ascending order.

View File

@@ -53,6 +53,28 @@ static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, N
}
}
static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, NSIndexPath *indexPath) {
NSUInteger indexLength = indexPath.length;
ASDisplayNodeCAssert(indexLength != 0, @"Must have a non-zero indexPath length");
NSUInteger firstIndex = [indexPath indexAtPosition:0];
BOOL elementExists = firstIndex < array.count;
if (indexLength == 1) {
return elementExists;
}
if (!elementExists) {
return NO;
}
NSUInteger indexesLength = indexLength - 1;
NSUInteger indexes[indexesLength];
[indexPath getIndexes:indexes range:NSMakeRange(1, indexesLength)];
NSIndexPath *indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength];
return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex);
}
#pragma mark - Public Methods
NSObject<NSCopying> *ASMultidimensionalArrayDeepMutableCopy(NSObject<NSCopying> *obj)
@@ -142,6 +164,18 @@ NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensiona
return res;
}
NSArray<NSIndexPath *> *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray<NSIndexPath *> *indexPaths)
{
NSMutableArray *res = [NSMutableArray array];
for (NSIndexPath *indexPath in indexPaths) {
if (ASElementExistsAtIndexPathForMultidimensionalArray(multidimensionalArray, indexPath)) {
[res addObject:indexPath];
}
}
return res;
}
NSArray *ASIndexPathsForTwoDimensionalArray(NSArray <NSArray *>* twoDimensionalArray)
{
NSMutableArray *result = [NSMutableArray array];