Complete overhaul of ASFlowLayoutController.

Introduced ASIndexPath for efficient handling of index paths in C++ vectors,
while maintaining the readability of ".section" and ".row" instead of
".first" and ".second" inside of complicated business logic.

Confirmed that the working range calls are firing appropriately during
ASTableViewStressTest, including the deallocation of the rich text placeholders
provided by ASTextNode.
This commit is contained in:
Scott Goodson
2015-07-04 20:22:04 -07:00
parent 57465c7fd3
commit 8fa092fb77
10 changed files with 224 additions and 167 deletions

View File

@@ -1337,6 +1337,7 @@ static NSInteger incrementIfFound(NSInteger i) {
{
self.layer.contents = nil;
_placeholderLayer.contents = nil;
_placeholderImage = nil;
}
- (void)recursivelyClearContents

View File

@@ -117,7 +117,7 @@ static BOOL _isInterceptedSelector(SEL sel)
_ASTableViewProxy *_proxyDelegate;
ASDataController *_dataController;
ASCollectionViewLayoutController *_layoutController;
ASFlowLayoutController *_layoutController;
ASRangeController *_rangeController;
@@ -159,9 +159,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
- (void)configureWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
{
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
_layoutController = [[ASCollectionViewLayoutController alloc] initWithScrollView:self collectionViewLayout:flowLayout];
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical];
_rangeController = [[ASRangeController alloc] init];
_rangeController.layoutController = _layoutController;
_rangeController.delegate = self;
@@ -169,6 +168,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
_dataController.dataSource = self;
_dataController.delegate = _rangeController;
_layoutController.dataSource = _dataController;
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
_asyncDataSourceLocked = NO;

View File

@@ -14,6 +14,5 @@
@interface ASCollectionViewLayoutController : ASAbstractLayoutController
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView;
- (instancetype)initWithScrollView:(UIScrollView *)scrollView collectionViewLayout:(UICollectionViewLayout *)layout;
@end

View File

@@ -78,20 +78,6 @@ typedef struct ASRangeGeometry ASRangeGeometry;
return self;
}
- (instancetype)initWithScrollView:(UIScrollView *)scrollView collectionViewLayout:(UICollectionViewLayout *)layout
{
if (!(self = [super init])) {
return nil;
}
_scrollableDirections = ASScrollDirectionVerticalDirections;
_scrollView = scrollView;
_collectionViewLayout = layout;
_updateRangeBoundsIndexedByRangeType = std::vector<CGRect>(ASLayoutRangeTypeCount);
return self;
}
#pragma mark -
#pragma mark Index Paths in Range

View File

