mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Add support automatically adjusting the content offset to UITableView as well as support for performing endUpdates with no animations. Additionally, there are critical bug fixes for ASDataController (begin/end updates delegates not called in correct order) and ASRangeController (failure to fully refresh internal state when inserts or delete are made.)
This commit is contained in:
@@ -78,6 +78,30 @@
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
|
||||
|
||||
/**
|
||||
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. You can call it from background
|
||||
* thread (it is recommendated) and the UI collection view will be updated asynchronously. The asyncDataSource must be updated
|
||||
* to reflect the changes before this method is called.
|
||||
*
|
||||
* @param animated NO to disable animations for this batch
|
||||
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
|
||||
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
|
||||
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
|
||||
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
|
||||
*/
|
||||
- (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion;
|
||||
|
||||
/**
|
||||
* Perform a batch of updates asynchronously. You can call it from background thread (it is recommendated) and the UI collection
|
||||
* view will be updated asynchronously. The asyncDataSource must be updated to reflect the changes before this method is called.
|
||||
*
|
||||
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
|
||||
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
|
||||
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
|
||||
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
|
||||
*/
|
||||
- (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion;
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
|
||||
@@ -281,11 +281,16 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
|
||||
#pragma mark Assertions.
|
||||
|
||||
- (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion
|
||||
- (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion
|
||||
{
|
||||
[_dataController beginUpdates];
|
||||
updates();
|
||||
[_dataController endUpdatesWithCompletion:completion];
|
||||
[_dataController endUpdatesAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion
|
||||
{
|
||||
[self performBatchAnimated:YES updates:updates completion:completion];
|
||||
}
|
||||
|
||||
- (void)insertSections:(NSIndexSet *)sections
|
||||
@@ -540,7 +545,7 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
_performingBatchUpdates = YES;
|
||||
}
|
||||
|
||||
- (void)rangeControllerEndUpdates:(ASRangeController *)rangeController completion:(void (^)(BOOL))completion {
|
||||
- (void)rangeController:(ASRangeController *)rangeController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (!self.asyncDataSource) {
|
||||
@@ -550,11 +555,21 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
}
|
||||
|
||||
BOOL animationsEnabled = NO;
|
||||
|
||||
if (!animated) {
|
||||
animationsEnabled = [UIView areAnimationsEnabled];
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
}
|
||||
|
||||
[super performBatchUpdates:^{
|
||||
[_batchUpdateBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) {
|
||||
block();
|
||||
}];
|
||||
} completion:^(BOOL finished) {
|
||||
if (!animated) {
|
||||
[UIView setAnimationsEnabled:animationsEnabled];
|
||||
}
|
||||
if (completion) {
|
||||
completion(finished);
|
||||
}
|
||||
@@ -581,7 +596,7 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
return [_dataController nodesAtIndexPaths:indexPaths];
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
@@ -600,7 +615,7 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
|
||||
@@ -93,15 +93,40 @@
|
||||
*/
|
||||
- (void)reloadData;
|
||||
|
||||
|
||||
/**
|
||||
* We don't support the these methods for animation yet.
|
||||
*
|
||||
* TODO: support animations.
|
||||
* begins a batch of insert, delete reload and move operations. Batches are asynchronous an thread safe.
|
||||
*/
|
||||
- (void)beginUpdates;
|
||||
|
||||
/**
|
||||
* Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view.
|
||||
* You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations
|
||||
* to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating
|
||||
* the operations simultaneously. This method is asynchronous and thread safe. It's important to remeber that the ASTableView will
|
||||
* be processing the updates asynchronously after this call is completed.
|
||||
*
|
||||
* @param animated NO to disable all animations.
|
||||
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
|
||||
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
|
||||
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
|
||||
*/
|
||||
- (void)endUpdates;
|
||||
|
||||
/**
|
||||
* Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view.
|
||||
* You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations
|
||||
* to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating
|
||||
* the operations simultaneously. This method is asynchronous and thread safe. It's important to remeber that the ASTableView will
|
||||
* be processing the updates asynchronously after this call and are not guaranteed to be reflected in the ASTableView until
|
||||
* the completion block is executed.
|
||||
*
|
||||
* @param animated NO to disable all animations.
|
||||
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
|
||||
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
|
||||
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
|
||||
*/
|
||||
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion;
|
||||
|
||||
/**
|
||||
* Inserts one or more sections, with an option to animate the insertion.
|
||||
*
|
||||
@@ -222,6 +247,14 @@
|
||||
*/
|
||||
- (NSArray *)visibleNodes;
|
||||
|
||||
/**
|
||||
* YES to automatically adjust the contentOffset when cells are inserted or deleted "before"
|
||||
* visible cells, maintaining the users' visible scroll position.
|
||||
*
|
||||
* default is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL automaticallyAdjustsContentOffset;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASBatchFetching.h"
|
||||
|
||||
//#define LOG(...) NSLog(__VA_ARGS__)
|
||||
#define LOG(...)
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Proxying.
|
||||
@@ -126,6 +128,9 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
ASBatchContext *_batchContext;
|
||||
|
||||
NSIndexPath *_pendingVisibleIndexPath;
|
||||
|
||||
NSIndexPath *_contentOffsetAdjustmentTopVisibleRow;
|
||||
CGFloat _contentOffsetAdjustment;
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
@@ -176,6 +181,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
|
||||
_leadingScreensForBatching = 1.0;
|
||||
_batchContext = [[ASBatchContext alloc] init];
|
||||
|
||||
_automaticallyAdjustsContentOffset = NO;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||
@@ -326,9 +333,13 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
|
||||
- (void)endUpdates
|
||||
{
|
||||
[_dataController endUpdates];
|
||||
[self endUpdatesAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion;
|
||||
{
|
||||
[_dataController endUpdatesAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Editing
|
||||
@@ -373,6 +384,57 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark adjust content offset
|
||||
|
||||
- (void)beginAdjustingContentOffset
|
||||
{
|
||||
ASDisplayNodeAssert(_automaticallyAdjustsContentOffset, @"this method should only be called when _automaticallyAdjustsContentOffset == YES");
|
||||
_contentOffsetAdjustment = 0;
|
||||
_contentOffsetAdjustmentTopVisibleRow = self.indexPathsForVisibleRows.firstObject;
|
||||
}
|
||||
|
||||
- (void)endAdjustingContentOffset
|
||||
{
|
||||
ASDisplayNodeAssert(_automaticallyAdjustsContentOffset, @"this method should only be called when _automaticallyAdjustsContentOffset == YES");
|
||||
if (_contentOffsetAdjustment != 0) {
|
||||
self.contentOffset = CGPointMake(0, self.contentOffset.y+_contentOffsetAdjustment);
|
||||
}
|
||||
|
||||
_contentOffsetAdjustment = 0;
|
||||
_contentOffsetAdjustmentTopVisibleRow = nil;
|
||||
}
|
||||
|
||||
- (void)adjustContentOffsetWithNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths inserting:(BOOL)inserting {
|
||||
// Maintain the users visible window when inserting or deleteing cells by adjusting the content offset for nodes
|
||||
// before the visible area. If in a begin/end updates block this will update _contentOffsetAdjustment, otherwise it will
|
||||
// update self.contentOffset directly.
|
||||
|
||||
ASDisplayNodeAssert(_automaticallyAdjustsContentOffset, @"this method should only be called when _automaticallyAdjustsContentOffset == YES");
|
||||
|
||||
CGFloat dir = (inserting) ? +1 : -1;
|
||||
CGFloat adjustment = 0;
|
||||
NSIndexPath *top = _contentOffsetAdjustmentTopVisibleRow ?: self.indexPathsForVisibleRows.firstObject;
|
||||
|
||||
for (int index=0; index<indexPaths.count; index++) {
|
||||
NSIndexPath *indexPath = indexPaths[index];
|
||||
if ([indexPath compare:top] <= 0) { // if this row is before or equal to the topmost visible row, make adjustments...
|
||||
ASCellNode *cellNode = nodes[index];
|
||||
adjustment += cellNode.calculatedSize.height * dir;
|
||||
if (indexPath.section == top.section) {
|
||||
top = [NSIndexPath indexPathForRow:top.row+dir inSection:top.section];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_contentOffsetAdjustmentTopVisibleRow) { // true of we are in a begin/end update block (see beginAdjustingContentOffset)
|
||||
_contentOffsetAdjustmentTopVisibleRow = top;
|
||||
_contentOffsetAdjustment += adjustment;
|
||||
} else if (adjustment != 0) {
|
||||
self.contentOffset = CGPointMake(0, self.contentOffset.y+adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Intercepted selectors
|
||||
|
||||
@@ -499,17 +561,23 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
- (void)rangeControllerBeginUpdates:(ASRangeController *)rangeController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"--- UITableView beginUpdates");
|
||||
|
||||
if (!self.asyncDataSource) {
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
}
|
||||
|
||||
[super beginUpdates];
|
||||
|
||||
if (_automaticallyAdjustsContentOffset) {
|
||||
[self beginAdjustingContentOffset];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rangeControllerEndUpdates:(ASRangeController *)rangeController completion:(void (^)(BOOL))completion
|
||||
- (void)rangeController:(ASRangeController *)rangeController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"--- UITableView endUpdates");
|
||||
|
||||
if (!self.asyncDataSource) {
|
||||
if (completion) {
|
||||
@@ -518,7 +586,13 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
}
|
||||
|
||||
[super endUpdates];
|
||||
if (_automaticallyAdjustsContentOffset) {
|
||||
[self endAdjustingContentOffset];
|
||||
}
|
||||
|
||||
ASPerformBlockWithoutAnimation(!animated, ^{
|
||||
[super endUpdates];
|
||||
});
|
||||
|
||||
if (completion) {
|
||||
completion(YES);
|
||||
@@ -586,9 +660,10 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
return self.bounds.size;
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"UITableView insertRows:%ld rows", indexPaths.count);
|
||||
|
||||
if (!self.asyncDataSource) {
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
@@ -598,11 +673,16 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
ASPerformBlockWithoutAnimation(preventAnimation, ^{
|
||||
[super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
|
||||
});
|
||||
|
||||
if (_automaticallyAdjustsContentOffset) {
|
||||
[self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"UITableView deleteRows:%ld rows", indexPaths.count);
|
||||
|
||||
if (!self.asyncDataSource) {
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
@@ -612,11 +692,17 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
ASPerformBlockWithoutAnimation(preventAnimation, ^{
|
||||
[super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
|
||||
});
|
||||
|
||||
if (_automaticallyAdjustsContentOffset) {
|
||||
[self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"UITableView insertSections:%@", indexSet);
|
||||
|
||||
|
||||
if (!self.asyncDataSource) {
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
@@ -631,6 +717,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"UITableView deleteSections:%@", indexSet);
|
||||
|
||||
if (!self.asyncDataSource) {
|
||||
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
|
||||
|
||||
@@ -65,7 +65,7 @@ typedef NSUInteger ASDataControllerAnimationOptions;
|
||||
Called for batch update.
|
||||
*/
|
||||
- (void)dataControllerBeginUpdates:(ASDataController *)dataController;
|
||||
- (void)dataControllerEndUpdates:(ASDataController *)dataController completion:(void (^)(BOOL))completion;
|
||||
- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion;
|
||||
|
||||
/**
|
||||
Called for insertion of elements.
|
||||
@@ -75,7 +75,7 @@ typedef NSUInteger ASDataControllerAnimationOptions;
|
||||
/**
|
||||
Called for deletion of elements.
|
||||
*/
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
|
||||
/**
|
||||
Called for insertion of sections.
|
||||
@@ -138,7 +138,7 @@ typedef NSUInteger ASDataControllerAnimationOptions;
|
||||
|
||||
- (void)endUpdates;
|
||||
|
||||
- (void)endUpdatesWithCompletion:(void (^)(BOOL))completion;
|
||||
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion;
|
||||
|
||||
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#import "ASMultidimensionalArrayUtils.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
|
||||
//#define LOG(...) NSLog(__VA_ARGS__)
|
||||
#define LOG(...)
|
||||
|
||||
const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
|
||||
|
||||
static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
@@ -73,7 +76,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
// Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later.
|
||||
_delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)];
|
||||
_delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodesAtIndexPaths:withAnimationOptions:)];
|
||||
_delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)];
|
||||
_delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)];
|
||||
_delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)];
|
||||
}
|
||||
@@ -164,12 +167,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
if (indexPaths.count == 0)
|
||||
return;
|
||||
LOG(@"_deleteNodesAtIndexPaths:%@, full index paths in _editingNodes = %@", indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes));
|
||||
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths);
|
||||
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, indexPaths);
|
||||
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, indexPaths);
|
||||
if (_delegateDidDeleteNodes)
|
||||
[_delegate dataController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -242,6 +247,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - reloadData");
|
||||
|
||||
// Remove everything that existed before the reload, now that we're ready to insert replacements
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes);
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
@@ -318,6 +325,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)beginUpdates
|
||||
{
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
// Begin queuing up edit calls that happen on the main thread.
|
||||
// This will prevent further operations from being scheduled on _editingTransactionQueue.
|
||||
// It's fine if there is an in-flight operation on _editingTransactionQueue,
|
||||
@@ -327,23 +335,38 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)endUpdates
|
||||
{
|
||||
[self endUpdatesWithCompletion:NULL];
|
||||
[self endUpdatesAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)endUpdatesWithCompletion:(void (^)(BOOL))completion
|
||||
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
|
||||
{
|
||||
_batchUpdateCounter--;
|
||||
|
||||
if (_batchUpdateCounter == 0) {
|
||||
[_delegate dataControllerBeginUpdates:self];
|
||||
LOG(@"endUpdatesWithCompletion - beginning");
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
LOG(@"endUpdatesWithCompletion - begin updates call to delegate");
|
||||
[_delegate dataControllerBeginUpdates:self];
|
||||
});
|
||||
}];
|
||||
|
||||
// Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
|
||||
// Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction.
|
||||
LOG(@"endUpdatesWithCompletion - %zd blocks to run", _pendingEditCommandBlocks.count);
|
||||
[_pendingEditCommandBlocks enumerateObjectsUsingBlock:^(dispatch_block_t block, NSUInteger idx, BOOL *stop) {
|
||||
LOG(@"endUpdatesWithCompletion - running block #%zd", idx);
|
||||
block();
|
||||
}];
|
||||
[_pendingEditCommandBlocks removeAllObjects];
|
||||
|
||||
[_delegate dataControllerEndUpdates:self completion:completion];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
LOG(@"endUpdatesWithCompletion - calling delegate end");
|
||||
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
|
||||
});
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +387,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - insertSections: %@", indexSet);
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
@@ -372,6 +396,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
[self _populateFromDataSourceWithSectionIndexSet:indexSet mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - insertSections: %@", indexSet);
|
||||
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:indexSet.count];
|
||||
for (NSUInteger i = 0; i < indexSet.count; i++) {
|
||||
[sectionArray addObject:[NSMutableArray array]];
|
||||
@@ -388,10 +413,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - deleteSections: %@", indexSet);
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
// remove elements
|
||||
LOG(@"Edit Transaction - deleteSections: %@", indexSet);
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, indexSet);
|
||||
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
@@ -404,6 +431,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - reloadSections: %@", sections);
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
@@ -417,6 +446,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, sections);
|
||||
|
||||
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes));
|
||||
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
|
||||
// reinsert the elements
|
||||
@@ -430,10 +462,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - moveSection");
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
// remove elements
|
||||
|
||||
LOG(@"Edit Transaction - moveSection");
|
||||
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes, [NSIndexSet indexSetWithIndex:section]);
|
||||
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, indexPaths);
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
@@ -457,6 +494,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - insertRows: %@", indexPaths);
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
@@ -468,6 +507,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
}
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
|
||||
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
@@ -478,12 +518,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - deleteRows: %@", indexPaths);
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// sort indexPath in order to avoid messing up the index when deleting
|
||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
|
||||
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
@@ -493,6 +536,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - reloadRows: %@", indexPaths);
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
|
||||
@@ -504,6 +549,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
}];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -515,9 +561,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
[self performEditCommandWithBlock:^{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath);
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
|
||||
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes, [NSArray arrayWithObject:indexPath]);
|
||||
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
|
||||
@@ -103,16 +103,18 @@ static const CGFloat kASFlowLayoutControllerRefreshingThreshold = 0.3;
|
||||
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
|
||||
|
||||
NSArray *completedNodes = [_dataSource completedNodes];
|
||||
|
||||
ASIndexPath currPath = startPath;
|
||||
|
||||
while (!ASIndexPathEqualToIndexPath(startPath, endPath)) {
|
||||
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:startPath]];
|
||||
startPath.row++;
|
||||
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 (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");
|
||||
while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < completedNodes.count - 1) {
|
||||
currPath.row = 0;
|
||||
currPath.section++;
|
||||
ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
*
|
||||
* @param completion Completion block.
|
||||
*/
|
||||
- (void)rangeControllerEndUpdates:(ASRangeController * )rangeController completion:(void (^)(BOOL))completion ;
|
||||
- (void)rangeController:(ASRangeController * )rangeController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion;
|
||||
|
||||
/**
|
||||
* Fetch nodes at specific index paths.
|
||||
@@ -108,7 +108,7 @@
|
||||
*
|
||||
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.
|
||||
*/
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
|
||||
/**
|
||||
* Called for nodes deletion.
|
||||
@@ -119,7 +119,7 @@
|
||||
*
|
||||
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.
|
||||
*/
|
||||
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
|
||||
/**
|
||||
* Called for section insertion.
|
||||
|
||||
@@ -89,6 +89,12 @@
|
||||
}
|
||||
|
||||
NSArray *visibleNodePaths = [_delegate rangeControllerVisibleNodeIndexPaths:self];
|
||||
|
||||
if ( visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
|
||||
_queuedRangeUpdate = NO;
|
||||
return ; // don't do anything for this update, but leave _rangeIsValid to make sure we update it later
|
||||
}
|
||||
|
||||
NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths];
|
||||
CGSize viewportSize = [_delegate rangeControllerViewportSize:self];
|
||||
|
||||
@@ -104,7 +110,7 @@
|
||||
// this delegate decide what happens when a node is added or removed from a range
|
||||
id<ASRangeHandler> rangeDelegate = _rangeTypeHandlers[rangeKey];
|
||||
|
||||
if ([_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths viewportSize:viewportSize rangeType:rangeType]) {
|
||||
if (!_rangeIsValid || [_layoutController shouldUpdateForVisibleIndexPaths:visibleNodePaths viewportSize:viewportSize rangeType:rangeType]) {
|
||||
NSSet *indexPaths = [_layoutController indexPathsForScrolling:_scrollDirection viewportSize:viewportSize rangeType:rangeType];
|
||||
|
||||
// Notify to remove indexpaths that are leftover that are not visible or included in the _layoutController calculated paths
|
||||
@@ -176,9 +182,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataControllerEndUpdates:(ASDataController *)dataController completion:(void (^)(BOOL))completion {
|
||||
- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion {
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
[_delegate rangeControllerEndUpdates:self completion:completion];
|
||||
[_delegate rangeController:self endUpdatesAnimated:animated completion:completion];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,14 +198,14 @@
|
||||
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
[_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
|
||||
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions {
|
||||
ASDisplayNodePerformBlockOnMainThread(^{
|
||||
_rangeIsValid = NO;
|
||||
[_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
[_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user