Initial Work for Range Controller to Support Supplementary Elements (#3140)

* Initial work supporting supplementaries in range controller

* Rename indexPathForElementIfItem
This commit is contained in:
Adlai Holler 2017-03-06 10:11:00 -08:00 committed by GitHub
parent d0ad24808b
commit d59ea3902d
12 changed files with 172 additions and 69 deletions

View File

@ -98,7 +98,7 @@
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; };
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; };
509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; }; 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; };
509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */; };
509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */; }; 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */; };
636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; };
@ -490,7 +490,7 @@
205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbstractLayoutController.h; sourceTree = "<group>"; }; 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbstractLayoutController.h; sourceTree = "<group>"; };
205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAbstractLayoutController.mm; sourceTree = "<group>"; }; 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAbstractLayoutController.mm; sourceTree = "<group>"; };
205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = "<group>"; }; 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = "<group>"; };
205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionViewLayoutController.mm; sourceTree = "<group>"; }; 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewLayoutController.m; sourceTree = "<group>"; };
205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CoreGraphics+ASConvenience.h"; sourceTree = "<group>"; }; 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CoreGraphics+ASConvenience.h"; sourceTree = "<group>"; };
205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CoreGraphics+ASConvenience.m"; sourceTree = "<group>"; }; 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CoreGraphics+ASConvenience.m"; sourceTree = "<group>"; };
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; };
@ -1071,7 +1071,7 @@
68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */, 68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */,
68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */, 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */,
205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */,
205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */,
696F01EA1DD2AF450049FBD5 /* ASEventLog.h */, 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */,
696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */, 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */,
4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */, 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */,
@ -1861,7 +1861,7 @@
69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */,
CC034A021E5FAF9700626263 /* ASElementMap.m in Sources */, CC034A021E5FAF9700626263 /* ASElementMap.m in Sources */,
B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */,
509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */,
B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */,
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */,
B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */,

View File

@ -1679,13 +1679,51 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return _rangeController; return _rangeController;
} }
- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController /// The UIKit version of this method is only available on iOS >= 9
- (NSArray<NSIndexPath *> *)asdk_indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)kind
{ {
ASDisplayNodeAssertMainThread(); if (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_9_0) {
// Calling -indexPathsForVisibleItems will trigger UIKit to call reloadData if it never has, which can result return [self indexPathsForVisibleSupplementaryElementsOfKind:kind];
// in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast. }
BOOL isZeroSized = CGSizeEqualToSize(self.bounds.size, CGSizeZero);
return isZeroSized ? @[] : [self indexPathsForVisibleItems]; // iOS 8 workaround
// We cannot use willDisplaySupplementaryView/didEndDisplayingSupplementaryView
// because those methods send index paths for _deleted items_ (invalid index paths)
[self layoutIfNeeded];
NSArray<UICollectionViewLayoutAttributes *> *visibleAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:self.bounds];
NSMutableArray *result = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *attributes in visibleAttributes) {
if (attributes.representedElementCategory == UICollectionElementCategorySupplementaryView
&& [attributes.representedElementKind isEqualToString:kind]) {
[result addObject:attributes.indexPath];
}
}
return result;
}
- (NSArray<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController
{
if (CGRectIsEmpty(self.bounds)) {
return @[];
}
ASElementMap *map = _dataController.visibleMap;
NSMutableArray<ASCollectionElement *> *result = [NSMutableArray array];
// Visible items
for (NSIndexPath *indexPath in self.indexPathsForVisibleItems) {
ASCollectionElement *element = [map elementForItemAtIndexPath:indexPath];
[result addObject:element];
}
// Visible supplementary elements
for (NSString *kind in map.supplementaryElementKinds) {
for (NSIndexPath *indexPath in [self asdk_indexPathsForVisibleSupplementaryElementsOfKind:kind]) {
ASCollectionElement *element = [map supplementaryElementOfKind:kind atIndexPath:indexPath];
[result addObject:element];
}
}
return result;
} }
- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController - (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController

View File

@ -649,18 +649,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (NSArray<ASCellNode *> *)visibleNodes - (NSArray<ASCellNode *> *)visibleNodes
{ {
NSArray *indexPaths = [self visibleNodeIndexPathsForRangeController:_rangeController]; NSArray<ASCollectionElement *> *elements = [self visibleElementsForRangeController:_rangeController];
return ASArrayByFlatMapping(elements, ASCollectionElement *e, e.node);
NSMutableArray<ASCellNode *> *visibleNodes = [NSMutableArray array];
for (NSIndexPath *indexPath in indexPaths) {
ASCellNode *node = [self nodeForRowAtIndexPath:indexPath];
if (node) {
// It is possible for UITableView to return indexPaths before the node is completed.
[visibleNodes addObject:node];
}
}
return visibleNodes;
} }
- (void)beginUpdates - (void)beginUpdates
@ -1358,7 +1348,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
return _rangeController; return _rangeController;
} }
- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController - (NSArray<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -1383,7 +1373,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
}]]; }]];
} }
return visibleIndexPaths; ASElementMap *map = _dataController.visibleMap;
return ASArrayByFlatMapping(visibleIndexPaths, NSIndexPath *indexPath, [map elementForItemAtIndexPath:indexPath]);
} }
- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController - (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController

View File

@ -217,3 +217,31 @@
id __val = x;\ id __val = x;\
((c *) ([__val isKindOfClass:[c class]] ? __val : nil));\ ((c *) ([__val isKindOfClass:[c class]] ? __val : nil));\
}) })
/**
* Create a new set by mapping `collection` over `work`, ignoring nil.
*/
#define ASSetByFlatMapping(collection, decl, work) ({ \
NSMutableSet *s = [NSMutableSet set]; \
for (decl in collection) {\
id result = work; \
if (result != nil) { \
[s addObject:result]; \
} \
} \
s; \
})
/**
* Create a new array by mapping `collection` over `work`, ignoring nil.
*/
#define ASArrayByFlatMapping(collection, decl, work) ({ \
NSMutableArray *a = [NSMutableArray array]; \
for (decl in collection) {\
id result = work; \
if (result != nil) { \
[a addObject:result]; \
} \
} \
a; \
})

