Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to map between sections even if they're empty #660

Merged
merged 3 commits into from
Nov 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [Layout] Fixes a deadlock in layout. [#638](https://github.com/TextureGroup/Texture/pull/638) [Garrett Moon](https://github.com/garrettmoon)
- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler)
- [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)
- [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)

## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)
Expand Down
59 changes: 22 additions & 37 deletions Source/ASCollectionView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@
return __val; \
}

#define ASIndexPathForSection(section) [NSIndexPath indexPathForItem:0 inSection:section]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this macro because using item 0 is only something that's done for flow layout headers and footers. In any other context, "index path for section" would mean a one-node index path.


#define ASFlowLayoutDefault(layout, property, default) \
({ \
UICollectionViewFlowLayout *flowLayout = ASDynamicCast(layout, UICollectionViewFlowLayout); \
Expand Down Expand Up @@ -679,19 +677,13 @@ - (NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath wai
if (indexPath == nil) {
return nil;
}

// If this is a section index path, we don't currently have a method
// to do a mapping.
if (indexPath.item == NSNotFound) {
return indexPath;
} else {
NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
if (viewIndexPath == nil && wait) {
[self waitUntilAllUpdatesAreCommitted];
return [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:NO];
}
return viewIndexPath;

NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
if (viewIndexPath == nil && wait) {
[self waitUntilAllUpdatesAreCommitted];
return [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:NO];
}
return viewIndexPath;
}

/**
Expand Down Expand Up @@ -725,13 +717,7 @@ - (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath
return nil;
}

// If this is a section index path, we don't currently have a method
// to do a mapping.
if (indexPath.item == NSNotFound) {
return indexPath;
} else {
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
}
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
}

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

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

// For the methods that call delegateIndexPathForSection:withSelector:, translate the section from
// visibleMap to pendingMap. If the section no longer exists, or the delegate doesn't implement
// the selector, we will return a nil indexPath (and then use the ASFlowLayoutDefault).
- (NSIndexPath *)delegateIndexPathForSection:(NSInteger)section withSelector:(SEL)selector
// the selector, we will return NSNotFound (and then use the ASFlowLayoutDefault).
- (NSInteger)delegateIndexForSection:(NSInteger)section withSelector:(SEL)selector
{
if ([_asyncDelegate respondsToSelector:selector]) {
return [_dataController.pendingMap convertIndexPath:ASIndexPathForSection(section)
fromMap:_dataController.visibleMap];
return [_dataController.pendingMap convertSection:section fromMap:_dataController.visibleMap];
} else {
return nil;
return NSNotFound;
}
}

- (UIEdgeInsets)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
insetForSectionAtIndex:(NSInteger)section
{
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
if (indexPath) {
return [(id)_asyncDelegate collectionView:cv layout:l insetForSectionAtIndex:indexPath.section];
section = [self delegateIndexForSection:section withSelector:_cmd];
if (section != NSNotFound) {
return [(id)_asyncDelegate collectionView:cv layout:l insetForSectionAtIndex:section];
}
return ASFlowLayoutDefault(l, sectionInset, UIEdgeInsetsZero);
}

- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
if (indexPath) {
section = [self delegateIndexForSection:section withSelector:_cmd];
if (section != NSNotFound) {
return [(id)_asyncDelegate collectionView:cv layout:l
minimumInteritemSpacingForSectionAtIndex:indexPath.section];
minimumInteritemSpacingForSectionAtIndex:section];
}
return ASFlowLayoutDefault(l, minimumInteritemSpacing, 10.0); // Default is documented as 10.0
}

- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l
minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
NSIndexPath *indexPath = [self delegateIndexPathForSection:section withSelector:_cmd];
if (indexPath) {
section = [self delegateIndexForSection:section withSelector:_cmd];
if (section != NSNotFound) {
return [(id)_asyncDelegate collectionView:cv layout:l
minimumLineSpacingForSectionAtIndex:indexPath.section];
minimumLineSpacingForSectionAtIndex:section];
}
return ASFlowLayoutDefault(l, minimumLineSpacing, 10.0); // Default is documented as 10.0
}
Expand Down
24 changes: 6 additions & 18 deletions Source/ASTableView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -588,18 +588,12 @@ - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath

- (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait
{
// If this is a section index path, we don't currently have a method
// to do a mapping.
if (indexPath == nil || indexPath.row == NSNotFound) {
return indexPath;
} else {
NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
if (viewIndexPath == nil && wait) {
[self waitUntilAllUpdatesAreCommitted];
return [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:NO];
}
return viewIndexPath;
NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap];
if (viewIndexPath == nil && wait) {
[self waitUntilAllUpdatesAreCommitted];
return [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:NO];
}
return viewIndexPath;
}

- (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath
Expand All @@ -608,13 +602,7 @@ - (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath
return nil;
}

// If this is a section index path, we don't currently have a method
// to do a mapping.
if (indexPath.row == NSNotFound) {
return indexPath;
} else {
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
}
return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap];
}

- (NSArray<NSIndexPath *> *)convertIndexPathsToTableNode:(NSArray<NSIndexPath *> *)indexPaths
Expand Down
29 changes: 12 additions & 17 deletions Source/Details/ASDataController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
ASMutableElementMap *mutableMap = [previousMap mutableCopy];

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

Expand Down Expand Up @@ -599,43 +599,38 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet
/**
* Update sections based on the given change set.
*/
- (void)_updateSectionContextsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet
- (void)_updateSectionsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet
{
ASDisplayNodeAssertMainThread();

if (!_dataSourceFlags.contextForSection) {
return;
}

if (changeSet.includesReloadData) {
[map removeAllSectionContexts];
[map removeAllSections];

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

for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
[map removeSectionContextsAtIndexes:change.indexSet];
[map removeSectionsAtIndexes:change.indexSet];
}

for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
[self _insertSectionContextsIntoMap:map indexes:change.indexSet];
[self _insertSectionsIntoMap:map indexes:change.indexSet];
}
}

- (void)_insertSectionContextsIntoMap:(ASMutableElementMap *)map indexes:(NSIndexSet *)sectionIndexes
- (void)_insertSectionsIntoMap:(ASMutableElementMap *)map indexes:(NSIndexSet *)sectionIndexes
{
ASDisplayNodeAssertMainThread();

if (!_dataSourceFlags.contextForSection) {
return;
}


[sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
id<ASSectionContext> context = [_dataSource dataController:self contextForSection:idx];
id<ASSectionContext> context;
if (_dataSourceFlags.contextForSection) {
context = [_dataSource dataController:self contextForSection:idx];
}
ASSection *section = [[ASSection alloc] initWithSectionID:_nextSectionID context:context];
[map insertSection:section atIndex:idx];
_nextSectionID++;
Expand Down
12 changes: 11 additions & 1 deletion Source/Details/ASElementMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,20 @@ AS_SUBCLASSING_RESTRICTED
@property (copy, readonly) NSArray<ASCollectionElement *> *itemElements;

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

/**
* Returns the section index into the receiver that corresponds to the same element in @c map at @c sectionIndex. Fast O(N).
*
* Returns @c NSNotFound if the section does not exist in the receiver.
*/
- (NSInteger)convertSection:(NSInteger)sectionIndex fromMap:(ASElementMap *)map;

/**
* Returns the index path for the given element. O(1)
*/
Expand Down
23 changes: 21 additions & 2 deletions Source/Details/ASElementMap.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ - (instancetype)init

- (instancetype)initWithSections:(NSArray<ASSection *> *)sections items:(ASCollectionElementTwoDimensionalArray *)items supplementaryElements:(ASSupplementaryElementDictionary *)supplementaryElements
{
NSCParameterAssert(items.count == sections.count);

if (self = [super init]) {
_sections = [sections copy];
_sectionsOfItems = [[NSArray alloc] initWithArray:items copyItems:YES];
Expand Down Expand Up @@ -157,8 +159,25 @@ - (ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttri

- (NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map
{
id element = [map elementForItemAtIndexPath:indexPath];
return [self indexPathForElement:element];
if (indexPath.item == NSNotFound) {
// Section index path
NSInteger result = [self convertSection:indexPath.section fromMap:map];
return (result != NSNotFound ? [NSIndexPath indexPathWithIndex:result] : nil);
} else {
// Item index path
ASCollectionElement *element = [map elementForItemAtIndexPath:indexPath];
return [self indexPathForElement:element];
}
}

- (NSInteger)convertSection:(NSInteger)sectionIndex fromMap:(ASElementMap *)map
{
if (![map sectionIndexIsValid:sectionIndex assert:YES]) {
return NSNotFound;
}

ASSection *section = map.sections[sectionIndex];
return [_sections indexOfObjectIdenticalTo:section];
}

#pragma mark - NSCopying
Expand Down
4 changes: 2 additions & 2 deletions Source/Private/ASMutableElementMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ AS_SUBCLASSING_RESTRICTED

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

- (void)removeAllSectionContexts;
- (void)removeAllSections;

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

- (void)removeAllElements;

Expand Down
4 changes: 2 additions & 2 deletions Source/Private/ASMutableElementMap.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ - (id)copyWithZone:(NSZone *)zone
return [[ASElementMap alloc] initWithSections:_sections items:_sectionsOfItems supplementaryElements:_supplementaryElements];
}

- (void)removeAllSectionContexts
- (void)removeAllSections
{
[_sections removeAllObjects];
}
Expand All @@ -63,7 +63,7 @@ - (void)removeItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(_sectionsOfItems, indexPaths);
}

- (void)removeSectionContextsAtIndexes:(NSIndexSet *)indexes
- (void)removeSectionsAtIndexes:(NSIndexSet *)indexes
{
[_sections removeObjectsAtIndexes:indexes];
}
Expand Down
22 changes: 18 additions & 4 deletions Source/Private/ASSection.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,29 @@
//

#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>

@protocol ASSectionContext;

NS_ASSUME_NONNULL_BEGIN

/**
* An object representing the metadata for a section of elements in a collection.
*
* Its sectionID is namespaced to the data controller that created the section.
*
* These are useful for tracking the movement & lifetime of sections, independent of
* their contents.
*/
AS_SUBCLASSING_RESTRICTED
@interface ASSection : NSObject

@property (nonatomic, assign, readonly) NSInteger sectionID;
@property (nonatomic, strong, nullable, readonly) id<ASSectionContext> context;
@property (assign, readonly) NSInteger sectionID;
@property (strong, nullable, readonly) id<ASSectionContext> context;

- (nullable instancetype)init __unavailable;
- (nullable instancetype)initWithSectionID:(NSInteger)sectionID context:(nullable id<ASSectionContext>)context NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithSectionID:(NSInteger)sectionID context:(nullable id<ASSectionContext>)context NS_DESIGNATED_INITIALIZER;

@end

NS_ASSUME_NONNULL_END