mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-19 12:10:55 +00:00
577 lines
18 KiB
Plaintext
577 lines
18 KiB
Plaintext
/* 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 "ASCollectionView.h"
|
|
|
|
#import "ASAssert.h"
|
|
#import "ASFlowLayoutController.h"
|
|
#import "ASRangeController.h"
|
|
#import "ASDataController.h"
|
|
#import "ASDisplayNodeInternal.h"
|
|
#import "ASBatchFetching.h"
|
|
|
|
const static NSUInteger kASCollectionViewAnimationNone = 0;
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Proxying.
|
|
|
|
/**
|
|
* ASCollectionView intercepts and/or overrides a few of UICollectionView's critical data source and delegate methods.
|
|
*
|
|
* Any selector included in this function *MUST* be implemented by ASCollectionView.
|
|
*/
|
|
static BOOL _isInterceptedSelector(SEL sel)
|
|
{
|
|
return (
|
|
// handled by ASCollectionView node<->cell machinery
|
|
sel == @selector(collectionView:cellForItemAtIndexPath:) ||
|
|
sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
|
|
|
|
// handled by ASRangeController
|
|
sel == @selector(numberOfSectionsInCollectionView:) ||
|
|
sel == @selector(collectionView:numberOfItemsInSection:) ||
|
|
|
|
// used for ASRangeController visibility updates
|
|
sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
|
|
sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
|
|
|
|
// used for batch fetching API
|
|
sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Stand-in for UICollectionViewDataSource and UICollectionViewDelegate. Any method calls we intercept are routed to ASCollectionView;
|
|
* everything else leaves AsyncDisplayKit safely and arrives at the original intended data source and delegate.
|
|
*/
|
|
@interface _ASCollectionViewProxy : NSProxy
|
|
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASCollectionView *)interceptor;
|
|
@end
|
|
|
|
@implementation _ASCollectionViewProxy {
|
|
id<NSObject> __weak _target;
|
|
ASCollectionView * __weak _interceptor;
|
|
}
|
|
|
|
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASCollectionView *)interceptor
|
|
{
|
|
// -[NSProxy init] is undefined
|
|
if (!self) {
|
|
return nil;
|
|
}
|
|
|
|
ASDisplayNodeAssert(target, @"target must not be nil");
|
|
ASDisplayNodeAssert(interceptor, @"interceptor must not be nil");
|
|
|
|
_target = target;
|
|
_interceptor = interceptor;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)respondsToSelector:(SEL)aSelector
|
|
{
|
|
return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]);
|
|
}
|
|
|
|
- (id)forwardingTargetForSelector:(SEL)aSelector
|
|
{
|
|
if (_isInterceptedSelector(aSelector)) {
|
|
return _interceptor;
|
|
}
|
|
|
|
return [_target respondsToSelector:aSelector] ? _target : nil;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark ASCollectionView.
|
|
|
|
@interface ASCollectionView () <ASRangeControllerDelegate, ASDataControllerSource> {
|
|
_ASCollectionViewProxy *_proxyDataSource;
|
|
_ASCollectionViewProxy *_proxyDelegate;
|
|
|
|
ASDataController *_dataController;
|
|
ASRangeController *_rangeController;
|
|
ASFlowLayoutController *_layoutController;
|
|
|
|
BOOL _performingBatchUpdates;
|
|
NSMutableArray *_batchUpdateBlocks;
|
|
|
|
BOOL _asyncDataFetchingEnabled;
|
|
|
|
ASBatchContext *_batchContext;
|
|
}
|
|
|
|
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
|
|
|
@end
|
|
|
|
@implementation ASCollectionView
|
|
|
|
#pragma mark -
|
|
#pragma mark Lifecycle.
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
|
|
{
|
|
return [self initWithFrame:frame collectionViewLayout:layout asyncDataFetching:NO];
|
|
}
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
|
{
|
|
if (!(self = [super initWithFrame:frame collectionViewLayout:layout]))
|
|
return nil;
|
|
|
|
ASDisplayNodeAssert([layout isKindOfClass:UICollectionViewFlowLayout.class], @"only flow layouts are currently supported");
|
|
|
|
ASFlowLayoutDirection direction = (((UICollectionViewFlowLayout *)layout).scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASFlowLayoutDirectionHorizontal : ASFlowLayoutDirectionVertical;
|
|
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:direction];
|
|
|
|
_rangeController = [[ASRangeController alloc] init];
|
|
_rangeController.delegate = self;
|
|
_rangeController.layoutController = _layoutController;
|
|
|
|
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
|
|
_dataController.delegate = _rangeController;
|
|
_dataController.dataSource = self;
|
|
|
|
_batchContext = [[ASBatchContext alloc] init];
|
|
|
|
_leadingScreensForBatching = 1.0;
|
|
|
|
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
|
|
_asyncDataSourceLocked = NO;
|
|
|
|
_performingBatchUpdates = NO;
|
|
_batchUpdateBlocks = [NSMutableArray array];
|
|
|
|
[self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"_ASCollectionViewCell"];
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Overrides.
|
|
|
|
- (void)reloadData
|
|
{
|
|
ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set.");
|
|
ASDisplayNodePerformBlockOnMainThread(^{
|
|
[super reloadData];
|
|
});
|
|
[_dataController reloadDataWithAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)setDataSource:(id<UICollectionViewDataSource>)dataSource
|
|
{
|
|
ASDisplayNodeAssert(NO, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property.");
|
|
}
|
|
|
|
- (void)setDelegate:(id<UICollectionViewDelegate>)delegate
|
|
{
|
|
// Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here.
|
|
ASDisplayNodeAssert(delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property.");
|
|
}
|
|
|
|
- (void)setAsyncDataSource:(id<ASCollectionViewDataSource>)asyncDataSource
|
|
{
|
|
if (_asyncDataSource == asyncDataSource)
|
|
return;
|
|
|
|
if (asyncDataSource == nil) {
|
|
_asyncDataSource = nil;
|
|
_proxyDataSource = nil;
|
|
super.dataSource = nil;
|
|
} else {
|
|
_asyncDataSource = asyncDataSource;
|
|
_proxyDataSource = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
|
|
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
|
|
}
|
|
}
|
|
|
|
- (void)setAsyncDelegate:(id<ASCollectionViewDelegate>)asyncDelegate
|
|
{
|
|
if (_asyncDelegate == asyncDelegate)
|
|
return;
|
|
|
|
if (asyncDelegate == nil) {
|
|
_asyncDelegate = nil;
|
|
_proxyDelegate = nil;
|
|
super.delegate = nil;
|
|
} else {
|
|
_asyncDelegate = asyncDelegate;
|
|
_proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
|
|
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
|
|
}
|
|
}
|
|
|
|
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
|
|
{
|
|
[_layoutController setTuningParameters:tuningParameters forRangeType:rangeType];
|
|
}
|
|
|
|
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
|
|
{
|
|
return [_layoutController tuningParametersForRangeType:rangeType];
|
|
}
|
|
|
|
- (ASRangeTuningParameters)rangeTuningParameters
|
|
{
|
|
return [self tuningParametersForRangeType:ASLayoutRangeTypeRender];
|
|
}
|
|
|
|
- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters
|
|
{
|
|
[self setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender];
|
|
}
|
|
|
|
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
|
|
}
|
|
|
|
- (NSArray *)visibleNodes
|
|
{
|
|
NSArray *indexPaths = [self indexPathsForVisibleItems];
|
|
NSMutableArray *visibleNodes = [[NSMutableArray alloc] init];
|
|
|
|
[indexPaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
|
ASCellNode *visibleNode = [self nodeForItemAtIndexPath:obj];
|
|
[visibleNodes addObject:visibleNode];
|
|
}];
|
|
|
|
return visibleNodes;
|
|
}
|
|
|
|
#pragma mark Assertions.
|
|
|
|
- (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion
|
|
{
|
|
[_dataController beginUpdates];
|
|
updates();
|
|
[_dataController endUpdatesWithCompletion:completion];
|
|
}
|
|
|
|
- (void)insertSections:(NSIndexSet *)sections
|
|
{
|
|
[_dataController insertSections:sections withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)deleteSections:(NSIndexSet *)sections
|
|
{
|
|
[_dataController deleteSections:sections withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)reloadSections:(NSIndexSet *)sections
|
|
{
|
|
[_dataController reloadSections:sections withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
|
|
{
|
|
[_dataController moveSection:section toSection:newSection withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
|
|
{
|
|
[_dataController insertRowsAtIndexPaths:indexPaths withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths
|
|
{
|
|
[_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
|
|
{
|
|
[_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
|
|
{
|
|
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOption:kASCollectionViewAnimationNone];
|
|
}
|
|
|
|
- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
return [_dataController nodeAtIndexPath:indexPath];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Intercepted selectors.
|
|
|
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
static NSString *reuseIdentifier = @"_ASCollectionViewCell";
|
|
|
|
UICollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
|
|
|
|
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
|
|
|
|
[_rangeController configureContentView:cell.contentView forCellNode:node];
|
|
|
|
return cell;
|
|
}
|
|
|
|
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
return [[_dataController nodeAtIndexPath:indexPath] calculatedSize];
|
|
}
|
|
|
|
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
|
{
|
|
return [_dataController numberOfSections];
|
|
}
|
|
|
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
|
{
|
|
return [_dataController numberOfRowsInSection:section];
|
|
}
|
|
|
|
- (ASScrollDirection)scrollDirection
|
|
{
|
|
CGPoint scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview];
|
|
ASScrollDirection direction = ASScrollDirectionNone;
|
|
if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) {
|
|
if (scrollVelocity.x > 0) {
|
|
direction = ASScrollDirectionRight;
|
|
} else if (scrollVelocity.x < 0) {
|
|
direction = ASScrollDirectionLeft;
|
|
}
|
|
} else {
|
|
if (scrollVelocity.y > 0) {
|
|
direction = ASScrollDirectionDown;
|
|
} else {
|
|
direction = ASScrollDirectionUp;
|
|
}
|
|
}
|
|
|
|
return direction;
|
|
}
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
|
|
|
|
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) {
|
|
[_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath];
|
|
}
|
|
}
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
|
|
|
|
if ([_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNodeForItemAtIndexPath:)]) {
|
|
[_asyncDelegate collectionView:self didEndDisplayingNodeForItemAtIndexPath:indexPath];
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Batch Fetching
|
|
|
|
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
|
{
|
|
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
|
|
|
|
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
|
|
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
|
|
}
|
|
}
|
|
|
|
- (BOOL)shouldBatchFetch
|
|
{
|
|
// if the delegate does not respond to this method, there is no point in starting to fetch
|
|
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)];
|
|
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]) {
|
|
return [_asyncDelegate shouldBatchFetchForCollectionView:self];
|
|
} else {
|
|
return canFetch;
|
|
}
|
|
}
|
|
|
|
- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset
|
|
{
|
|
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
|
|
|
|
if (![self shouldBatchFetch]) {
|
|
return;
|
|
}
|
|
|
|
if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) {
|
|
[_batchContext beginBatchFetching];
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
[_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext];
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark - ASDataControllerSource
|
|
|
|
- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
|
|
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
|
|
return node;
|
|
}
|
|
|
|
- (CGSize)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
CGSize restrainedSize = self.bounds.size;
|
|
|
|
if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) {
|
|
restrainedSize.width = FLT_MAX;
|
|
} else {
|
|
restrainedSize.height = FLT_MAX;
|
|
}
|
|
|
|
return restrainedSize;
|
|
}
|
|
|
|
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
|
|
{
|
|
return [_asyncDataSource collectionView:self numberOfItemsInSection:section];
|
|
}
|
|
|
|
- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController {
|
|
if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
|
|
return [_asyncDataSource numberOfSectionsInCollectionView:self];
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
- (void)dataControllerLockDataSource
|
|
{
|
|
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
|
|
|
|
self.asyncDataSourceLocked = YES;
|
|
if ([_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]) {
|
|
[_asyncDataSource collectionViewLockDataSource:self];
|
|
}
|
|
}
|
|
|
|
- (void)dataControllerUnlockDataSource
|
|
{
|
|
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
|
|
|
|
self.asyncDataSourceLocked = NO;
|
|
if ([_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]) {
|
|
[_asyncDataSource collectionViewUnlockDataSource:self];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark ASRangeControllerDelegate.
|
|
|
|
- (void)rangeControllerBeginUpdates:(ASRangeController *)rangeController {
|
|
ASDisplayNodeAssertMainThread();
|
|
_performingBatchUpdates = YES;
|
|
}
|
|
|
|
- (void)rangeControllerEndUpdates:(ASRangeController *)rangeController completion:(void (^)(BOOL))completion {
|
|
ASDisplayNodeAssertMainThread();
|
|
|
|
[super performBatchUpdates:^{
|
|
[_batchUpdateBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) {
|
|
block();
|
|
}];
|
|
} completion:^(BOOL finished) {
|
|
if (completion) {
|
|
completion(finished);
|
|
}
|
|
}];
|
|
|
|
[_batchUpdateBlocks removeAllObjects];
|
|
_performingBatchUpdates = NO;
|
|
}
|
|
|
|
- (NSArray *)rangeControllerVisibleNodeIndexPaths:(ASRangeController *)rangeController
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
return [self indexPathsForVisibleItems];
|
|
}
|
|
|
|
- (CGSize)rangeControllerViewportSize:(ASRangeController *)rangeController
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
return self.bounds.size;
|
|
}
|
|
|
|
- (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths
|
|
{
|
|
return [_dataController nodesAtIndexPaths:indexPaths];
|
|
}
|
|
|
|
- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
if (_performingBatchUpdates) {
|
|
[_batchUpdateBlocks addObject:^{
|
|
[super insertItemsAtIndexPaths:indexPaths];
|
|
}];
|
|
} else {
|
|
[UIView performWithoutAnimation:^{
|
|
[super insertItemsAtIndexPaths:indexPaths];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
|
|
if (_performingBatchUpdates) {
|
|
[_batchUpdateBlocks addObject:^{
|
|
[super deleteItemsAtIndexPaths:indexPaths];
|
|
}];
|
|
} else {
|
|
[UIView performWithoutAnimation:^{
|
|
[super deleteItemsAtIndexPaths:indexPaths];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
|
|
if (_performingBatchUpdates) {
|
|
[_batchUpdateBlocks addObject:^{
|
|
[super insertSections:indexSet];
|
|
}];
|
|
} else {
|
|
[UIView performWithoutAnimation:^{
|
|
[super insertSections:indexSet];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOption:(ASDataControllerAnimationOptions)animationOption
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
|
|
if (_performingBatchUpdates) {
|
|
[_batchUpdateBlocks addObject:^{
|
|
[super deleteSections:indexSet];
|
|
}];
|
|
} else {
|
|
[UIView performWithoutAnimation:^{
|
|
[super deleteSections:indexSet];
|
|
}];
|
|
}
|
|
}
|
|
|
|
@end
|