Improve TableLayoutController: Simplify, Fix Crash & Improve Perf (#2969)

* Refactor FlowLayoutController -> TableLayoutController

* Use most conservative row index path
This commit is contained in:
Adlai Holler
2017-02-05 18:24:15 -08:00
committed by GitHub
parent f71eba77af
commit 86b669dc38
14 changed files with 253 additions and 392 deletions

View File

@@ -95,8 +95,6 @@
34EFC7731B701D0700AD841F /* ASAbsoluteLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASAbsoluteLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED191B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm */; };
3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; };
430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; };
430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; };
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; };
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; };
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; };
@@ -262,8 +260,8 @@
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; };
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; };
B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */; };
B350621B1B010EFD0018CF92 /* ASTableLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */; settings = {ATTRIBUTES = (Private, ); }; };
B350621C1B010EFD0018CF92 /* ASTableLayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */; };
B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */; };
B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -533,12 +531,10 @@
299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = "<group>"; };
29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASBasicImageDownloaderContextTests.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = "<group>"; };
430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = "<group>"; };
464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = "<group>"; };
4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = "<group>"; };
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = "<group>"; };
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = "<group>"; };
4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = "<group>"; };
4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableLayoutController.m; sourceTree = "<group>"; };
4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = "<group>"; };
683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Deprecated.h"; sourceTree = "<group>"; };
68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = "<group>"; };
@@ -1062,15 +1058,13 @@
68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */,
205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */,
205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */,
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */,
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */,
4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */,
4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */,
058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */,
058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */,
68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */,
68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */,
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */,
430E7C8D1B4C23F100697A4C /* ASIndexPath.h */,
430E7C8E1B4C23F100697A4C /* ASIndexPath.m */,
4640521D1A3F83C40061C0BA /* ASLayoutController.h */,
292C59991A956527007E5DD6 /* ASLayoutRangeType.h */,
68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */,
@@ -1437,13 +1431,12 @@
CC0F88601E4280B800576FED /* _ASCollectionViewCell.h in Headers */,
B35062001B010EFD0018CF92 /* ASEditableTextNode.h in Headers */,
680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */,
B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */,
B350621B1B010EFD0018CF92 /* ASTableLayoutController.h in Headers */,
B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */,
C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */,
7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */,
B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */,
B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */,
430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */,
34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */,
34EFC7671B701CD900AD841F /* ASLayout.h in Headers */,
DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */,
@@ -1873,7 +1866,7 @@
B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */,
254C6B881BF94F8A003EC431 /* ASTextKitRenderer.mm in Sources */,
CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */,
B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */,
B350621C1B010EFD0018CF92 /* ASTableLayoutController.m in Sources */,
B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */,
9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.m in Sources */,
DE89C17B1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */,
@@ -1883,7 +1876,6 @@
68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */,
B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */,
254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */,
430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */,
34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */,
AC6145441D8AFD4F003D62A2 /* ASSection.m in Sources */,
34EFC75E1B701BF000AD841F /* ASInternalHelpers.m in Sources */,

View File

@@ -25,6 +25,7 @@
#import <AsyncDisplayKit/ASTableNode.h>
#import <AsyncDisplayKit/ASRangeController.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASTableLayoutController.h>
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
#import <AsyncDisplayKit/ASBatchContext.h>
@@ -120,7 +121,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
ASTableViewProxy *_proxyDataSource;
ASTableViewProxy *_proxyDelegate;
ASFlowLayoutController *_layoutController;
ASTableLayoutController *_layoutController;
ASRangeController *_rangeController;
@@ -250,7 +251,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)configureWithDataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog
{
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical];
_layoutController = [[ASTableLayoutController alloc] initWithTableView:self];
_rangeController = [[ASRangeController alloc] init];
_rangeController.layoutController = _layoutController;
@@ -260,8 +261,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
_dataController = [[dataControllerClass alloc] initWithDataSource:self eventLog:eventLog];
_dataController.delegate = _rangeController;
_dataController.environmentDelegate = self;
_layoutController.dataSource = _dataController;
_leadingScreensForBatching = 2.0;
_batchContext = [[ASBatchContext alloc] init];

View File

@@ -11,6 +11,7 @@
//
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASWeakSet.h>
#import <AsyncDisplayKit/UIImage+ASConvenience.h>

View File

@@ -88,7 +88,6 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASIndexPath.h>
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASMutableAttributedStringBuilder.h>

