From ad17f61e708640bab62d17be8ca26a08c419e9a5 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 14 Sep 2016 22:31:04 -0700 Subject: [PATCH] [ASTableView] Add Support for Interactive Reordering (#2221) --- AsyncDisplayKit/ASTableView.mm | 22 +++++++++++++++++++ AsyncDisplayKit/Details/ASDataController.h | 8 +++++++ AsyncDisplayKit/Details/ASDataController.mm | 7 ++++++ AsyncDisplayKit/Details/ASDelegateProxy.m | 4 ++++ .../Private/ASMultidimensionalArrayUtils.h | 5 +++++ .../Private/ASMultidimensionalArrayUtils.mm | 9 ++++++++ 6 files changed, 55 insertions(+) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 688a7be630..14330d7981 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -146,6 +146,8 @@ @interface ASTableView () )asyncDataSource _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceTableViewCanMoveRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:canMoveRowAtIndexPath:)]; + _asyncDataSourceFlags.asyncDataSourceTableViewMoveRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:moveRowAtIndexPath:toIndexPath:)]; // Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath: ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath); @@ -660,6 +664,24 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger return [_dataController numberOfRowsInSection:section]; } +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (_asyncDataSourceFlags.asyncDataSourceTableViewCanMoveRowAtIndexPath) { + return [_asyncDataSource tableView:self canMoveRowAtIndexPath:indexPath]; + } else { + return NO; + } +} + +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath +{ + if (_asyncDataSourceFlags.asyncDataSourceTableViewMoveRowAtIndexPath) { + [_asyncDataSource tableView:self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; + } + // Move node after informing data source in case they call nodeAtIndexPath: + [_dataController moveCompletedNodeAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; +} + - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { _pendingVisibleIndexPath = indexPath; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 7d748663dd..58e7b35bfe 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -188,6 +188,14 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (NSArray *> *)completedNodes; +/** + * Immediately move this item. This is called by ASTableView when the user has finished an interactive + * item move and the table view is requesting a model update. + * + * This must be called on the main thread. + */ +- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index e6ec2b763d..448d719234 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -898,6 +898,13 @@ - (NSArray *)completedNodes return _externalCompletedNodes ? : _completedNodes[ASDataControllerRowNodeKind]; } +- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath +{ + ASDisplayNodeAssertMainThread(); + ASMoveElementInTwoDimensionalArray(_externalCompletedNodes, indexPath, newIndexPath); + ASMoveElementInTwoDimensionalArray(_completedNodes[ASDataControllerRowNodeKind], indexPath, newIndexPath); +} + #pragma mark - Dealloc - (void)dealloc diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 110845bd1e..5e1fcc2818 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -25,6 +25,10 @@ - (BOOL)interceptsSelector:(SEL)selector // handled by ASRangeController selector == @selector(numberOfSectionsInTableView:) || selector == @selector(tableView:numberOfRowsInSection:) || + + // reordering support + selector == @selector(tableView:canMoveRowAtIndexPath:) || + selector == @selector(tableView:moveRowAtIndexPath:toIndexPath:) || // used for ASCellNode visibility selector == @selector(scrollViewDidScroll:) || diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h index 46529d3b2c..44d8256dcf 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h @@ -51,6 +51,11 @@ extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray */ extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet); +/** + * Moves the object at `sourceIndexPath` to `destinationIndexPath`. + */ +extern void ASMoveElementInTwoDimensionalArray(NSMutableArray *mutableArray, NSIndexPath *sourceIndexPath, NSIndexPath *destinationIndexPath); + /** * Return the index paths of the given multidimensional array that are present in the given index paths array. */ diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm index e8b3671977..6969d1d657 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm @@ -182,6 +182,15 @@ void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutable return res; } +void ASMoveElementInTwoDimensionalArray(NSMutableArray *mutableArray, NSIndexPath *sourceIndexPath, NSIndexPath *destinationIndexPath) +{ + NSMutableArray *oldSection = mutableArray[sourceIndexPath.section]; + NSInteger oldItem = sourceIndexPath.item; + id object = oldSection[oldItem]; + [oldSection removeObjectAtIndex:oldItem]; + [mutableArray[destinationIndexPath.section] insertObject:object atIndex:destinationIndexPath.item]; +} + NSArray *ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths(NSArray *multidimensionalArray, NSArray *indexPaths) { NSMutableArray *res = [NSMutableArray array];