Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit 4c1f88e

Browse files
committed
Merge pull request #1629 from erichoracek/recalculate-supplementaries-on-item-changes
[ASCollectionView] Repopulate supplementary views on item-level changes
2 parents 3725e53 + 70fbbe0 commit 4c1f88e

5 files changed

Lines changed: 231 additions & 19 deletions

File tree

AsyncDisplayKit/Details/ASCollectionDataController.mm

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,61 @@ - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
156156
}
157157
}
158158

159+
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
160+
{
161+
for (NSString *kind in [self supplementaryKinds]) {
162+
LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths);
163+
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
164+
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
165+
_pendingContexts[kind] = contexts;
166+
}
167+
}
168+
169+
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
170+
{
171+
NSArray *keys = _pendingContexts.allKeys;
172+
for (NSString *kind in keys) {
173+
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
174+
175+
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
176+
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
177+
}];
178+
[_pendingContexts removeObjectForKey:kind];
179+
}
180+
}
181+
182+
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
183+
{
184+
for (NSString *kind in [self supplementaryKinds]) {
185+
NSArray<NSIndexPath *> *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths);
186+
[self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil];
187+
}
188+
}
189+
190+
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
191+
{
192+
for (NSString *kind in [self supplementaryKinds]) {
193+
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
194+
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
195+
_pendingContexts[kind] = contexts;
196+
}
197+
}
198+
199+
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
200+
{
201+
NSArray *keys = _pendingContexts.allKeys;
202+
for (NSString *kind in keys) {
203+
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
204+
205+
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
206+
// reinsert the elements
207+
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
208+
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
209+
}];
210+
[_pendingContexts removeObjectForKey:kind];
211+
}
212+
}
213+
159214
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
160215
{
161216
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
@@ -167,21 +222,7 @@ - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(
167222
NSUInteger rowCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:i];
168223
for (NSUInteger j = 0; j < rowCount; j++) {
169224
NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j];
170-
171-
ASCellNodeBlock supplementaryCellBlock;
172-
if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) {
173-
supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath];
174-
} else {
175-
ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath];
176-
supplementaryCellBlock = ^{ return supplementaryNode; };
177-
}
178-
179-
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
180-
ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock
181-
indexPath:indexPath
182-
constrainedSize:constrainedSize
183-
environmentTraitCollection:environmentTraitCollection];
184-
[contexts addObject:context];
225+
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection];
185226
}
186227
}
187228
}
@@ -196,7 +237,33 @@ - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndex
196237
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
197238
for (NSUInteger i = 0; i < rowNum; i++) {
198239
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
240+
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection];
241+
}
242+
}];
243+
}
244+
245+
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
246+
{
247+
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
248+
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
249+
250+
NSMutableIndexSet *sections = [NSMutableIndexSet indexSet];
251+
for (NSIndexPath *indexPath in indexPaths) {
252+
[sections addIndex:indexPath.section];
253+
}
254+
255+
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
256+
NSUInteger rowNum = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:idx];
257+
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
258+
for (NSUInteger i = 0; i < rowNum; i++) {
259+
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
260+
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection];
261+
}
262+
}];
263+
}
199264

265+
- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection
266+
{
200267
ASCellNodeBlock supplementaryCellBlock;
201268
if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) {
202269
supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath];
@@ -211,8 +278,6 @@ - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndex
211278
constrainedSize:constrainedSize
212279
environmentTraitCollection:environmentTraitCollection];
213280
[contexts addObject:context];
214-
}
215-
}];
216281
}
217282

218283
#pragma mark - Sizing query
@@ -264,4 +329,4 @@ - (void)setDataSource:(id<ASDataControllerSource>)dataSource
264329
ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]);
265330
}
266331

267-
@end
332+
@end

AsyncDisplayKit/Details/ASDataController+Subclasses.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,70 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NS
160160
*/
161161
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection;
162162

163+
/**
164+
* Notifies the subclass to perform setup before rows are inserted in the data controller.
165+
*
166+
* @discussion This method will be performed before the data controller enters its editing queue.
167+
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
168+
* data stores before entering into editing the backing store on a background thread.
169+
*
170+
* @param indexPaths Index paths for the rows to be inserted.
171+
*/
172+
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
173+
174+
/**
175+
* Notifies the subclass that the data controller will insert new rows at the given index paths.
176+
*
177+
* @discussion This method will be performed on the data controller's editing background queue before the parent's
178+
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
179+
* or header/footer nodes.
180+
*
181+
* @param indexPaths Index paths for the rows to be inserted.
182+
*/
183+
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
184+
185+
/**
186+
* Notifies the subclass to perform setup before rows are deleted in the data controller.
187+
*
188+
* @discussion This method will be performed before the data controller enters its editing queue.
189+
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
190+
* data stores before entering into editing the backing store on a background thread.
191+
*
192+
* @param indexPaths Index paths for the rows to be deleted.
193+
*/
194+
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
195+
196+
/**
197+
* Notifies the subclass that the data controller will delete rows at the given index paths.
198+
*
199+
* @discussion This method will be performed before the data controller enters its editing queue.
200+
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
201+
* data stores before entering into editing the backing store on a background thread.
202+
*
203+
* @param indexPaths Index paths for the rows to be deleted.
204+
*/
205+
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
206+
207+
/**
208+
* Notifies the subclass to perform any work needed before the given rows will be reloaded.
209+
*
210+
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main
211+
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
212+
* data stores before entering into editing the backing store on a background thread.
213+
*
214+
* @param indexPaths Index paths for the rows to be reloaded.
215+
*/
216+
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
217+
218+
/**
219+
* Notifies the subclass that the data controller will reload the rows at the given index paths.
220+
*
221+
* @discussion This method will be performed on the data controller's editing background queue before the parent's
222+
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
223+
* or header/footer nodes.
224+
*
225+
* @param indexPaths Index paths for the rows to be reloaded.
226+
*/
227+
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
228+
163229
@end