View File

@@ -13,7 +13,6 @@
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBlockTypes.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASFlowLayoutController.h>
#import <AsyncDisplayKit/ASEventLog.h>
#ifdef __cplusplus
#import <vector>
@@ -112,7 +111,7 @@ extern NSString * const ASCollectionInvalidUpdateException;
* will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called.
* For each data updating, the corresponding methods in delegate will be called.
*/
@interface ASDataController : NSObject <ASFlowLayoutControllerDataSource>
@interface ASDataController : NSObject
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;

View File

@@ -1,42 +0,0 @@
//
// ASFlowLayoutController.h
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
NS_ASSUME_NONNULL_BEGIN
@class ASCellNode;
typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) {
ASFlowLayoutDirectionVertical,
ASFlowLayoutDirectionHorizontal,
};
@protocol ASFlowLayoutControllerDataSource
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array.
@end
/**
* An optimized flow layout controller that supports only vertical or horizontal scrolling, not simultaneously two-dimensional scrolling.
* It is used for all ASTableViews, and may be used with ASCollectionView.
*/
@interface ASFlowLayoutController : ASAbstractLayoutController
@property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection;
@property (nonatomic, readwrite, weak) id <ASFlowLayoutControllerDataSource> dataSource;
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,201 +0,0 @@
//
// ASFlowLayoutController.mm
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <AsyncDisplayKit/ASFlowLayoutController.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASIndexPath.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#include <map>
#include <vector>
@interface ASFlowLayoutController()
{
ASIndexPathRange _visibleRange;
std::vector<ASIndexPathRange> _rangesByType; // All ASLayoutRangeTypes besides visible.
}
@end
@implementation ASFlowLayoutController
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection
{
if (!(self = [super init])) {
return nil;
}
_layoutDirection = layoutDirection;
_rangesByType = std::vector<ASIndexPathRange>(ASLayoutRangeTypeCount);
return self;
}
#pragma mark - Visible Indices
- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths
{
_visibleRange = [self indexPathRangeForIndexPaths:indexPaths];
}
/**
* IndexPath array for the element in the working range.
*/
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{
CGSize viewportSize = [self viewportSize];
CGFloat viewportDirectionalSize = 0.0;
ASDirectionalScreenfulBuffer directionalBuffer = { 0, 0 };
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType];
if (_layoutDirection == ASFlowLayoutDirectionHorizontal) {
viewportDirectionalSize = viewportSize.width;
directionalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters);
} else {
viewportDirectionalSize = viewportSize.height;
directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters);
}
ASIndexPath startPath = [self findIndexPathAtDistance:(-directionalBuffer.negativeDirection * viewportDirectionalSize)
fromIndexPath:_visibleRange.start];
ASIndexPath endPath = [self findIndexPathAtDistance:(directionalBuffer.positiveDirection * viewportDirectionalSize)
fromIndexPath:_visibleRange.end];
ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath");
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
NSArray *completedNodes = [_dataSource completedNodes];
ASIndexPath currPath = startPath;
while (!ASIndexPathEqualToIndexPath(currPath, endPath)) {
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:currPath]];
currPath.row++;
// Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized.
while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < endPath.section) {
currPath.row = 0;
currPath.section++;
}
}
ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath");
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]];
return indexPathSet;
}
#pragma mark - Utility
- (ASIndexPathRange)indexPathRangeForIndexPaths:(NSArray *)indexPaths
{
// Set up an initial value so the MIN and MAX can work in the enumeration.
__block ASIndexPath currentIndexPath = [[indexPaths firstObject] ASIndexPathValue];
__block ASIndexPathRange range;
range.start = currentIndexPath;
range.end = currentIndexPath;
for (NSIndexPath *indexPath in indexPaths) {
currentIndexPath = [indexPath ASIndexPathValue];
range.start = ASIndexPathMinimum(range.start, currentIndexPath);
range.end = ASIndexPathMaximum(range.end, currentIndexPath);
}
return range;
}
- (ASIndexPath)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(ASIndexPath)start
{
// "end" is the index path we'll advance until we have gone far enough from "start" to reach "distance"
ASIndexPath end = start;
// "previous" will store one iteration before "end", in case we go too far and need to reset "end" to be "previous"
ASIndexPath previous = start;
NSArray *completedNodes = [_dataSource completedNodes];
NSUInteger numberOfSections = [completedNodes count];
NSUInteger numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
// If "distance" is negative, advance "end" backwards across rows and sections.
// Otherwise, advance forward. In either case, bring "distance" closer to zero by the dimension of each row passed.
if (distance < 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) {
while (distance < 0.0 && end.section >= 0 && end.row >= 0) {
previous = end;
ASDisplayNode *node = completedNodes[end.section][end.row];
CGSize size = node.calculatedSize;
distance += (_layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height);
end.row--;
// If we've gone to a negative row, set to the last row of the previous section. While loop is required to handle empty sections.
while (end.row < 0 && end.section > 0) {
end.section--;
numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
end.row = numberOfRowsInSection - 1;
}
}
if (end.row < 0) {
end = previous;
}
} else {
while (distance > 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) {
previous = end;
ASDisplayNode *node = completedNodes[end.section][end.row];
CGSize size = node.calculatedSize;
distance -= _layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height;
end.row++;
// If we've gone beyond the section, reset to the beginning of the next section. While loop is required to handle empty sections.
while (end.row >= numberOfRowsInSection && end.section < numberOfSections - 1) {
end.row = 0;
end.section++;
numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
}
}
if (end.row >= numberOfRowsInSection) {
end = previous;
}
}
return end;
}
- (NSInteger)flowLayoutDistanceForRange:(ASIndexPathRange)range
{
// This method should only be called with the range in proper order (start comes before end).
ASDisplayNodeAssert(ASIndexPathEqualToIndexPath(ASIndexPathMinimum(range.start, range.end), range.start), @"flowLayoutDistanceForRange: called with invalid range");
if (ASIndexPathEqualToIndexPath(range.start, range.end)) {
return 0;
}
NSInteger totalRowCount = 0;
NSUInteger numberOfRowsInSection = 0;
NSArray *completedNodes = [_dataSource completedNodes];
for (NSInteger section = range.start.section; section <= range.end.section; section++) {
numberOfRowsInSection = [(NSArray *)completedNodes[section] count];
totalRowCount += numberOfRowsInSection;
if (section == range.start.section) {
// For the start section, make sure we don't count the rows before the start row.
totalRowCount -= range.start.row;
} else if (section == range.end.section) {
// For the start section, make sure we don't count the rows after the end row.
totalRowCount -= (numberOfRowsInSection - (range.end.row + 1));
}
}
ASDisplayNodeAssert(totalRowCount >= 0, @"totalRowCount in flowLayoutDistanceForRange: should not be negative");
return totalRowCount;
}
@end

