mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Merge pull request #753 from nguyenhuy/DataControllerSortedTransaction
Sort edit commands during batch updating of table and collection views
This commit is contained in:
@@ -7,9 +7,10 @@
|
||||
*/
|
||||
|
||||
#import "ASTableView.h"
|
||||
#import "ASTableViewInternal.h"
|
||||
|
||||
#import "ASAssert.h"
|
||||
#import "ASDataController.h"
|
||||
#import "ASChangeSetDataController.h"
|
||||
#import "ASCollectionViewLayoutController.h"
|
||||
#import "ASLayoutController.h"
|
||||
#import "ASRangeController.h"
|
||||
@@ -155,7 +156,6 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
_ASTableViewProxy *_proxyDataSource;
|
||||
_ASTableViewProxy *_proxyDelegate;
|
||||
|
||||
ASDataController *_dataController;
|
||||
ASFlowLayoutController *_layoutController;
|
||||
|
||||
ASRangeController *_rangeController;
|
||||
@@ -174,6 +174,7 @@ static BOOL _isInterceptedSelector(SEL sel)
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
@property (nonatomic, retain, readwrite) ASDataController *dataController;
|
||||
|
||||
@end
|
||||
|
||||
@@ -199,24 +200,29 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
}
|
||||
}
|
||||
|
||||
+ (Class)dataControllerClass
|
||||
{
|
||||
return [ASChangeSetDataController class];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Lifecycle
|
||||
|
||||
- (void)configureWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
- (void)configureWithDataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetching
|
||||
{
|
||||
_layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical];
|
||||
|
||||
_rangeController = [[ASRangeController alloc] init];
|
||||
_rangeController.layoutController = _layoutController;
|
||||
_rangeController.delegate = self;
|
||||
|
||||
_dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
|
||||
|
||||
_dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:asyncDataFetching];
|
||||
_dataController.dataSource = self;
|
||||
_dataController.delegate = _rangeController;
|
||||
|
||||
_layoutController.dataSource = _dataController;
|
||||
|
||||
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
|
||||
_asyncDataFetchingEnabled = asyncDataFetching;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
_leadingScreensForBatching = 1.0;
|
||||
@@ -236,6 +242,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
return [self initWithFrame:frame style:style dataControllerClass:[self.class dataControllerClass] asyncDataFetching:asyncDataFetchingEnabled];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
if (!(self = [super initWithFrame:frame style:style]))
|
||||
return nil;
|
||||
@@ -244,8 +255,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
// https://github.com/facebook/AsyncDisplayKit/issues/385
|
||||
asyncDataFetchingEnabled = NO;
|
||||
|
||||
[self configureWithAsyncDataFetching:asyncDataFetchingEnabled];
|
||||
|
||||
[self configureWithDataControllerClass:dataControllerClass asyncDataFetching:asyncDataFetchingEnabled];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -254,7 +265,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
if (!(self = [super initWithCoder:aDecoder]))
|
||||
return nil;
|
||||
|
||||
[self configureWithAsyncDataFetching:NO];
|
||||
[self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO];
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -417,7 +428,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
}
|
||||
}
|
||||
|
||||
// To ensure _maxWidthForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last
|
||||
// To ensure _nodesConstrainedWidth is up-to-date for every usage, this call to super must be done last
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
@@ -895,7 +906,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) {
|
||||
// Normally the content view width equals to the constrained size width (which equals to the table view width).
|
||||
// If there is a mismatch between these values, for example after the table view entered or left editing mode,
|
||||
// content view width is preferred and used to re-measure the cell node.
|
||||
if (!_ignoreNodesConstrainedWidthChange && contentViewWidth != constrainedSize.max.width) {
|
||||
if (contentViewWidth != constrainedSize.max.width) {
|
||||
constrainedSize.min.width = contentViewWidth;
|
||||
constrainedSize.max.width = contentViewWidth;
|
||||
|
||||
|
||||
31
AsyncDisplayKit/ASTableViewInternal.h
Normal file
31
AsyncDisplayKit/ASTableViewInternal.h
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// ASTableViewInternal.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Huy Nguyen on 26/10/15.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASTableView.h"
|
||||
|
||||
@class ASDataController;
|
||||
|
||||
@interface ASTableView (Internal)
|
||||
|
||||
@property (nonatomic, retain, readonly) ASDataController *dataController;
|
||||
|
||||
/**
|
||||
* Initializer.
|
||||
*
|
||||
* @param frame A rectangle specifying the initial location and size of the table view in its superview’s coordinates.
|
||||
* The frame of the table view changes as table cells are added and deleted.
|
||||
*
|
||||
* @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants.
|
||||
*
|
||||
* @param dataControllerClass A controller class injected to and used to create a data controller for the table view.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
@end
|
||||
23
AsyncDisplayKit/Details/ASChangeSetDataController.h
Normal file
23
AsyncDisplayKit/Details/ASChangeSetDataController.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// ASChangeSetDataController.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Huy Nguyen on 19/10/15.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
|
||||
/**
|
||||
* @abstract Subclass of ASDataController that simulates ordering of operations in batch updates defined in UITableView and UICollectionView.
|
||||
*
|
||||
* @discussion The ordering is achieved by using _ASHierarchyChangeSet to enqueue and sort operations.
|
||||
* More information about the ordering and the index paths used for operations can be found here:
|
||||
* https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW17
|
||||
*
|
||||
* @see ASDataController
|
||||
* @see _ASHierarchyChangeSet
|
||||
*/
|
||||
@interface ASChangeSetDataController : ASDataController
|
||||
|
||||
@end
|
||||
179
AsyncDisplayKit/Details/ASChangeSetDataController.m
Normal file
179
AsyncDisplayKit/Details/ASChangeSetDataController.m
Normal file
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// ASChangeSetDataController.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Huy Nguyen on 19/10/15.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ASChangeSetDataController.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "_ASHierarchyChangeSet.h"
|
||||
#import "ASAssert.h"
|
||||
|
||||
@interface ASChangeSetDataController ()
|
||||
|
||||
@property (nonatomic, assign) NSUInteger batchUpdateCounter;
|
||||
@property (nonatomic, strong) _ASHierarchyChangeSet *changeSet;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASChangeSetDataController
|
||||
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
if (!(self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_batchUpdateCounter = 0;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Batching (External API)
|
||||
|
||||
- (void)beginUpdates
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (_batchUpdateCounter == 0) {
|
||||
_changeSet = [_ASHierarchyChangeSet new];
|
||||
}
|
||||
_batchUpdateCounter++;
|
||||
}
|
||||
|
||||
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
_batchUpdateCounter--;
|
||||
|
||||
if (_batchUpdateCounter == 0) {
|
||||
[_changeSet markCompleted];
|
||||
|
||||
[super beginUpdates];
|
||||
|
||||
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) {
|
||||
[super reloadSections:change.indexSet withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) {
|
||||
[super reloadRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
|
||||
[super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
|
||||
[super deleteSections:change.indexSet withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
|
||||
[super insertSections:change.indexSet withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) {
|
||||
[super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
[super endUpdatesAnimated:animated completion:completion];
|
||||
|
||||
_changeSet = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)batchUpdating
|
||||
{
|
||||
BOOL batchUpdating = (_batchUpdateCounter != 0);
|
||||
// _changeSet must be available during batch update
|
||||
ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil));
|
||||
return batchUpdating;
|
||||
}
|
||||
|
||||
#pragma mark - Section Editing (External API)
|
||||
|
||||
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet insertSections:sections animationOptions:animationOptions];
|
||||
} else {
|
||||
[super insertSections:sections withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet deleteSections:sections animationOptions:animationOptions];
|
||||
} else {
|
||||
[super deleteSections:sections withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet reloadSections:sections animationOptions:animationOptions];
|
||||
} else {
|
||||
[super reloadSections:sections withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions];
|
||||
[_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions];
|
||||
} else {
|
||||
[super moveSection:section toSection:newSection withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Row Editing (External API)
|
||||
|
||||
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet insertItems:indexPaths animationOptions:animationOptions];
|
||||
} else {
|
||||
[super insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet deleteItems:indexPaths animationOptions:animationOptions];
|
||||
} else {
|
||||
[super deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet reloadItems:indexPaths animationOptions:animationOptions];
|
||||
} else {
|
||||
[super reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if ([self batchUpdating]) {
|
||||
[_changeSet deleteItems:@[indexPath] animationOptions:animationOptions];
|
||||
[_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions];
|
||||
} else {
|
||||
[super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
#import <AsyncDisplayKit/ASChangeSetDataController.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
@class ASDisplayNode;
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionDataController : ASDataController
|
||||
@interface ASCollectionDataController : ASChangeSetDataController
|
||||
|
||||
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ typedef NSUInteger ASDataControllerAnimationOptions;
|
||||
*
|
||||
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data
|
||||
* 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.
|
||||
* For each data updating, the corresponding methods in delegate will be called.
|
||||
*/
|
||||
@protocol ASFlowLayoutControllerDataSource;
|
||||
@interface ASDataController : ASDealloc2MainObject <ASFlowLayoutControllerDataSource>
|
||||
|
||||
@@ -26,3 +26,7 @@ CGFloat ASCeilPixelValue(CGFloat f);
|
||||
CGFloat ASRoundPixelValue(CGFloat f);
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_END
|
||||
|
||||
@interface NSIndexPath (ASInverseComparison)
|
||||
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath;
|
||||
@end
|
||||
|
||||
@@ -70,3 +70,12 @@ CGFloat ASRoundPixelValue(CGFloat f)
|
||||
{
|
||||
return roundf(f * ASScreenScale()) / ASScreenScale();
|
||||
}
|
||||
|
||||
@implementation NSIndexPath (ASInverseComparison)
|
||||
|
||||
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath
|
||||
{
|
||||
return [otherIndexPath compare:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
72
AsyncDisplayKit/Private/_ASHierarchyChangeSet.h
Normal file
72
AsyncDisplayKit/Private/_ASHierarchyChangeSet.h
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// _ASHierarchyChangeSet.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 9/29/15.
|
||||
// Copyright © 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ASDataController.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
|
||||
_ASHierarchyChangeTypeReload,
|
||||
_ASHierarchyChangeTypeDelete,
|
||||
_ASHierarchyChangeTypeInsert
|
||||
};
|
||||
|
||||
@interface _ASHierarchySectionChange : NSObject
|
||||
|
||||
// FIXME: Generalize this to `changeMetadata` dict?
|
||||
@property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSIndexSet *indexSet;
|
||||
@property (nonatomic, readonly) _ASHierarchyChangeType changeType;
|
||||
@end
|
||||
|
||||
@interface _ASHierarchyItemChange : NSObject
|
||||
@property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions;
|
||||
|
||||
/// Index paths are sorted descending for changeType .Delete, ascending otherwise
|
||||
@property (nonatomic, strong, readonly) NSArray *indexPaths;
|
||||
@property (nonatomic, readonly) _ASHierarchyChangeType changeType;
|
||||
@end
|
||||
|
||||
@interface _ASHierarchyChangeSet : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSIndexSet *deletedSections;
|
||||
@property (nonatomic, strong, readonly) NSIndexSet *insertedSections;
|
||||
@property (nonatomic, strong, readonly) NSIndexSet *reloadedSections;
|
||||
@property (nonatomic, strong, readonly) NSArray *insertedItems;
|
||||
@property (nonatomic, strong, readonly) NSArray *deletedItems;
|
||||
@property (nonatomic, strong, readonly) NSArray *reloadedItems;
|
||||
|
||||
@property (nonatomic, readonly) BOOL completed;
|
||||
|
||||
/// Call this once the change set has been constructed to prevent future modifications to the changeset. Calling this more than once is a programmer error.
|
||||
- (void)markCompleted;
|
||||
|
||||
/**
|
||||
@abstract Return sorted changes of the given type, grouped by animation options.
|
||||
|
||||
Items deleted from deleted sections are not reported.
|
||||
Items inserted into inserted sections are not reported.
|
||||
Items reloaded in reloaded sections are not reported.
|
||||
|
||||
The safe order for processing change groups is:
|
||||
- Reloaded sections & reloaded items
|
||||
- Deleted items, descending order
|
||||
- Deleted sections, descending order
|
||||
- Inserted sections, ascending order
|
||||
- Inserted items, ascending order
|
||||
*/
|
||||
- (NSArray /*<_ASHierarchySectionChange *>*/ *)sectionChangesOfType:(_ASHierarchyChangeType)changeType;
|
||||
- (NSArray /*<_ASHierarchyItemChange *>*/ *)itemChangesOfType:(_ASHierarchyChangeType)changeType;
|
||||
|
||||
- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
|
||||
- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
|
||||
- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
|
||||
- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
|
||||
- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
|
||||
- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
|
||||
@end
|
||||
337
AsyncDisplayKit/Private/_ASHierarchyChangeSet.m
Normal file
337
AsyncDisplayKit/Private/_ASHierarchyChangeSet.m
Normal file
@@ -0,0 +1,337 @@
|
||||
//
|
||||
// _ASHierarchyChangeSet.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 9/29/15.
|
||||
// Copyright © 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "_ASHierarchyChangeSet.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
|
||||
@interface _ASHierarchySectionChange ()
|
||||
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
||||
|
||||
/**
|
||||
On return `changes` is sorted according to the change type with changes coalesced by animationOptions
|
||||
Assumes: `changes` is [_ASHierarchySectionChange] all with the same changeType
|
||||
*/
|
||||
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes;
|
||||
@end
|
||||
|
||||
@interface _ASHierarchyItemChange ()
|
||||
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted;
|
||||
|
||||
/**
|
||||
On return `changes` is sorted according to the change type with changes coalesced by animationOptions
|
||||
Assumes: `changes` is [_ASHierarchyItemChange] all with the same changeType
|
||||
*/
|
||||
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections;
|
||||
@end
|
||||
|
||||
@interface _ASHierarchyChangeSet ()
|
||||
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *insertItemChanges;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *deleteItemChanges;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *reloadItemChanges;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *insertSectionChanges;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *deleteSectionChanges;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *reloadSectionChanges;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASHierarchyChangeSet {
|
||||
NSMutableIndexSet *_deletedSections;
|
||||
NSMutableIndexSet *_insertedSections;
|
||||
NSMutableIndexSet *_reloadedSections;
|
||||
NSMutableArray *_insertedItems;
|
||||
NSMutableArray *_deletedItems;
|
||||
NSMutableArray *_reloadedItems;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_deletedSections = [NSMutableIndexSet new];
|
||||
_insertedSections = [NSMutableIndexSet new];
|
||||
_reloadedSections = [NSMutableIndexSet new];
|
||||
|
||||
_deletedItems = [NSMutableArray new];
|
||||
_insertedItems = [NSMutableArray new];
|
||||
_reloadedItems = [NSMutableArray new];
|
||||
|
||||
_insertItemChanges = [NSMutableArray new];
|
||||
_deleteItemChanges = [NSMutableArray new];
|
||||
_reloadItemChanges = [NSMutableArray new];
|
||||
_insertSectionChanges = [NSMutableArray new];
|
||||
_deleteSectionChanges = [NSMutableArray new];
|
||||
_reloadSectionChanges = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark External API
|
||||
|
||||
- (void)markCompleted
|
||||
{
|
||||
NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed.");
|
||||
_completed = YES;
|
||||
[self _sortAndCoalesceChangeArrays];
|
||||
}
|
||||
|
||||
- (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType
|
||||
{
|
||||
[self _ensureCompleted];
|
||||
switch (changeType) {
|
||||
case _ASHierarchyChangeTypeInsert:
|
||||
return _insertSectionChanges;
|
||||
case _ASHierarchyChangeTypeReload:
|
||||
return _reloadSectionChanges;
|
||||
case _ASHierarchyChangeTypeDelete:
|
||||
return _deleteSectionChanges;
|
||||
default:
|
||||
NSAssert(NO, @"Request for section changes with invalid type: %lu", changeType);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType
|
||||
{
|
||||
[self _ensureCompleted];
|
||||
switch (changeType) {
|
||||
case _ASHierarchyChangeTypeInsert:
|
||||
return _insertItemChanges;
|
||||
case _ASHierarchyChangeTypeReload:
|
||||
return _reloadItemChanges;
|
||||
case _ASHierarchyChangeTypeDelete:
|
||||
return _deleteItemChanges;
|
||||
default:
|
||||
NSAssert(NO, @"Request for item changes with invalid type: %lu", changeType);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
|
||||
{
|
||||
[self _ensureNotCompleted];
|
||||
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:indexPaths animationOptions:options presorted:NO];
|
||||
[_deleteItemChanges addObject:change];
|
||||
}
|
||||
|
||||
- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
|
||||
{
|
||||
[self _ensureNotCompleted];
|
||||
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:sections animationOptions:options];
|
||||
[_deleteSectionChanges addObject:change];
|
||||
}
|
||||
|
||||
- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
|
||||
{
|
||||
[self _ensureNotCompleted];
|
||||
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:indexPaths animationOptions:options presorted:NO];
|
||||
[_insertItemChanges addObject:change];
|
||||
}
|
||||
|
||||
- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
|
||||
{
|
||||
[self _ensureNotCompleted];
|
||||
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:sections animationOptions:options];
|
||||
[_insertSectionChanges addObject:change];
|
||||
}
|
||||
|
||||
- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
|
||||
{
|
||||
[self _ensureNotCompleted];
|
||||
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO];
|
||||
[_reloadItemChanges addObject:change];
|
||||
}
|
||||
|
||||
- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
|
||||
{
|
||||
[self _ensureNotCompleted];
|
||||
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options];
|
||||
[_reloadSectionChanges addObject:change];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (BOOL)_ensureNotCompleted
|
||||
{
|
||||
NSAssert(!_completed, @"Attempt to modify completed changeset %@", self);
|
||||
return !_completed;
|
||||
}
|
||||
|
||||
- (BOOL)_ensureCompleted
|
||||
{
|
||||
NSAssert(_completed, @"Attempt to process incomplete changeset %@", self);
|
||||
return _completed;
|
||||
}
|
||||
|
||||
- (void)_sortAndCoalesceChangeArrays
|
||||
{
|
||||
@autoreleasepool {
|
||||
[_ASHierarchySectionChange sortAndCoalesceChanges:_deleteSectionChanges];
|
||||
[_ASHierarchySectionChange sortAndCoalesceChanges:_insertSectionChanges];
|
||||
[_ASHierarchySectionChange sortAndCoalesceChanges:_reloadSectionChanges];
|
||||
[_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections];
|
||||
[_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:_reloadedSections];
|
||||
[_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:_insertedSections];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASHierarchySectionChange
|
||||
|
||||
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_changeType = changeType;
|
||||
_indexSet = indexSet;
|
||||
_animationOptions = animationOptions;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes
|
||||
{
|
||||
if (changes.count < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ASHierarchyChangeType type = [changes.firstObject changeType];
|
||||
|
||||
// Lookup table [Int: AnimationOptions]
|
||||
NSMutableDictionary *animationOptions = [NSMutableDictionary new];
|
||||
|
||||
// All changed indexes, sorted
|
||||
NSMutableIndexSet *allIndexes = [NSMutableIndexSet new];
|
||||
|
||||
for (_ASHierarchySectionChange *change in changes) {
|
||||
[change.indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, __unused BOOL *stop) {
|
||||
animationOptions[@(idx)] = @(change.animationOptions);
|
||||
}];
|
||||
[allIndexes addIndexes:change.indexSet];
|
||||
}
|
||||
|
||||
// Create new changes by grouping sorted changes by animation option
|
||||
NSMutableArray *result = [NSMutableArray new];
|
||||
|
||||
__block ASDataControllerAnimationOptions currentOptions = 0;
|
||||
__block NSMutableIndexSet *currentIndexes = nil;
|
||||
NSUInteger lastIndex = allIndexes.lastIndex;
|
||||
|
||||
NSEnumerationOptions options = type == _ASHierarchyChangeTypeDelete ? NSEnumerationReverse : kNilOptions;
|
||||
[allIndexes enumerateIndexesWithOptions:options usingBlock:^(NSUInteger idx, __unused BOOL * stop) {
|
||||
ASDataControllerAnimationOptions options = [animationOptions[@(idx)] integerValue];
|
||||
BOOL endingCurrentGroup = NO;
|
||||
|
||||
if (currentIndexes == nil) {
|
||||
// Starting a new group
|
||||
currentIndexes = [NSMutableIndexSet indexSetWithIndex:idx];
|
||||
currentOptions = options;
|
||||
} else if (options == currentOptions) {
|
||||
// Continuing the current group
|
||||
[currentIndexes addIndex:idx];
|
||||
} else {
|
||||
endingCurrentGroup = YES;
|
||||
}
|
||||
|
||||
BOOL endingLastGroup = (currentIndexes != nil && lastIndex == idx);
|
||||
|
||||
if (endingCurrentGroup || endingLastGroup) {
|
||||
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:currentIndexes animationOptions:currentOptions];
|
||||
[result addObject:change];
|
||||
currentOptions = 0;
|
||||
currentIndexes = nil;
|
||||
}
|
||||
}];
|
||||
|
||||
[changes setArray:result];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASHierarchyItemChange
|
||||
|
||||
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_changeType = changeType;
|
||||
if (presorted) {
|
||||
_indexPaths = indexPaths;
|
||||
} else {
|
||||
SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
|
||||
_indexPaths = [indexPaths sortedArrayUsingSelector:sorting];
|
||||
}
|
||||
_animationOptions = animationOptions;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections
|
||||
{
|
||||
if (changes.count < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ASHierarchyChangeType type = [changes.firstObject changeType];
|
||||
|
||||
// Lookup table [NSIndexPath: AnimationOptions]
|
||||
NSMutableDictionary *animationOptions = [NSMutableDictionary new];
|
||||
|
||||
// All changed index paths, sorted
|
||||
NSMutableArray *allIndexPaths = [NSMutableArray new];
|
||||
|
||||
NSPredicate *indexPathInValidSection = [NSPredicate predicateWithBlock:^BOOL(NSIndexPath *indexPath, __unused NSDictionary *_) {
|
||||
return ![sections containsIndex:indexPath.section];
|
||||
}];
|
||||
for (_ASHierarchyItemChange *change in changes) {
|
||||
for (NSIndexPath *indexPath in change.indexPaths) {
|
||||
if ([indexPathInValidSection evaluateWithObject:indexPath]) {
|
||||
animationOptions[indexPath] = @(change.animationOptions);
|
||||
[allIndexPaths addObject:indexPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
|
||||
[allIndexPaths sortUsingSelector:sorting];
|
||||
|
||||
// Create new changes by grouping sorted changes by animation option
|
||||
NSMutableArray *result = [NSMutableArray new];
|
||||
|
||||
ASDataControllerAnimationOptions currentOptions = 0;
|
||||
NSMutableArray *currentIndexPaths = nil;
|
||||
NSIndexPath *lastIndexPath = allIndexPaths.lastObject;
|
||||
|
||||
for (NSIndexPath *indexPath in allIndexPaths) {
|
||||
ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue];
|
||||
BOOL endingCurrentGroup = NO;
|
||||
|
||||
if (currentIndexPaths == nil) {
|
||||
// Starting a new group
|
||||
currentIndexPaths = [NSMutableArray arrayWithObject:indexPath];
|
||||
currentOptions = options;
|
||||
} else if (options == currentOptions) {
|
||||
// Continuing the current group
|
||||
[currentIndexPaths addObject:indexPath];
|
||||
} else {
|
||||
endingCurrentGroup = YES;
|
||||
}
|
||||
|
||||
BOOL endingLastGroup = (currentIndexPaths != nil && (NSOrderedSame == [lastIndexPath compare:indexPath]));
|
||||
|
||||
if (endingCurrentGroup || endingLastGroup) {
|
||||
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:currentIndexPaths animationOptions:currentOptions presorted:YES];
|
||||
[result addObject:change];
|
||||
currentOptions = 0;
|
||||
currentIndexPaths = nil;
|
||||
}
|
||||
}
|
||||
|
||||
[changes setArray:result];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user