mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-01-06 21:22:44 +00:00
Improve TableLayoutController: Simplify, Fix Crash & Improve Perf (#2969)
* Refactor FlowLayoutController -> TableLayoutController * Use most conservative row index path
This commit is contained in:
@@ -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 */,
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
30
AsyncDisplayKit/Details/ASTableLayoutController.h
Normal file
30
AsyncDisplayKit/Details/ASTableLayoutController.h
Normal 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
|
||||
200
AsyncDisplayKit/Details/ASTableLayoutController.m
Normal file
200
AsyncDisplayKit/Details/ASTableLayoutController.m
Normal 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
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user