View File

@@ -1,50 +0,0 @@
//
// ASIndexPath.h
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <Foundation/Foundation.h>
typedef struct {
NSInteger section;
NSInteger row;
} ASIndexPath;
typedef struct {
ASIndexPath start;
ASIndexPath end;
} ASIndexPathRange;
NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN
extern ASIndexPath ASIndexPathMake(NSInteger section, NSInteger row);
extern BOOL ASIndexPathEqualToIndexPath(ASIndexPath first, ASIndexPath second);
extern ASIndexPath ASIndexPathMinimum(ASIndexPath first, ASIndexPath second);
extern ASIndexPath ASIndexPathMaximum(ASIndexPath first, ASIndexPath second);
extern ASIndexPathRange ASIndexPathRangeMake(ASIndexPath first, ASIndexPath second);
extern BOOL ASIndexPathRangeEqualToIndexPathRange(ASIndexPathRange first, ASIndexPathRange second);
ASDISPLAYNODE_EXTERN_C_END
@interface NSIndexPath (ASIndexPathAdditions)
+ (NSIndexPath *)indexPathWithASIndexPath:(ASIndexPath)indexPath;
- (ASIndexPath)ASIndexPathValue;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,75 +0,0 @@
//
// ASIndexPath.m
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <AsyncDisplayKit/ASIndexPath.h>
#import <UIKit/UIKit.h>
ASIndexPath ASIndexPathMake(NSInteger section, NSInteger row)
{
ASIndexPath indexPath;
indexPath.section = section;
indexPath.row = row;
return indexPath;
}
BOOL ASIndexPathEqualToIndexPath(ASIndexPath first, ASIndexPath second)
{
return (first.section == second.section && first.row == second.row);
}
ASIndexPath ASIndexPathMinimum(ASIndexPath first, ASIndexPath second)
{
if (first.section < second.section) {
return first;
} else if (first.section > second.section) {
return second;
} else {
return (first.row < second.row ? first : second);
}
}
ASIndexPath ASIndexPathMaximum(ASIndexPath first, ASIndexPath second)
{
if (first.section > second.section) {
return first;
} else if (first.section < second.section) {
return second;
} else {
return (first.row > second.row ? first : second);
}
}
ASIndexPathRange ASIndexPathRangeMake(ASIndexPath first, ASIndexPath second)
{
ASIndexPathRange range;
range.start = ASIndexPathMinimum(first, second);
range.end = ASIndexPathMaximum(first, second);
return range;
}
BOOL ASIndexPathRangeEqualToIndexPathRange(ASIndexPathRange first, ASIndexPathRange second)
{
return ASIndexPathEqualToIndexPath(first.start, second.start) && ASIndexPathEqualToIndexPath(first.end, second.end);
}
@implementation NSIndexPath (ASIndexPathAdditions)
+ (NSIndexPath *)indexPathWithASIndexPath:(ASIndexPath)indexPath
{
return [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
}
- (ASIndexPath)ASIndexPathValue
{
return ASIndexPathMake(self.section, self.row);
}
@end

View File

@@ -39,7 +39,7 @@ ASDISPLAYNODE_EXTERN_C_END
- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType;
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType;
- (NSSet<NSIndexPath *> *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType;
@optional

View File

@@ -11,6 +11,7 @@
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDataController.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import <AsyncDisplayKit/ASRangeControllerUpdateRangeProtocol+Beta.h>
#import <AsyncDisplayKit/ASBaseDefines.h>

View File

@@ -0,0 +1,30 @@
//
// ASTableLayoutController.h
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
NS_ASSUME_NONNULL_BEGIN
@class UITableView;
/**
* A layout controller designed for use with UITableView.
*/
AS_SUBCLASSING_RESTRICTED
@interface ASTableLayoutController : ASAbstractLayoutController
@property (nonatomic, weak, readonly) UITableView *tableView;
- (instancetype)initWithTableView:(UITableView *)tableView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,200 @@
//
// ASTableLayoutController.m
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <AsyncDisplayKit/ASTableLayoutController.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASAssert.h>
@interface ASTableLayoutController()
@property (nonatomic, strong) NSIndexPath *minVisibleIndexPath;
@property (nonatomic, strong) NSIndexPath *maxVisibleIndexPath;
@end
@implementation ASTableLayoutController
- (instancetype)initWithTableView:(UITableView *)tableView
{
if (!(self = [super init])) {
return nil;
}
_tableView = tableView;
return self;
}
#pragma mark - Visible Indices
- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths
{
_minVisibleIndexPath = [ASTableLayoutController fastArrayMin:indexPaths];
_maxVisibleIndexPath = [ASTableLayoutController fastArrayMax:indexPaths];
}
/**
* IndexPath array for the element in the working range.
*/
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{
if (_minVisibleIndexPath == nil) {
return [NSSet set];
}
CGSize viewportSize = [self viewportSize];
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType];
ASDirectionalScreenfulBuffer directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters);
NSIndexPath *startPath = [self findIndexPathAtDistance:(-directionalBuffer.negativeDirection * viewportSize.height)
fromIndexPath:_minVisibleIndexPath];
NSIndexPath *endPath = [self findIndexPathAtDistance:(directionalBuffer.positiveDirection * viewportSize.height)
fromIndexPath:_maxVisibleIndexPath];
NSSet *indexPaths = [self indexPathsFromIndexPath:startPath toIndexPath:endPath];
return indexPaths;
}
#pragma mark - Utility
- (NSIndexPath *)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(NSIndexPath *)start
{
BOOL downward = (distance >= 0);
CGRect startRowRect = [_tableView rectForRowAtIndexPath:start];
CGFloat targetY = distance + (downward ? CGRectGetMaxY(startRowRect) : CGRectGetMinY(startRowRect));
// Before first row.
NSIndexPath *firstIndexPath = [self firstIndexPathInTableView];
if (targetY <= CGRectGetMaxY([_tableView rectForRowAtIndexPath:firstIndexPath])) {
return firstIndexPath;
}
// After last row.
NSIndexPath *lastIndexPath = [self lastIndexPathInTableView];
if (targetY >= CGRectGetMinY([_tableView rectForRowAtIndexPath:lastIndexPath])) {
return lastIndexPath;
}
/**
* There may not be a row at any given EXACT point, for these possible reasons:
* - There is a section header/footer at that point.
* - That point is beyond the start/end of the table content. (Handled above)
*
* Solution: Make a search rect, and if we don't
* find any rows, keep doubling its height and searching again. In practice,
* this will virtually always find a row on the first try (unless you have a
* tall section header and we land near the middle.)
*/
NSIndexPath *result = nil;
for (CGRect searchRect = [ASTableLayoutController initialSearchRectDownward:downward targetY:targetY];
// continue while result is nil
result == nil;
// grow search rect after each loop
searchRect = [ASTableLayoutController growSearchRect:searchRect downward:downward]) {
NSArray *rows = [_tableView indexPathsForRowsInRect:searchRect];
if (downward) {
result = [ASTableLayoutController fastArrayMin:rows];
} else {
result = [ASTableLayoutController fastArrayMax:rows];
}
}
return result;
}
- (NSSet<NSIndexPath *> *)indexPathsFromIndexPath:(NSIndexPath *)startIndexPath toIndexPath:(NSIndexPath *)endIndexPath
{
ASDisplayNodeAssert([startIndexPath compare:endIndexPath] != NSOrderedDescending, @"Index paths must be in nondescending order. Start: %@, end %@", startIndexPath, endIndexPath);
NSMutableSet *result = [NSMutableSet set];
NSInteger const endSection = endIndexPath.section;
NSInteger i = startIndexPath.row;
for (NSInteger s = startIndexPath.section; s <= endSection; s++) {
// If end section, row <= end.item. Otherwise (row <= sectionRowCount - 1).
NSInteger const rowLimit = (s == endSection ? endIndexPath.row : ([_tableView numberOfRowsInSection:s] - 1));
for (; i <= rowLimit; i++) {
[result addObject:[NSIndexPath indexPathForRow:i inSection:s]];
}
i = 0;
}
return result;
}
- (nullable NSIndexPath *)firstIndexPathInTableView
{
NSInteger sectionCount = _tableView.numberOfSections;
for (NSInteger s = 0; s < sectionCount; s++) {
if ([_tableView numberOfRowsInSection:s] > 0) {
return [NSIndexPath indexPathForRow:0 inSection:s];
}
}
return nil;
}
- (nullable NSIndexPath *)lastIndexPathInTableView
{
NSInteger lastSectionWithAnyRows = _tableView.numberOfSections;
NSInteger rowCount = 0;
while (rowCount == 0) {
lastSectionWithAnyRows -= 1;
if (lastSectionWithAnyRows < 0) {
return nil;
}
rowCount = [_tableView numberOfRowsInSection:lastSectionWithAnyRows];
}
return [NSIndexPath indexPathForRow:rowCount - 1 inSection:lastSectionWithAnyRows];
}
// Same as valueForKeyPath:@"@min.self" but faster
+ (nullable id)fastArrayMin:(NSArray *)array ASDISPLAYNODE_CONST
{
id min = nil;
for (id obj in array) {
if (min == nil || [obj compare:min] == NSOrderedAscending) {
min = obj;
}
}
return min;
}
// Same as valueForKeyPath:@"@max.self" but faster
+ (nullable id)fastArrayMax:(NSArray *)array ASDISPLAYNODE_CONST
{
id max = nil;
for (id obj in array) {
if (max == nil || [max compare:obj] == NSOrderedAscending) {
max = obj;
}
}
return max;
}
+ (CGRect)initialSearchRectDownward:(BOOL)downward targetY:(CGFloat)targetY ASDISPLAYNODE_CONST
{
static CGFloat const kInitialRowSearchHeight = 100;
CGRect result = CGRectMake(0, targetY, 1, kInitialRowSearchHeight);
if (!downward) {
result.origin.y -= result.size.height;
}
return result;
}
+ (CGRect)growSearchRect:(CGRect)searchRect downward:(BOOL)downward ASDISPLAYNODE_CONST
{
CGRect result = searchRect;
if (!downward) {
result.origin.y -= result.size.height;
}
result.size.height *= 2;
return result;
}
@end

View File

@@ -70,6 +70,14 @@
# endif
#endif
#ifndef ASDISPLAYNODE_CONST
# if ASDISPLAYNODE_GNUC (3, 0)
# define ASDISPLAYNODE_CONST __attribute__ ((const))
# else
# define ASDISPLAYNODE_CONST /* no const */
# endif
#endif
#ifndef ASDISPLAYNODE_WARN_UNUSED
# if ASDISPLAYNODE_GNUC (3, 4)
# define ASDISPLAYNODE_WARN_UNUSED __attribute__ ((warn_unused_result))