diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 779260815c..c0c8ddb37c 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -321,9 +321,6 @@ A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */; }; A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = A373200E1C571B050011FC94 /* ASTextNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC026B581BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m */; }; - AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */; }; - AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */; }; AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */; }; AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */; }; AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */; }; @@ -578,7 +575,6 @@ F7CE6C4E1D2CDB3E00BE4C15 /* ASThread.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; }; F7CE6C4F1D2CDB3E00BE4C15 /* CoreGraphics+ASConvenience.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; }; F7CE6C501D2CDB3E00BE4C15 /* ASDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; }; - F7CE6C511D2CDB3E00BE4C15 /* ASChangeSetDataController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */; }; F7CE6C521D2CDB3E00BE4C15 /* ASIndexedNodeContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; F7CE6C531D2CDB3E00BE4C15 /* NSMutableAttributedString+TextKitAdditions.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; }; F7CE6C541D2CDB3E00BE4C15 /* _ASAsyncTransaction.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; }; @@ -757,7 +753,6 @@ F7CE6C4E1D2CDB3E00BE4C15 /* ASThread.h in CopyFiles */, F7CE6C4F1D2CDB3E00BE4C15 /* CoreGraphics+ASConvenience.h in CopyFiles */, F7CE6C501D2CDB3E00BE4C15 /* ASDataController.h in CopyFiles */, - F7CE6C511D2CDB3E00BE4C15 /* ASChangeSetDataController.h in CopyFiles */, F7CE6C521D2CDB3E00BE4C15 /* ASIndexedNodeContext.h in CopyFiles */, F7CE6C531D2CDB3E00BE4C15 /* NSMutableAttributedString+TextKitAdditions.h in CopyFiles */, F7CE6C541D2CDB3E00BE4C15 /* _ASAsyncTransaction.h in CopyFiles */, @@ -1088,8 +1083,6 @@ A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitFontSizeAdjuster.h; path = TextKit/ASTextKitFontSizeAdjuster.h; sourceTree = ""; }; A373200E1C571B050011FC94 /* ASTextNode+Beta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASTextNode+Beta.h"; sourceTree = ""; }; AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASAbsoluteLayoutSpecSnapshotTests.m; sourceTree = ""; }; - AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASChangeSetDataController.h; sourceTree = ""; }; - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASChangeSetDataController.mm; sourceTree = ""; }; AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASHierarchyChangeSet.h; sourceTree = ""; }; AC026B6E1BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASHierarchyChangeSet.mm; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; @@ -1695,8 +1688,6 @@ 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, 464052191A3F83C40061C0BA /* ASDataController.h */, 4640521A1A3F83C40061C0BA /* ASDataController.mm */, - AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.mm */, E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */, E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */, AC6145401D8AFAE8003D62A2 /* ASSection.h */, @@ -1834,7 +1825,6 @@ 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */, 698C8B621CAB49FC0052DC3F /* ASLayoutElementExtensibility.h in Headers */, 698548641CA9E025008A345F /* ASEnvironment.h in Headers */, - AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, @@ -2312,7 +2302,6 @@ 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */, 9C70F2041CDA4EFA007D6C76 /* ASTraitCollection.m in Sources */, ACF6ED321B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm in Sources */, - AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, 690C35611E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, 68C215591DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */, @@ -2503,7 +2492,6 @@ 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, - AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */, diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index 07e9010a9d..ca9905e224 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -529,7 +529,7 @@ - (void)beginUpdates { - [self.dataController beginUpdates]; + [self.view beginUpdates]; } - (void)endUpdatesAnimated:(BOOL)animated @@ -539,7 +539,7 @@ - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - [self.dataController endUpdatesAnimated:animated completion:completion]; + [self.view endUpdatesAnimated:animated completion:completion]; } - (void)insertSections:(NSIndexSet *)sections diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 73be431bbd..3a5496e04f 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -26,6 +26,7 @@ #import "ASCollectionViewLayoutFacilitatorProtocol.h" #import "ASSectionContext.h" #import "ASCollectionView+Undeprecated.h" +#import "_ASHierarchyChangeSet.h" /** * A macro to get self.collectionNode and assign it to a local variable, or return @@ -196,7 +197,17 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; * (0 sections) we always check at least once after each update (initial reload is the first update.) */ BOOL _hasEverCheckedForBatchFetchingDueToUpdate; - + + /** + * The change set that we're currently building, if any. + */ + _ASHierarchyChangeSet *_changeSet; + + /** + * Counter used to keep track of nested batch updates. + */ + NSInteger _batchUpdateCount; + struct { unsigned int scrollViewDidScroll:1; unsigned int scrollViewWillBeginDragging:1; @@ -352,6 +363,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (void)dealloc { ASDisplayNodeAssertMainThread(); + ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASCollectionView deallocated in the middle of a batch update."); + // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc. _isDeallocating = YES; [self setAsyncDelegate:nil]; @@ -402,6 +415,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (void)waitUntilAllUpdatesAreCommitted { ASDisplayNodeAssertMainThread(); + if (_batchUpdateCount > 0) { + // This assertion will be enabled soon. + // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); + return; + } + [_dataController waitUntilAllUpdatesAreCommitted]; } @@ -761,15 +780,43 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return _dataController; } +- (void)beginUpdates +{ + ASDisplayNodeAssertMainThread(); + // _changeSet must be available during batch update + ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); + + if (_batchUpdateCount == 0) { + _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; + } + _batchUpdateCount++; +} + +- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); + + _batchUpdateCount--; + // Prevent calling endUpdatesAnimated:completion: in an unbalanced way + NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); + + [_changeSet addCompletionHandler:completion]; + + if (_batchUpdateCount == 0) { + [_dataController updateWithChangeSet:_changeSet animated:animated]; + _changeSet = nil; + } +} + - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion { ASDisplayNodeAssertMainThread(); - - [_dataController beginUpdates]; + [self beginUpdates]; if (updates) { updates(); } - [_dataController endUpdatesAnimated:animated completion:completion]; + [self endUpdatesAnimated:animated completion:completion]; } - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion @@ -789,27 +836,35 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController insertSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet insertSections:sections animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)deleteSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController deleteSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet deleteSections:sections animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)reloadSections:(NSIndexSet *)sections { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController reloadSections:sections withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet reloadSections:sections animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet moveSection:section toSection:newSection animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (id)contextForSection:(NSInteger)section @@ -822,27 +877,35 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet insertItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet deleteItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet reloadItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; + [self performBatchUpdates:^{ + [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:kASCollectionViewAnimationNone]; + } completion:nil]; } #pragma mark - diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index ca2b4a0659..aa1d5a92b4 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -10,18 +10,18 @@ #import "ASTableViewInternal.h" +#import "_ASCoreAnimationExtras.h" +#import "_ASDisplayLayer.h" +#import "_ASHierarchyChangeSet.h" #import "ASAssert.h" #import "ASAvailability.h" #import "ASBatchFetching.h" #import "ASCellNode+Internal.h" -#import "ASChangeSetDataController.h" #import "ASDelegateProxy.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" -#import "_ASDisplayLayer.h" -#import "_ASCoreAnimationExtras.h" #import "ASTableNode.h" #import "ASEqualityHelpers.h" #import "ASTableView+Undeprecated.h" @@ -154,6 +154,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // This is useful because we need to measure our row nodes against (width - indexView.width). __weak UIView *_sectionIndexView; + /** + * The change set that we're currently building, if any. + */ + _ASHierarchyChangeSet *_changeSet; + + /** + * Counter used to keep track of nested batch updates. + */ + NSInteger _batchUpdateCount; + struct { unsigned int scrollViewDidScroll:1; unsigned int scrollViewWillBeginDragging:1; @@ -230,7 +240,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; + (Class)dataControllerClass { - return [ASChangeSetDataController class]; + return [ASDataController class]; } #pragma mark - @@ -303,6 +313,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)dealloc { ASDisplayNodeAssertMainThread(); + ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASTableView deallocated in the middle of a batch update."); + // Sometimes the UIKit classes can call back to their delegate even during deallocation. _isDeallocating = YES; [self setAsyncDelegate:nil]; @@ -605,7 +617,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; NSIndexPath *indexPath = [_dataController completedIndexPathForNode:cellNode]; indexPath = [self validateIndexPath:indexPath]; if (indexPath == nil && wait) { - [_dataController waitUntilAllUpdatesAreCommitted]; + [self waitUntilAllUpdatesAreCommitted]; indexPath = [_dataController completedIndexPathForNode:cellNode]; indexPath = [self validateIndexPath:indexPath]; } @@ -631,7 +643,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)beginUpdates { ASDisplayNodeAssertMainThread(); - [_dataController beginUpdates]; + // _changeSet must be available during batch update + ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); + + if (_batchUpdateCount == 0) { + _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; + } + _batchUpdateCount++; } - (void)endUpdates @@ -643,12 +661,29 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion; { ASDisplayNodeAssertMainThread(); - [_dataController endUpdatesAnimated:animated completion:completion]; + ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); + + _batchUpdateCount--; + // Prevent calling endUpdatesAnimated:completion: in an unbalanced way + NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); + + [_changeSet addCompletionHandler:completion]; + + if (_batchUpdateCount == 0) { + [_dataController updateWithChangeSet:_changeSet animated:animated]; + _changeSet = nil; + } } - (void)waitUntilAllUpdatesAreCommitted { ASDisplayNodeAssertMainThread(); + if (_batchUpdateCount > 0) { + // This assertion will be enabled soon. + // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); + return; + } + [_dataController waitUntilAllUpdatesAreCommitted]; } @@ -681,54 +716,70 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController insertSections:sections withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet insertSections:sections animationOptions:animation]; + [self endUpdates]; } - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController deleteSections:sections withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet deleteSections:sections animationOptions:animation]; + [self endUpdates]; } - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (sections.count == 0) { return; } - [_dataController reloadSections:sections withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet reloadSections:sections animationOptions:animation]; + [self endUpdates]; } - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; + [self beginUpdates]; + [_changeSet moveSection:section toSection:newSection animationOptions:UITableViewRowAnimationNone]; + [self endUpdates]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet insertItems:indexPaths animationOptions:animation]; + [self endUpdates]; } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet deleteItems:indexPaths animationOptions:animation]; + [self endUpdates]; } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { ASDisplayNodeAssertMainThread(); if (indexPaths.count == 0) { return; } - [_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation]; + [self beginUpdates]; + [_changeSet reloadItems:indexPaths animationOptions:animation]; + [self endUpdates]; } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; + [self beginUpdates]; + [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:UITableViewRowAnimationNone]; + [self endUpdates]; } #pragma mark - diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index fe33989ca9..79ef39325d 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -44,7 +44,7 @@ #import #import -#import +#import #import #import diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.h b/AsyncDisplayKit/Details/ASChangeSetDataController.h deleted file mode 100644 index fa24b28ae2..0000000000 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// ASChangeSetDataController.h -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// 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 - -/** - * @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 diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/AsyncDisplayKit/Details/ASChangeSetDataController.mm deleted file mode 100644 index 8544b10180..0000000000 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.mm +++ /dev/null @@ -1,208 +0,0 @@ -// -// ASChangeSetDataController.m -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// 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 "ASChangeSetDataController.h" -#import "_ASHierarchyChangeSet.h" -#import "ASAssert.h" -#import "ASDataController+Subclasses.h" - -@implementation ASChangeSetDataController { - NSInteger _changeSetBatchUpdateCounter; - _ASHierarchyChangeSet *_changeSet; -} - -- (void)dealloc -{ - ASDisplayNodeCAssert(_changeSetBatchUpdateCounter == 0, @"ASChangeSetDataController deallocated in the middle of a batch update."); -} - -#pragma mark - Batching (External API) - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - if (_changeSetBatchUpdateCounter <= 0) { - _changeSetBatchUpdateCounter = 0; - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[self itemCountsFromDataSource]]; - } - _changeSetBatchUpdateCounter++; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - _changeSetBatchUpdateCounter--; - - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - if (_changeSetBatchUpdateCounter == 0) { - void (^batchCompletion)(BOOL) = _changeSet.completionHandler; - - /** - * If the initial reloadData has not been called, just bail because we don't have - * our old data source counts. - * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - * For the issue that UICollectionView has that we're choosing to workaround. - */ - if (!self.initialReloadDataHasBeenCalled) { - if (batchCompletion != nil) { - batchCompletion(YES); - } - _changeSet = nil; - return; - } - - [self invalidateDataSourceItemCounts]; - - // Attempt to mark the update completed. This is when update validation will occur inside the changeset. - // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, - // which is the table/collection node's data source, into the exception reason to help debugging. - @try { - [_changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; - } @catch (NSException *e) { - id responsibleDataSource = self.validationErrorSource; - if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { - [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; - } else { - @throw e; - } - } - - ASDataControllerLogEvent(self, @"triggeredUpdate: %@", _changeSet); - - [super beginUpdates]; - - 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]; - } - -#if ASEVENTLOG_ENABLE - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(_changeSet); - batchCompletion = ^(BOOL finished) { - if (batchCompletion != nil) { - batchCompletion(finished); - } - ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); - }; -#endif - - [super endUpdatesAnimated:animated completion:batchCompletion]; - - _changeSet = nil; - } -} - -- (BOOL)batchUpdating -{ - BOOL batchUpdating = (_changeSetBatchUpdateCounter != 0); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil)); - return batchUpdating; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (self.batchUpdating) { - // This assertion will be enabled soon. -// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [super waitUntilAllUpdatesAreCommitted]; -} - -#pragma mark - Section Editing (External API) - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; - [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; - [self endUpdates]; -} - -#pragma mark - Row Editing (External API) - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; - [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; - [self endUpdates]; -} - -@end diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 74c08ec413..34e7e6f872 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -10,7 +10,7 @@ #import -#import +#import #import @class ASDisplayNode; @@ -41,7 +41,7 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASCollectionDataController : ASChangeSetDataController +@interface ASCollectionDataController : ASDataController - (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.h b/AsyncDisplayKit/Details/ASCollectionInternal.h index a75c8fc2a7..2613ca98b0 100644 --- a/AsyncDisplayKit/Details/ASCollectionInternal.h +++ b/AsyncDisplayKit/Details/ASCollectionInternal.h @@ -48,6 +48,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSArray *)convertIndexPathsToCollectionNode:(nullable NSArray *)indexPaths; +- (void)beginUpdates; + +- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 1a17e3dd3f..c07997c7a4 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -14,6 +14,9 @@ #import #import #import +#ifdef __cplusplus +#import +#endif NS_ASSUME_NONNULL_BEGIN @@ -25,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @class ASCellNode; @class ASDataController; +@class _ASHierarchyChangeSet; @protocol ASEnvironment; typedef NSUInteger ASDataControllerAnimationOptions; @@ -137,6 +141,16 @@ extern NSString * const ASCollectionInvalidUpdateException; */ @property (nonatomic, weak) id environmentDelegate; +#ifdef __cplusplus +/** + * Returns the most recently gathered item counts from the data source. If the counts + * have been invalidated, this synchronously queries the data source and saves the result. + * + * This must be called on the main thread. + */ +- (std::vector)itemCountsFromDataSource; +#endif + /** * Returns YES if reloadData has been called at least once. Before this point it is * important to ignore/suppress some operations. For example, inserting a section @@ -155,25 +169,7 @@ extern NSString * const ASCollectionInvalidUpdateException; /** @name Data Updating */ -- (void)beginUpdates; - -- (void)endUpdates; - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL))completion; - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet animated:(BOOL)animated; /** * Re-measures all loaded nodes in the backing store. @@ -183,8 +179,6 @@ extern NSString * const ASCollectionInvalidUpdateException; */ - (void)relayoutAllNodes; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - - (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; - (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index df12fcfe1d..8c31a565ee 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -10,6 +10,7 @@ #import "ASDataController.h" +#import "_ASHierarchyChangeSet.h" #import "ASAssert.h" #import "ASCellNode.h" #import "ASEnvironmentInternal.h" @@ -77,7 +78,6 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat if (!(self = [super init])) { return nil; } - ASDisplayNodeAssert(![self isMemberOfClass:[ASDataController class]], @"ASDataController is an abstract class and should not be instantiated. Instantiate a subclass instead."); _dataSource = dataSource; @@ -580,11 +580,6 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat }); } -- (void)endUpdates -{ - [self endUpdatesAnimated:YES completion:nil]; -} - - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { LOG(@"endUpdatesWithCompletion - beginning"); @@ -603,6 +598,74 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat }); } +- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet animated:(BOOL)animated +{ + ASDisplayNodeAssertMainThread(); + + void (^batchCompletion)(BOOL) = changeSet.completionHandler; + + /** + * If the initial reloadData has not been called, just bail because we don't have + * our old data source counts. + * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable + * For the issue that UICollectionView has that we're choosing to workaround. + */ + if (!self.initialReloadDataHasBeenCalled) { + if (batchCompletion != nil) { + batchCompletion(YES); + } + return; + } + + [self invalidateDataSourceItemCounts]; + + // Attempt to mark the update completed. This is when update validation will occur inside the changeset. + // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, + // which is the table/collection node's data source, into the exception reason to help debugging. + @try { + [changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; + } @catch (NSException *e) { + id responsibleDataSource = self.validationErrorSource; + if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { + [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; + } else { + @throw e; + } + } + + ASDataControllerLogEvent(self, @"triggeredUpdate: %@", changeSet); + + [self beginUpdates]; + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { + [self deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { + [self deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { + [self insertSections:change.indexSet withAnimationOptions:change.animationOptions]; + } + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { + [self insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; + } + +#if ASEVENTLOG_ENABLE + NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet); + batchCompletion = ^(BOOL finished) { + if (batchCompletion != nil) { + batchCompletion(finished); + } + ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); + }; +#endif + + [self endUpdatesAnimated:animated completion:batchCompletion]; +} + #pragma mark - Section Editing (External API) - (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -665,17 +728,6 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat }); } -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController the reload will be broken into delete & insert.", NSStringFromSelector(_cmd)); -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the move will be processed along with the current batch of updates.", NSStringFromSelector(_cmd)); -} - - #pragma mark - Backing store manipulation optional hooks (Subclass API) - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount @@ -794,11 +846,6 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat }); } -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the reload will be broken into delete & insert.", NSStringFromSelector(_cmd)); -} - - (void)relayoutAllNodes { ASDisplayNodeAssertMainThread(); @@ -843,11 +890,6 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat } } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the move will be processed along with the current batch of updates.", NSStringFromSelector(_cmd)); -} - #pragma mark - Data Querying (Subclass API) - (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind diff --git a/AsyncDisplayKit/Private/ASDataController+Subclasses.h b/AsyncDisplayKit/Private/ASDataController+Subclasses.h index 2012460f21..cf2f1b5b91 100644 --- a/AsyncDisplayKit/Private/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Private/ASDataController+Subclasses.h @@ -34,21 +34,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS */ - (NSMutableArray *)completedNodesOfKind:(NSString *)kind; -/** - * Ensure that next time `itemCountsFromDataSource` is called, new values are retrieved. - * - * This must be called on the main thread. - */ -- (void)invalidateDataSourceItemCounts; - -/** - * Returns the most recently gathered item counts from the data source. If the counts - * have been invalidated, this synchronously queries the data source and saves the result. - * - * This must be called on the main thread. - */ -- (std::vector)itemCountsFromDataSource; - #pragma mark - Node sizing /** diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h index f2994c2ab2..6e6e74ead7 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h @@ -145,6 +145,9 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); - (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; - (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; - (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options; +- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm index e88551500c..8a04f5a88d 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm @@ -287,6 +287,24 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) [_reloadSectionChanges addObject:change]; } +- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options +{ + /** + * TODO: Proper move implementation. + */ + [self deleteItems:@[ indexPath ] animationOptions:options]; + [self insertItems:@[ newIndexPath ] animationOptions:options]; +} + +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options +{ + /** + * TODO: Proper move implementation. + */ + [self deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:options]; + [self insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:options]; +} + #pragma mark Private - (BOOL)_ensureNotCompleted diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.mm b/AsyncDisplayKitTests/ASCollectionViewTests.mm index a906eec068..0bb3e88670 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.mm +++ b/AsyncDisplayKitTests/ASCollectionViewTests.mm @@ -696,7 +696,7 @@ XCTAssertTrue(toSection < originalSection); ASTestSectionContext *movedSectionContext = (ASTestSectionContext *)[cn contextForSection:toSection]; XCTAssertNotNil(movedSectionContext); - // ASCollectionView currently uses ASChangeSetDataController which splits a move operation to a pair of delete and insert ones. + // ASCollectionView currently splits a move operation to a pair of delete and insert ones. // So this movedSectionContext is newly loaded and thus is second generation. XCTAssertEqual(movedSectionContext.sectionGeneration, 2); XCTAssertEqual(movedSectionContext.sectionIndex, toSection); diff --git a/AsyncDisplayKitTests/ASTableViewTests.mm b/AsyncDisplayKitTests/ASTableViewTests.mm index 99df044364..078a62e7ec 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.mm +++ b/AsyncDisplayKitTests/ASTableViewTests.mm @@ -13,7 +13,6 @@ #import "ASTableView.h" #import "ASTableViewInternal.h" #import "ASDisplayNode+Subclasses.h" -#import "ASChangeSetDataController.h" #import "ASCellNode.h" #import "ASTableNode.h" #import "ASTableView+Undeprecated.h" @@ -24,7 +23,7 @@ #define NumberOfSections 10 #define NumberOfReloadIterations 50 -@interface ASTestDataController : ASChangeSetDataController +@interface ASTestDataController : ASDataController @property (nonatomic) int numberOfAllNodesRelayouts; @end