AsyncDisplayKit/Details/ASDataController.mm

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,36 @@ - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
772772
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
773773
}
774774

775+
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
776+
{
777+
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
778+
}
779+
780+
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
781+
{
782+
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
783+
}
784+
785+
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
786+
{
787+
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
788+
}
789+
790+
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
791+
{
792+
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
793+
}
794+
795+
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
796+
{
797+
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
798+
}
799+
800+
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
801+
{
802+
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
803+
}
804+
775805
#pragma mark - Row Editing (External API)
776806

777807
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@@ -799,7 +829,11 @@ - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDat
799829
environmentTraitCollection:environmentTraitCollection]];
800830
}
801831

832+
[self prepareForInsertRowsAtIndexPaths:indexPaths];
833+
802834
[_editingTransactionQueue addOperationWithBlock:^{
835+
[self willInsertRowsAtIndexPaths:indexPaths];
836+
803837
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
804838
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
805839
}];
@@ -819,7 +853,11 @@ - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDat
819853
// FIXME: Shouldn't deletes be sorted in descending order?
820854
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
821855

856+
[self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths];
857+
822858
[_editingTransactionQueue addOperationWithBlock:^{
859+
[self willDeleteRowsAtIndexPaths:sortedIndexPaths];
860+
823861
LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
824862
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
825863
}];
@@ -853,8 +891,12 @@ - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDat
853891
constrainedSize:constrainedSize
854892
environmentTraitCollection:environmentTraitCollection]];
855893
}
894+
895+
[self prepareForReloadRowsAtIndexPaths:indexPaths];
856896

857897
[_editingTransactionQueue addOperationWithBlock:^{
898+
[self willReloadRowsAtIndexPaths:indexPaths];
899+
858900
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
859901
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
860902
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];

AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray
4747
/**
4848
* Return all the index paths of mutable multidimensional array at given index set, in ascending order.
4949
*/
50-
extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *MultidimensionalArray, NSIndexSet *indexSet);
50+
extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet);
51+
52+
/**
53+
* Return the index paths of the given multidimensional array that are present in the given index paths array.
54+
*/
55+
extern NSArray<NSIndexPath *> *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray<NSIndexPath *> *indexPaths);
5156

5257
/**
5358
* Return all the index paths of a two-dimensional array, in ascending order.

AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, N
5353
}
5454
}
5555

56+
static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, NSIndexPath *indexPath) {
57+
NSUInteger indexLength = indexPath.length;
58+
ASDisplayNodeCAssert(indexLength != 0, @"Must have a non-zero indexPath length");
59+
NSUInteger firstIndex = [indexPath indexAtPosition:0];
60+
BOOL elementExists = firstIndex < array.count;
61+
62+
if (indexLength == 1) {
63+
return elementExists;
64+
}
65+
66+
if (!elementExists) {
67+
return NO;
68+
}
69+
70+
NSUInteger indexesLength = indexLength - 1;
71+
NSUInteger indexes[indexesLength];
72+
[indexPath getIndexes:indexes range:NSMakeRange(1, indexesLength)];
73+
NSIndexPath *indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength];
74+
75+
return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex);
76+
}
77+
5678
#pragma mark - Public Methods
5779

5880
NSObject<NSCopying> *ASMultidimensionalArrayDeepMutableCopy(NSObject<NSCopying> *obj)
@@ -142,6 +164,18 @@ void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutable
142164
return res;
143165
}
144166

167+
NSArray<NSIndexPath *> *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray<NSIndexPath *> *indexPaths)
168+
{
169+
NSMutableArray *res = [NSMutableArray array];
170+
for (NSIndexPath *indexPath in indexPaths) {
171+
if (ASElementExistsAtIndexPathForMultidimensionalArray(multidimensionalArray, indexPath)) {
172+
[res addObject:indexPath];
173+
}
174+
}
175+
176+
return res;
177+
}
178+
145179
NSArray *ASIndexPathsForTwoDimensionalArray(NSArray <NSArray *>* twoDimensionalArray)
146180
{
147181
NSMutableArray *result = [NSMutableArray array];

0 commit comments

Comments
 (0)