Skip to content

Commit 4fc753a

Browse files
authored
Make it possible to map between sections even if they're empty (#660)
* Make section mapping work even for empty sections * Unlock more cases and update changelog * Fix complexity documentation
1 parent ff608c9 commit 4fc753a

File tree

9 files changed

+95
-83
lines changed

9 files changed

+95
-83
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Layout] Fixes a deadlock in layout. [#638](https://github.com/TextureGroup/Texture/pull/638) [Garrett Moon](https://github.com/garrettmoon)
99
- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler)
1010
- [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651)
11+
- [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660)
1112

1213
## 2.6
1314
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)

Source/ASCollectionView.mm

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@
6464
return __val; \
6565
}
6666

67-
#define ASIndexPathForSection(section) [NSIndexPath indexPathForItem:0 inSection:section]
68-
6967
#define ASFlowLayoutDefault(layout, property, default) \
7068
({ \
7169
UICollectionViewFlowLayout *flowLayout = ASDynamicCast(layout, UICollectionViewFlowLayout); \
@@ -679,19 +677,13 @@ - (NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath wai
679677
if (indexPath == nil) {
680678
return nil;
681679
}
682-
683-
// If this is a section index path, we don't currently have a method
684-
// to do a mapping.
685-
if (indexPath.item == NSNotFound) {
686-
return indexPath;
687-
} else {
688-
NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
689-
if (viewIndexPath == nil && wait) {
690-
[self waitUntilAllUpdatesAreCommitted];
691-
return [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:NO];
692-
}
693-
return viewIndexPath;
680+
681+
NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
682+
if (viewIndexPath == nil && wait) {
683+
[self waitUntilAllUpdatesAreCommitted];
684+
return [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:NO];
694685
}
686+
return viewIndexPath;
695687
}
696688

697689
/**
@@ -725,13 +717,7 @@ - (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath
725717
return nil;
726718
}
727719

728-
// If this is a section index path, we don't currently have a method
729-
// to do a mapping.
730-
if (indexPath.item == NSNotFound) {
731-
return indexPath;
732-
} else {
733-
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
734-
}
720+
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
735721
}
736722

737723
- (NSArray<NSIndexPath *> *)convertIndexPathsToCollectionNode:(NSArray<NSIndexPath *> *)indexPaths
@@ -1050,7 +1036,7 @@ - (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *
10501036
ASDisplayNodeAssertMainThread();
10511037
ASElementMap *map = _dataController.visibleMap;
10521038
ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionHeader
1053-
atIndexPath:ASIndexPathForSection(section)];
1039+
atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
10541040
return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero);
10551041
}
10561042

@@ -1060,51 +1046,50 @@ - (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *
10601046
ASDisplayNodeAssertMainThread();
10611047
ASElementMap *map = _dataController.visibleMap;
10621048
ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionFooter
1063-
atIndexPath:ASIndexPathForSection(section)];
1049+
atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
10641050
return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero);
10651051
}
10661052

10671053
// For the methods that call delegateIndexPathForSection:withSelector:, translate the section from
10681054
// visibleMap to pendingMap. If the section no longer exists, or the delegate doesn't implement
1069-
// the selector, we will return a nil indexPath (and then use the ASFlowLayoutDefault).
1070-
- (NSIndexPath *)delegateIndexPathForSection:(NSInteger)section withSelector:(SEL)selector
1055+
// the selector, we will return NSNotFound (and then use the ASFlowLayoutDefault).
1056+
- (NSInteger)delegateIndexForSection:(NSInteger)section withSelector:(SEL)selector
10711057
{
10721058
if ([_asyncDelegate respondsToSelector:selector]) {
1073-
return [_dataController.pendingMap convertIndexPath:ASIndexPathForSection(section)
1074-
fromMap:_dataController.visibleMap];
1059+
return [_dataController.pendingMap convertSection:section fromMap:_dataController.visibleMap];
10751060
} else {
1076-
return nil;
1061+
return NSNotFound;
10771062
}
10781063
}
10791064

10801065
- (UIEdgeInsets)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
10811066
insetForSectionAtIndex:(NSInteger)section
10821067
{
1083-
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
1084-
if (indexPath) {
1085-
return [(id)_asyncDelegate collectionView:cv layout:l insetForSectionAtIndex:indexPath.section];
1068+
section = [self delegateIndexForSection:section withSelector:_cmd];
1069+
if (section != NSNotFound) {
1070+
return [(id)_asyncDelegate collectionView:cv layout:l insetForSectionAtIndex:section];
10861071
}
10871072
return ASFlowLayoutDefault(l, sectionInset, UIEdgeInsetsZero);
10881073
}
10891074

10901075
- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
10911076
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
10921077
{
1093-
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
1094-
if (indexPath) {
1078+
section = [self delegateIndexForSection:section withSelector:_cmd];
1079+
if (section != NSNotFound) {
10951080
return [(id)_asyncDelegate collectionView:cv layout:l
1096-
minimumInteritemSpacingForSectionAtIndex:indexPath.section];
1081+
minimumInteritemSpacingForSectionAtIndex:section];
10971082
}
10981083
return ASFlowLayoutDefault(l, minimumInteritemSpacing, 10.0); // Default is documented as 10.0
10991084
}
11001085

11011086
- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
11021087
minimumLineSpacingForSectionAtIndex:(NSInteger)section
11031088
{
1104-
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
1105-
if (indexPath) {
1089+
section = [self delegateIndexForSection:section withSelector:_cmd];
1090+
if (section != NSNotFound) {
11061091
return [(id)_asyncDelegate collectionView:cv layout:l
1107-
minimumLineSpacingForSectionAtIndex:indexPath.section];
1092+
minimumLineSpacingForSectionAtIndex:section];
11081093
}
11091094
return ASFlowLayoutDefault(l, minimumLineSpacing, 10.0); // Default is documented as 10.0
11101095
}

Source/ASTableView.mm

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -588,18 +588,12 @@ - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath
588588

589589
- (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait
590590
{
591-
// If this is a section index path, we don't currently have a method
592-
// to do a mapping.
593-
if (indexPath == nil || indexPath.row == NSNotFound) {
594-
return indexPath;
595-
} else {
596-
NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
597-
if (viewIndexPath == nil && wait) {
598-
[self waitUntilAllUpdatesAreCommitted];
599-
return [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:NO];
600-
}
601-
return viewIndexPath;
591+
NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
592+
if (viewIndexPath == nil && wait) {
593+
[self waitUntilAllUpdatesAreCommitted];
594+
return [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:NO];
602595
}
596+
return viewIndexPath;
603597
}
604598

605599
- (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath
@@ -608,13 +602,7 @@ - (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath
608602
return nil;
609603
}
610604

611-
// If this is a section index path, we don't currently have a method
612-
// to do a mapping.
613-
if (indexPath.row == NSNotFound) {
614-
return indexPath;
615-
} else {
616-
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
617-
}
605+
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
618606
}
619607

620608
- (NSArray<NSIndexPath *> *)convertIndexPathsToTableNode:(NSArray<NSIndexPath *> *)indexPaths

Source/Details/ASDataController.mm

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
540540
ASMutableElementMap *mutableMap = [previousMap mutableCopy];
541541

542542
// Step 1.1: Update the mutable copies to match the data source's state
543-
[self _updateSectionContextsInMap:mutableMap changeSet:changeSet];
543+
[self _updateSectionsInMap:mutableMap changeSet:changeSet];
544544
ASPrimitiveTraitCollection existingTraitCollection = [self.node primitiveTraitCollection];
545545
[self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegate) previousMap:previousMap];
546546

@@ -599,43 +599,38 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
599599
/**
600600
* Update sections based on the given change set.
601601
*/
602-
- (void)_updateSectionContextsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet
602+
- (void)_updateSectionsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet
603603
{
604604
ASDisplayNodeAssertMainThread();
605605

606-
if (!_dataSourceFlags.contextForSection) {
607-
return;
608-
}
609-
610606
if (changeSet.includesReloadData) {
611-
[map removeAllSectionContexts];
607+
[map removeAllSections];
612608

613609
NSUInteger sectionCount = [self itemCountsFromDataSource].size();
614610
NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
615-
[self _insertSectionContextsIntoMap:map indexes:sectionIndexes];
611+
[self _insertSectionsIntoMap:map indexes:sectionIndexes];
616612
// Return immediately because reloadData can't be used in conjuntion with other updates.
617613
return;
618614
}
619615

620616
for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
621-
[map removeSectionContextsAtIndexes:change.indexSet];
617+
[map removeSectionsAtIndexes:change.indexSet];
622618
}
623619

624620
for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
625-
[self _insertSectionContextsIntoMap:map indexes:change.indexSet];
621+
[self _insertSectionsIntoMap:map indexes:change.indexSet];
626622
}
627623
}
628624

629-
- (void)_insertSectionContextsIntoMap:(ASMutableElementMap *)map indexes:(NSIndexSet *)sectionIndexes
625+
- (void)_insertSectionsIntoMap:(ASMutableElementMap *)map indexes:(NSIndexSet *)sectionIndexes
630626
{
631627
ASDisplayNodeAssertMainThread();
632-
633-
if (!_dataSourceFlags.contextForSection) {
634-
return;
635-
}
636-
628+
637629
[sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
638-
id<ASSectionContext> context = [_dataSource dataController:self contextForSection:idx];
630+
id<ASSectionContext> context;
631+
if (_dataSourceFlags.contextForSection) {
632+
context = [_dataSource dataController:self contextForSection:idx];
633+
}
639634
ASSection *section = [[ASSection alloc] initWithSectionID:_nextSectionID context:context];
640635
[map insertSection:section atIndex:idx];
641636
_nextSectionID++;

Source/Details/ASElementMap.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,20 @@ AS_SUBCLASSING_RESTRICTED
6464
@property (copy, readonly) NSArray<ASCollectionElement *> *itemElements;
6565

6666
/**
67-
* Returns the index path that corresponds to the same element in @c map at the given @c indexPath. O(1)
67+
* Returns the index path that corresponds to the same element in @c map at the given @c indexPath.
68+
* O(1) for items, fast O(N) for sections.
69+
*
70+
* Note you can pass "section index paths" of length 1 and get a corresponding section index path.
6871
*/
6972
- (nullable NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map;
7073

74+
/**
75+
* Returns the section index into the receiver that corresponds to the same element in @c map at @c sectionIndex. Fast O(N).
76+
*
77+
* Returns @c NSNotFound if the section does not exist in the receiver.
78+
*/
79+
- (NSInteger)convertSection:(NSInteger)sectionIndex fromMap:(ASElementMap *)map;
80+
7181
/**
7282
* Returns the index path for the given element. O(1)
7383
*/

Source/Details/ASElementMap.m

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ - (instancetype)init
4747

4848
- (instancetype)initWithSections:(NSArray<ASSection *> *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements
4949
{
50+
NSCParameterAssert(items.count == sections.count);
51+
5052
if (self = [super init]) {
5153
_sections = [sections copy];
5254
_sectionsOfItems = [[NSArray alloc] initWithArray:items copyItems:YES];
@@ -157,8 +159,25 @@ - (ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttri
157159

158160
- (NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map
159161
{
160-
id element = [map elementForItemAtIndexPath:indexPath];
161-
return [self indexPathForElement:element];
162+
if (indexPath.item == NSNotFound) {
163+
// Section index path
164+
NSInteger result = [self convertSection:indexPath.section fromMap:map];
165+
return (result != NSNotFound ? [NSIndexPath indexPathWithIndex:result] : nil);
166+
} else {
167+
// Item index path
168+
ASCollectionElement *element = [map elementForItemAtIndexPath:indexPath];
169+
return [self indexPathForElement:element];
170+
}
171+
}
172+
173+
- (NSInteger)convertSection:(NSInteger)sectionIndex fromMap:(ASElementMap *)map
174+
{
175+
if (![map sectionIndexIsValid:sectionIndex assert:YES]) {
176+
return NSNotFound;
177+
}
178+
179+
ASSection *section = map.sections[sectionIndex];
180+
return [_sections indexOfObjectIdenticalTo:section];
162181
}
163182

164183
#pragma mark - NSCopying

Source/Private/ASMutableElementMap.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ AS_SUBCLASSING_RESTRICTED
3737

3838
- (void)insertSection:(ASSection *)section atIndex:(NSInteger)index;
3939

40-
- (void)removeAllSectionContexts;
40+
- (void)removeAllSections;
4141

4242
/// Only modifies the array of ASSection * objects
43-
- (void)removeSectionContextsAtIndexes:(NSIndexSet *)indexes;
43+
- (void)removeSectionsAtIndexes:(NSIndexSet *)indexes;
4444

4545
- (void)removeAllElements;
4646

Source/Private/ASMutableElementMap.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ - (id)copyWithZone:(NSZone *)zone
4848
return [[ASElementMap alloc] initWithSections:_sections items:_sectionsOfItems supplementaryElements:_supplementaryElements];
4949
}
5050

51-
- (void)removeAllSectionContexts
51+
- (void)removeAllSections
5252
{
5353
[_sections removeAllObjects];
5454
}
@@ -63,7 +63,7 @@ - (void)removeItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
6363
ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(_sectionsOfItems, indexPaths);
6464
}
6565

66-
- (void)removeSectionContextsAtIndexes:(NSIndexSet *)indexes
66+
- (void)removeSectionsAtIndexes:(NSIndexSet *)indexes
6767
{
6868
[_sections removeObjectsAtIndexes:indexes];
6969
}

Source/Private/ASSection.h

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,29 @@
1616
//
1717

1818
#import <Foundation/Foundation.h>
19+
#import <AsyncDisplayKit/ASBaseDefines.h>
1920

2021
@protocol ASSectionContext;
2122

23+
NS_ASSUME_NONNULL_BEGIN
24+
25+
/**
26+
* An object representing the metadata for a section of elements in a collection.
27+
*
28+
* Its sectionID is namespaced to the data controller that created the section.
29+
*
30+
* These are useful for tracking the movement & lifetime of sections, independent of
31+
* their contents.
32+
*/
33+
AS_SUBCLASSING_RESTRICTED
2234
@interface ASSection : NSObject
2335

24-
@property (nonatomic, assign, readonly) NSInteger sectionID;
25-
@property (nonatomic, strong, nullable, readonly) id<ASSectionContext> context;
36+
@property (assign, readonly) NSInteger sectionID;
37+
@property (strong, nullable, readonly) id<ASSectionContext> context;
2638

27-
- (nullable instancetype)init __unavailable;
28-
- (nullable instancetype)initWithSectionID:(NSInteger)sectionID context:(nullable id<ASSectionContext>)context NS_DESIGNATED_INITIALIZER;
39+
- (instancetype)init NS_UNAVAILABLE;
40+
- (instancetype)initWithSectionID:(NSInteger)sectionID context:(nullable id<ASSectionContext>)context NS_DESIGNATED_INITIALIZER;
2941

3042
@end
43+
44+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)