@@ -8,7 +8,7 @@
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASDealloc2MainObject.h>
#import "ASFlowLayoutController.h"
@class ASCellNode;
@class ASDataController;
@@ -97,7 +97,8 @@ typedef NSUInteger ASDataControllerAnimationOptions;
* will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called.
* For each data updatin, the corresponding methods in delegate will be called.
*/
@interface ASDataController : ASDealloc2MainObject
@protocol ASFlowLayoutControllerDataSource;
@interface ASDataController : ASDealloc2MainObject <ASFlowLayoutControllerDataSource>
/**
Data source for fetching data info.
@@ -167,4 +168,6 @@ typedef NSUInteger ASDataControllerAnimationOptions;
- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths;
- (NSArray *)completedNodes; // This provides efficient access to the entire _completedNodes multidimensional array.
@end

View File

@@ -54,7 +54,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
_pendingEditCommandBlocks = [NSMutableArray array];
_editingTransactionQueue = [[NSOperationQueue alloc] init];
_editingTransactionQueue.qualityOfService = NSQualityOfServiceUserInitiated;
_editingTransactionQueue.maxConcurrentOperationCount = 1; // Serial queue
_editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue";
@@ -553,17 +552,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths
{
ASDisplayNodeAssertMainThread();
// Make sure that any asynchronous layout operations have finished so that those nodes are present.
// Otherwise a failure case could be:
// - Reload section 2, deleting all current nodes in that section.
// - New nodes are created and sizing is triggered, but they are not yet added to _completedNodes.
// - This method is called and includes an indexPath in section 2.
// - Unless we wait for the layout group to finish, we will crash with array out of bounds looking for the index in _completedNodes.
return ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, [indexPaths sortedArrayUsingSelector:@selector(compare:)]);
}
- (NSArray *)completedNodes
{
ASDisplayNodeAssertMainThread();
return _completedNodes;
}
#pragma mark - Dealloc
- (void)dealloc

View File

@@ -15,12 +15,20 @@ typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) {
ASFlowLayoutDirectionHorizontal,
};
@protocol ASFlowLayoutControllerDataSource
- (NSArray *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array.
@end
/**
* The controller for flow layout.
* 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) id <ASFlowLayoutControllerDataSource> dataSource;
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection;

View File

@@ -7,110 +7,66 @@
*/
#import "ASFlowLayoutController.h"
#import "ASAssert.h"
#import "ASDisplayNode.h"
#import "ASIndexPath.h"
#include <map>
#include <vector>
#include <cassert>
#import "ASAssert.h"
static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3;
@interface ASFlowLayoutController() {
std::vector<std::vector<CGSize> > _nodeSizes;
std::pair<int, int> _visibleRangeStartPos;
std::pair<int, int> _visibleRangeEndPos;
std::vector<std::pair<int, int>> _rangeStartPos;
std::vector<std::pair<int, int>> _rangeEndPos;
@interface ASFlowLayoutController()
{
ASIndexPathRange _visibleRange;
std::vector<ASIndexPathRange> _rangesByType; // All ASLayoutRangeTypes besides visible.
}
@end
@implementation ASFlowLayoutController
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection {
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection
{
if (!(self = [super init])) {
return nil;
}
_layoutDirection = layoutDirection;
_rangesByType = std::vector<ASIndexPathRange>(ASLayoutRangeTypeCount);
return self;
}
#pragma mark - Editing
- (void)insertNodesAtIndexPaths:(NSArray *)indexPaths withSizes:(NSArray *)nodeSizes
{
ASDisplayNodeAssert(indexPaths.count == nodeSizes.count, @"Inconsistent index paths and node size");
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
std::vector<CGSize> &v = _nodeSizes[indexPath.section];
v.insert(v.begin() + indexPath.row, [(NSValue *)nodeSizes[idx] CGSizeValue]);
}];
}
- (void)deleteNodesAtIndexPaths:(NSArray *)indexPaths
{
[indexPaths enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
std::vector<CGSize> &v = _nodeSizes[indexPath.section];
v.erase(v.begin() + indexPath.row);
}];
}
- (void)insertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet
{
__block int cnt = 0;
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSArray *nodes = sections[cnt++];
std::vector<CGSize> v;
v.reserve(nodes.count);
for (int i = 0; i < nodes.count; i++) {
v.push_back([nodes[i] CGSizeValue]);
}
_nodeSizes.insert(_nodeSizes.begin() + idx, v);
}];
}
- (void)deleteSectionsAtIndexSet:(NSIndexSet *)indexSet {
[indexSet enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *stop) {
_nodeSizes.erase(_nodeSizes.begin() +idx);
}];
}
#pragma mark - Visible Indices
- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths viewportSize:(CGSize)viewportSize rangeType:(ASLayoutRangeType)rangeType
{
if (!indexPaths.count) {
if (!indexPaths.count || rangeType >= _rangesByType.size()) {
return NO;
}
std::pair<int, int> rangeStartPos, rangeEndPos;
if (rangeType < _rangeStartPos.size() && rangeType < _rangeEndPos.size()) {
rangeStartPos = _rangeStartPos[rangeType];
rangeEndPos = _rangeEndPos[rangeType];
}
std::pair<int, int> startPos, endPos;
ASFindIndexPathRange(indexPaths, startPos, endPos);
if (rangeStartPos >= startPos || rangeEndPos <= endPos) {
ASIndexPathRange existingRange = _rangesByType[rangeType];
ASIndexPathRange newRange = [self indexPathRangeForIndexPaths:indexPaths];
ASIndexPath maximumStart = ASIndexPathMaximum(existingRange.start, newRange.start);
ASIndexPath minimumEnd = ASIndexPathMinimum(existingRange.end, newRange.end);
if (ASIndexPathEqualToIndexPath(maximumStart, existingRange.start) || ASIndexPathEqualToIndexPath(minimumEnd, existingRange.end)) {
return YES;
}
return ASFlowLayoutDistance(startPos, _visibleRangeStartPos, _nodeSizes) > ASFlowLayoutDistance(_visibleRangeStartPos, rangeStartPos, _nodeSizes) * kASFlowLayoutControllerRefreshingThreshold ||
ASFlowLayoutDistance(endPos, _visibleRangeEndPos, _nodeSizes) > ASFlowLayoutDistance(_visibleRangeEndPos, rangeEndPos, _nodeSizes) * kASFlowLayoutControllerRefreshingThreshold;
NSInteger newStartDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.start, newRange.start)];
NSInteger existingStartDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.start, existingRange.start)] * kASFlowLayoutControllerRefreshingThreshold;
NSInteger newEndDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.end, newRange.end)];
NSInteger existingEndDelta = [self flowLayoutDistanceForRange:ASIndexPathRangeMake(_visibleRange.end, existingRange.end)] * kASFlowLayoutControllerRefreshingThreshold;
return (newStartDelta > existingStartDelta) || (newEndDelta > existingEndDelta);
}
- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths
{
ASFindIndexPathRange(indexPaths, _visibleRangeStartPos, _visibleRangeEndPos);
_visibleRange = [self indexPathRangeForIndexPaths:indexPaths];
}
/**
@@ -138,100 +94,134 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3;
CGFloat backScreens = scrollDirection == leadingDirection ? tuningParameters.leadingBufferScreenfuls : tuningParameters.trailingBufferScreenfuls;
CGFloat frontScreens = scrollDirection == leadingDirection ? tuningParameters.trailingBufferScreenfuls : tuningParameters.leadingBufferScreenfuls;
std::pair<int, int> startIter = ASFindIndexForRange(_nodeSizes, _visibleRangeStartPos, - backScreens * viewportScreenMetric, _layoutDirection);
std::pair<int, int> endIter = ASFindIndexForRange(_nodeSizes, _visibleRangeEndPos, frontScreens * viewportScreenMetric, _layoutDirection);
ASIndexPath startPath = [self findIndexPathAtDistance:(-backScreens * viewportScreenMetric) fromIndexPath:_visibleRange.start];
ASIndexPath endPath = [self findIndexPathAtDistance:(frontScreens * viewportScreenMetric) fromIndexPath:_visibleRange.end];
ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath");
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
while (startIter != endIter) {
[indexPathSet addObject:[NSIndexPath indexPathForRow:startIter.second inSection:startIter.first]];
startIter.second++;
NSArray *completedNodes = [_dataSource completedNodes];
while (!ASIndexPathEqualToIndexPath(startPath, endPath)) {
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:startPath]];
startPath.row++;
// Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized.
while (startIter.second == _nodeSizes[startIter.first].size() && startIter.first < _nodeSizes.size()) {
startIter.second = 0;
startIter.first++;
while (startPath.row >= [(NSArray *)completedNodes[startPath.section] count] && startPath.section < completedNodes.count - 1) {
startPath.row = 0;
startPath.section++;
ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never reach a further section than endPath");
}
}
[indexPathSet addObject:[NSIndexPath indexPathForRow:endIter.second inSection:endIter.first]];
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]];
return indexPathSet;
}
#pragma mark - Utility
static void ASFindIndexPathRange(NSArray *indexPaths, std::pair<int, int> &startPos, std::pair<int, int> &endPos)
- (ASIndexPathRange)indexPathRangeForIndexPaths:(NSArray *)indexPaths
{
NSIndexPath *initialIndexPath = [indexPaths firstObject];
startPos = endPos = {initialIndexPath.section, initialIndexPath.row};
// 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;
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
std::pair<int, int> p(indexPath.section, indexPath.row);
startPos = MIN(startPos, p);
endPos = MAX(endPos, p);
currentIndexPath = [indexPath ASIndexPathValue];
range.start = ASIndexPathMinimum(range.start, currentIndexPath);
range.end = ASIndexPathMaximum(range.end, currentIndexPath);
}];
return range;
}
static const std::pair<int, int> ASFindIndexForRange(const std::vector<std::vector<CGSize>> &nodes,
const std::pair<int, int> &pos,
CGFloat range,
ASFlowLayoutDirection layoutDirection)
- (ASIndexPath)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(ASIndexPath)start
{
std::pair<int, int> cur = pos, pre = pos;
// "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;
if (range < 0.0 && cur.first >= 0 && cur.first < nodes.size() && cur.second >= 0 && cur.second < nodes[cur.first].size()) {
// search backward
while (range < 0.0 && cur.first >= 0 && cur.second >= 0) {
pre = cur;
CGSize size = nodes[cur.first][cur.second];
range += layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height;
cur.second--;
while (cur.second < 0 && cur.first > 0) {
cur.second = (int)nodes[--cur.first].size() - 1;
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 (cur.second < 0) {
cur = pre;
if (end.row < 0) {
end = previous;
}
} else {
// search forward
while (range > 0.0 && cur.first >= 0 && cur.first < nodes.size() && cur.second >= 0 && cur.second < nodes[cur.first].size()) {
pre = cur;
CGSize size = nodes[cur.first][cur.second];
range -= layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height;
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;
cur.second++;
while (cur.second == nodes[cur.first].size() && cur.first < (int)nodes.size() - 1) {
cur.second = 0;
cur.first++;
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 (cur.second == nodes[cur.first].size()) {
cur = pre;
if (end.row >= numberOfRowsInSection) {
end = previous;
}
}
return cur;
return end;
}
static int ASFlowLayoutDistance(const std::pair<int, int> &start, const std::pair<int, int> &end, const std::vector<std::vector<CGSize>> &nodes)
- (NSInteger)flowLayoutDistanceForRange:(ASIndexPathRange)range
{
if (start == end) {
// 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;
} else if (start > end) {
return - ASFlowLayoutDistance(end, start, nodes);
}
NSInteger totalRowCount = 0;
NSUInteger numberOfRowsInSection = 0;
NSArray *completedNodes = [_dataSource completedNodes];
int res = 0;
for (int i = start.first; i <= end.first; i++) {
res += (i == end.first ? end.second + 1 : nodes[i].size()) - (i == start.first ? start.second : 0);
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));
}
}
return res;
ASDisplayNodeAssert(totalRowCount >= 0, @"totalRowCount in flowLayoutDistanceForRange: should not be negative");
return totalRowCount;
}
@end

View File

@@ -0,0 +1,84 @@
//
// ASIndexPath.h
// Pods
//
// Created by Scott Goodson on 7/4/15.
//
// A much more efficient way to handle index paths than NSIndexPath.
// For best results, use C++ vectors; NSValue wrapping with Cocoa collections
// would make NSIndexPath a much better choice.
//
typedef struct {
NSInteger section;
NSInteger row;
} ASIndexPath;
typedef struct {
ASIndexPath start;
ASIndexPath end;
} ASIndexPathRange;
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);
}
@interface NSIndexPath (ASIndexPathAdditions)
+ (NSIndexPath *)indexPathWithASIndexPath:(ASIndexPath)indexPath;
- (ASIndexPath)ASIndexPathValue;
@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

@@ -191,9 +191,6 @@
}];
ASDisplayNodePerformBlockOnMainThread(^{
if ([_layoutController respondsToSelector:@selector(insertNodesAtIndexPaths:withSizes:)]) {
[_layoutController insertNodesAtIndexPaths:indexPaths withSizes:nodeSizes];
}
_rangeIsValid = NO;
[_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
});
@@ -201,9 +198,6 @@
- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
ASDisplayNodePerformBlockOnMainThread(^{
if ([_layoutController respondsToSelector:@selector(deleteNodesAtIndexPaths:)]) {
[_layoutController deleteNodesAtIndexPaths:indexPaths];
}
_rangeIsValid = NO;
[_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
});
@@ -223,9 +217,6 @@
}];
ASDisplayNodePerformBlockOnMainThread(^{
if ([_layoutController respondsToSelector:@selector(insertSections:atIndexSet:)]) {
[_layoutController insertSections:sectionNodeSizes atIndexSet:indexSet];
}
_rangeIsValid = NO;
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
@@ -233,9 +224,6 @@
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
ASDisplayNodePerformBlockOnMainThread(^{
if ([_layoutController respondsToSelector:@selector(deleteSectionsAtIndexSet:)]) {
[_layoutController deleteSectionsAtIndexSet:indexSet];
}
_rangeIsValid = NO;
[_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});