View File

@ -167,13 +167,13 @@ extern CGRect CGRectExpandToRangeWithScrollableDirections(CGRect rect, ASRangeTu
#pragma mark - Abstract Index Path Range Support #pragma mark - Abstract Index Path Range Support
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType - (NSSet<ASCollectionElement *> *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map
{ {
ASDisplayNodeAssertNotSupported(); ASDisplayNodeAssertNotSupported();
return nil; return nil;
} }
- (void)allIndexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet *__autoreleasing _Nullable *)preloadSet - (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map
{ {
ASDisplayNodeAssertNotSupported(); ASDisplayNodeAssertNotSupported();
} }

View File

@ -12,6 +12,7 @@
#import <AsyncDisplayKit/ASAssert.h> #import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASCollectionView.h> #import <AsyncDisplayKit/ASCollectionView.h>
#import <AsyncDisplayKit/ASElementMap.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h> #import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h> #import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
@ -46,14 +47,14 @@ typedef struct ASRangeGeometry ASRangeGeometry;
return self; return self;
} }
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType - (NSSet<ASCollectionElement *> *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map
{ {
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType];
CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters];
return [self indexPathsForItemsWithinRangeBounds:rangeBounds]; return [self elementsWithinRangeBounds:rangeBounds map:map];
} }
- (void)allIndexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet **)displaySet preloadSet:(NSSet **)preloadSet - (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map
{ {
if (displaySet == NULL || preloadSet == NULL) { if (displaySet == NULL || preloadSet == NULL) {
return; return;
@ -67,12 +68,12 @@ typedef struct ASRangeGeometry ASRangeGeometry;
CGRect unionBounds = CGRectUnion(displayBounds, preloadBounds); CGRect unionBounds = CGRectUnion(displayBounds, preloadBounds);
NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:unionBounds]; NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:unionBounds];
NSMutableSet *display = [NSMutableSet setWithCapacity:layoutAttributes.count]; NSMutableSet<ASCollectionElement *> *display = [NSMutableSet setWithCapacity:layoutAttributes.count];
NSMutableSet *preload = [NSMutableSet setWithCapacity:layoutAttributes.count]; NSMutableSet<ASCollectionElement *> *preload = [NSMutableSet setWithCapacity:layoutAttributes.count];
for (UICollectionViewLayoutAttributes *la in layoutAttributes) { for (UICollectionViewLayoutAttributes *la in layoutAttributes) {
// Manually filter out elements that don't intersect the range bounds. // Manually filter out elements that don't intersect the range bounds.
// See comment in indexPathsForItemsWithinRangeBounds: // See comment in elementsForItemsWithinRangeBounds:
// This is re-implemented here so that the iteration over layoutAttributes can be done once to check both ranges. // This is re-implemented here so that the iteration over layoutAttributes can be done once to check both ranges.
CGRect frame = la.frame; CGRect frame = la.frame;
BOOL intersectsDisplay = CGRectIntersectsRect(displayBounds, frame); BOOL intersectsDisplay = CGRectIntersectsRect(displayBounds, frame);
@ -82,13 +83,13 @@ typedef struct ASRangeGeometry ASRangeGeometry;
continue; continue;
} }
// Avoid excessive retains and releases, as well as property calls. We know the indexPath is kept alive by la. // Avoid excessive retains and releases, as well as property calls. We know the element is kept alive by map.
__unsafe_unretained NSIndexPath *indexPath = la.indexPath; __unsafe_unretained ASCollectionElement *e = [map elementForLayoutAttributes:la];
if (intersectsDisplay) { if (intersectsDisplay) {
[display addObject:indexPath]; [display addObject:e];
} }
if (intersectsPreload) { if (intersectsPreload) {
[preload addObject:indexPath]; [preload addObject:e];
} }
} }
@ -97,14 +98,12 @@ typedef struct ASRangeGeometry ASRangeGeometry;
return; return;
} }
- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds - (NSSet<ASCollectionElement *> *)elementsWithinRangeBounds:(CGRect)rangeBounds map:(ASElementMap *)map
{ {
NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds];
NSMutableSet *indexPathSet = [NSMutableSet setWithCapacity:layoutAttributes.count]; NSMutableSet<ASCollectionElement *> *elementSet = [NSMutableSet setWithCapacity:layoutAttributes.count];
for (UICollectionViewLayoutAttributes *la in layoutAttributes) { for (UICollectionViewLayoutAttributes *la in layoutAttributes) {
//ASDisplayNodeAssert(![indexPathSet containsObject:la.indexPath], @"Shouldn't already contain indexPath");
// Manually filter out elements that don't intersect the range bounds. // Manually filter out elements that don't intersect the range bounds.
// If a layout returns elements outside the requested rect this can be a huge problem. // If a layout returns elements outside the requested rect this can be a huge problem.
// For instance in a paging flow, you may only want to preload 3 pages (one center, one on each side) // For instance in a paging flow, you may only want to preload 3 pages (one center, one on each side)
@ -113,10 +112,10 @@ typedef struct ASRangeGeometry ASRangeGeometry;
if (CATransform3DIsIdentity(la.transform3D) && CGRectIntersectsRect(la.frame, rangeBounds) == NO) { if (CATransform3DIsIdentity(la.transform3D) && CGRectIntersectsRect(la.frame, rangeBounds) == NO) {
continue; continue;
} }
[indexPathSet addObject:la.indexPath]; [elementSet addObject:[map elementForLayoutAttributes:la]];
} }
return indexPathSet; return elementSet;
} }
- (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection - (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection

View File

@ -16,7 +16,7 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class ASCellNode; @class ASCollectionElement, ASElementMap;
ASDISPLAYNODE_EXTERN_C_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN
@ -34,9 +34,9 @@ ASDISPLAYNODE_EXTERN_C_END
- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType;
- (NSSet<NSIndexPath *> *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - (NSSet<ASCollectionElement *> *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map;
- (void)allIndexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet * _Nullable * _Nullable)displaySet preloadSet:(NSSet * _Nullable * _Nullable)preloadSet; - (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet<ASCollectionElement *> * _Nullable * _Nullable)displaySet preloadSet:(NSSet<ASCollectionElement *> * _Nullable * _Nullable)preloadSet map:(ASElementMap *)map;
@optional @optional

View File

@ -107,9 +107,9 @@ AS_SUBCLASSING_RESTRICTED
/** /**
* @param rangeController Sender. * @param rangeController Sender.
* *
* @return an array of index paths corresponding to the nodes currently visible onscreen (i.e., the visible range). * @return an array of elements corresponding to the data currently visible onscreen (i.e., the visible range).
*/ */
- (NSArray<NSIndexPath *> *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController; - (NSArray<ASCollectionElement *> *)visibleElementsForRangeController:(ASRangeController *)rangeController;
/** /**
* @param rangeController Sender. * @param rangeController Sender.

View File

@ -209,10 +209,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
// TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges
// Example: ... = [_layoutController indexPathsForScrolling:scrollDirection rangeType:ASLayoutRangeTypeVisible]; // Example: ... = [_layoutController indexPathsForScrolling:scrollDirection rangeType:ASLayoutRangeTypeVisible];
NSArray<NSIndexPath *> *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self]; NSSet<ASCollectionElement *> *visibleElements = [NSSet setWithArray:[_dataSource visibleElementsForRangeController:self]];
ASWeakSet *newVisibleNodes = [[ASWeakSet alloc] init]; ASWeakSet *newVisibleNodes = [[ASWeakSet alloc] init];
if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
[self _setVisibleNodes:newVisibleNodes]; [self _setVisibleNodes:newVisibleNodes];
return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later
} }
@ -248,32 +248,36 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
// Check if both Display and Preload are unique. If they are, we load them with a single fetch from the layout controller for performance. // Check if both Display and Preload are unique. If they are, we load them with a single fetch from the layout controller for performance.
BOOL optimizedLoadingOfBothRanges = (equalDisplayPreload == NO && equalDisplayVisible == NO && emptyDisplayRange == NO); BOOL optimizedLoadingOfBothRanges = (equalDisplayPreload == NO && equalDisplayVisible == NO && emptyDisplayRange == NO);
NSSet<NSIndexPath *> *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths]; NSSet<ASCollectionElement *> *displayElements = nil;
NSSet<NSIndexPath *> *displayIndexPaths = nil; NSSet<ASCollectionElement *> *preloadElements = nil;
NSSet<NSIndexPath *> *preloadIndexPaths = nil;
if (optimizedLoadingOfBothRanges) { if (optimizedLoadingOfBothRanges) {
[_layoutController allIndexPathsForScrolling:scrollDirection rangeMode:rangeMode displaySet:&displayIndexPaths preloadSet:&preloadIndexPaths]; [_layoutController allElementsForScrolling:scrollDirection rangeMode:rangeMode displaySet:&displayElements preloadSet:&preloadElements map:map];
} else { } else {
if (emptyDisplayRange == YES) { if (emptyDisplayRange == YES) {
displayIndexPaths = [NSSet set]; displayElements = [NSSet set];
} if (equalDisplayVisible == YES) { } if (equalDisplayVisible == YES) {
displayIndexPaths = visibleIndexPaths; displayElements = visibleElements;
} else { } else {
// Calculating only the Display range means the Preload range is either the same as Display or Visible. // Calculating only the Display range means the Preload range is either the same as Display or Visible.
displayIndexPaths = [_layoutController indexPathsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; displayElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map];
} }
BOOL equalPreloadVisible = ASRangeTuningParametersEqualToRangeTuningParameters(parametersPreload, ASRangeTuningParametersZero); BOOL equalPreloadVisible = ASRangeTuningParametersEqualToRangeTuningParameters(parametersPreload, ASRangeTuningParametersZero);
if (equalDisplayPreload == YES) { if (equalDisplayPreload == YES) {
preloadIndexPaths = displayIndexPaths; preloadElements = displayElements;
} else if (equalPreloadVisible == YES) { } else if (equalPreloadVisible == YES) {
preloadIndexPaths = visibleIndexPaths; preloadElements = visibleElements;
} else { } else {
preloadIndexPaths = [_layoutController indexPathsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload]; preloadElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map];
} }
} }
// For now we are only interested in items. Filter-map out from element to item-index-path.
NSSet<NSIndexPath *> *visibleIndexPaths = ASSetByFlatMapping(visibleElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]);
NSSet<NSIndexPath *> *displayIndexPaths = ASSetByFlatMapping(displayElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]);
NSSet<NSIndexPath *> *preloadIndexPaths = ASSetByFlatMapping(preloadElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]);
// Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on // Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on
// the network or display queues before preloading (offscreen) nodes are enqueued. // the network or display queues before preloading (offscreen) nodes are enqueued.
NSMutableOrderedSet<NSIndexPath *> *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; NSMutableOrderedSet<NSIndexPath *> *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths];

View File

@ -13,6 +13,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASAssert.h> #import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASElementMap.h>
@interface ASTableLayoutController() @interface ASTableLayoutController()
@end @end
@ -30,28 +31,24 @@
#pragma mark - ASLayoutController #pragma mark - ASLayoutController
/** - (NSSet<ASCollectionElement *> *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map
* IndexPath array for the element in the working range.
*/
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{ {
CGRect bounds = _tableView.bounds; CGRect bounds = _tableView.bounds;
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType];
CGRect rangeBounds = CGRectExpandToRangeWithScrollableDirections(bounds, tuningParameters, ASScrollDirectionVerticalDirections, scrollDirection); CGRect rangeBounds = CGRectExpandToRangeWithScrollableDirections(bounds, tuningParameters, ASScrollDirectionVerticalDirections, scrollDirection);
NSArray *array = [_tableView indexPathsForRowsInRect:rangeBounds]; NSArray *array = [_tableView indexPathsForRowsInRect:rangeBounds];
return [NSSet setWithArray:array]; return ASSetByFlatMapping(array, NSIndexPath *indexPath, [map elementForItemAtIndexPath:indexPath]);
} }
- (void)allIndexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet **)displaySet preloadSet:(NSSet **)preloadSet - (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map
{ {
if (displaySet == NULL || preloadSet == NULL) { if (displaySet == NULL || preloadSet == NULL) {
return; return;
} }
*displaySet = [self indexPathsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; *displaySet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map];
*preloadSet = [self indexPathsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload]; *preloadSet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map];
return; return;
} }

View File

@ -11,7 +11,7 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@class ASCollectionElement, ASSection; @class ASCollectionElement, ASSection, UICollectionViewLayoutAttributes;
@protocol ASSectionContext; @protocol ASSectionContext;
/** /**
@ -27,6 +27,11 @@ AS_SUBCLASSING_RESTRICTED
*/ */
@property (readonly) NSInteger numberOfSections; @property (readonly) NSInteger numberOfSections;
/**
* The kinds of supplementary elements present in this map. O(1)
*/
@property (copy, readonly) NSArray<NSString *> *supplementaryElementKinds;
/** /**
* Returns number of items in the given section. O(1) * Returns number of items in the given section. O(1)
*/ */
@ -54,6 +59,11 @@ AS_SUBCLASSING_RESTRICTED
*/ */
- (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element; - (nullable NSIndexPath *)indexPathForElement:(ASCollectionElement *)element;
/**
* Returns the index path for the given element, if it represents a cell. O(1)
*/
- (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element;
/** /**
* Returns the item-element at the given index path. O(1) * Returns the item-element at the given index path. O(1)
*/ */
@ -64,6 +74,13 @@ AS_SUBCLASSING_RESTRICTED
*/ */
- (nullable ASCollectionElement *)supplementaryElementOfKind:(NSString *)supplementaryElementKind atIndexPath:(NSIndexPath *)indexPath; - (nullable ASCollectionElement *)supplementaryElementOfKind:(NSString *)supplementaryElementKind atIndexPath:(NSIndexPath *)indexPath;
/**
* Returns the element that corresponds to the given layout attributes, if any.
*
* NOTE: This method only regards the category, kind, and index path of the attributes object. Elements do not
* have any concept of size/position.
*/
- (nullable ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;
#pragma mark - Initialization -- Only Useful to ASDataController #pragma mark - Initialization -- Only Useful to ASDataController

View File

@ -73,6 +73,11 @@
return _sectionsOfItems.count; return _sectionsOfItems.count;
} }
- (NSArray<NSString *> *)supplementaryElementKinds
{
return _supplementaryElements.allKeys;
}
- (NSInteger)numberOfItemsInSection:(NSInteger)section - (NSInteger)numberOfItemsInSection:(NSInteger)section
{ {
return _sectionsOfItems[section].count; return _sectionsOfItems[section].count;
@ -88,6 +93,15 @@
return [_elementToIndexPathMap objectForKey:element]; return [_elementToIndexPathMap objectForKey:element];
} }
- (nullable NSIndexPath *)indexPathForElementIfCell:(ASCollectionElement *)element
{
if (element.supplementaryElementKind == nil) {
return [self indexPathForElement:element];
} else {
return nil;
}
}
- (nullable ASCollectionElement *)elementForItemAtIndexPath:(NSIndexPath *)indexPath - (nullable ASCollectionElement *)elementForItemAtIndexPath:(NSIndexPath *)indexPath
{ {
return (indexPath != nil) ? ASGetElementInTwoDimensionalArray(_sectionsOfItems, indexPath) : nil; return (indexPath != nil) ? ASGetElementInTwoDimensionalArray(_sectionsOfItems, indexPath) : nil;
@ -98,6 +112,21 @@
return _supplementaryElements[supplementaryElementKind][indexPath]; return _supplementaryElements[supplementaryElementKind][indexPath];
} }
- (ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
switch (layoutAttributes.representedElementCategory) {
case UICollectionElementCategoryCell:
// Cell
return [self elementForItemAtIndexPath:layoutAttributes.indexPath];
case UICollectionElementCategorySupplementaryView:
// Supplementary element.
return [self supplementaryElementOfKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath];
case UICollectionElementCategoryDecorationView:
// No support for decoration views.
return nil;
}
}
- (NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map - (NSIndexPath *)convertIndexPath:(NSIndexPath *)indexPath fromMap:(ASElementMap *)map
{ {
id element = [map elementForItemAtIndexPath:indexPath]; id element = [map elementForItemAtIndexPath:indexPath];