diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index bb3dfae87b..1c29a4b580 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -375,6 +375,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray<__kindof NSIndexPath *> *)indexPathsForVisibleItems AS_WARN_UNUSED_RESULT; +/** + * Retrieve the index path of the item at the given point. + * + * @param point The point of the requested item. + * + * @return The indexPath for the item at the given point. This must be called on the main thread. + */ +- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point AS_WARN_UNUSED_RESULT; + +/** + * Retrieve the cell at the given index path. + * + * @param indexPath The index path of the requested item. + * + * @return The cell for the given index path. This must be called on the main thread. + */ +- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath; + /** * Retrieves the context object for the given section, as provided by the data source in * the @c collectionNode:contextForSection: method. diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index a073769b08..4525e590fa 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -322,17 +322,29 @@ - (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition { ASDisplayNodeAssertMainThread(); - // TODO: Solve this in a way to be able to remove this restriction (https://github.com/facebook/AsyncDisplayKit/pull/2453#discussion_r84515457) - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded before calling selectItemAtIndexPath"); - [self.view selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; + ASCollectionView *collectionView = self.view; + + indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; + + if (indexPath != nil) { + [collectionView selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; + } else { + NSLog(@"Failed to select item at index path %@ because the item never reached the view.", indexPath); + } } - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { ASDisplayNodeAssertMainThread(); - // TODO: Solve this in a way to be able to remove this restriction (https://github.com/facebook/AsyncDisplayKit/pull/2453#discussion_r84515457) - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded before calling deselectItemAtIndexPath"); - [self.view deselectItemAtIndexPath:indexPath animated:animated]; + ASCollectionView *collectionView = self.view; + + indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; + + if (indexPath != nil) { + [collectionView deselectItemAtIndexPath:indexPath animated:animated]; + } else { + NSLog(@"Failed to deselect item at index path %@ because the item never reached the view.", indexPath); + } } - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated @@ -376,13 +388,20 @@ return self.isNodeLoaded ? [self.view visibleNodes] : @[]; } +- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + [self reloadDataInitiallyIfNeeded]; + return [self.dataController nodeAtIndexPath:indexPath]; +} + - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode { return [self.dataController indexPathForNode:cellNode]; } -- (NSArray<__kindof NSIndexPath *> *)indexPathsForVisibleItems +- (NSArray *)indexPathsForVisibleItems { + ASDisplayNodeAssertMainThread(); NSMutableArray *indexPathsArray = [NSMutableArray new]; for (ASCellNode *cell in [self visibleNodes]) { NSIndexPath *indexPath = [self indexPathForNode:cell]; @@ -393,11 +412,28 @@ return indexPathsArray; } - -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath +- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point { - [self reloadDataInitiallyIfNeeded]; - return [self.dataController nodeAtIndexPath:indexPath]; + ASDisplayNodeAssertMainThread(); + ASCollectionView *collectionView = self.view; + + NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:point]; + if (indexPath != nil) { + return [collectionView convertIndexPathToCollectionNode:indexPath]; + } + return indexPath; +} + +- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssertMainThread(); + ASCollectionView *collectionView = self.view; + + indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; + if (indexPath == nil) { + return nil; + } + return [collectionView cellForItemAtIndexPath:indexPath]; } - (id)contextForSection:(NSInteger)section diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 33806b4c00..b6c0b73e5f 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -214,6 +214,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); + /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. * The asyncDataSource must be updated to reflect the changes before the update block completes. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 06aab6899b..969402d7ce 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -608,28 +608,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return visibleNodes; } -/** - * TODO: This method was built when the distinction between data source - * index paths and view index paths was unclear. For compatibility, it - * still expects data source index paths for the time being. - */ -- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition -{ - ASDisplayNodeAssertMainThread(); - // If they passed nil, just forward it and be done. - if (indexPath == nil) { - [super selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; - return; - } - - NSIndexPath *viewIndexPath = [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - if (viewIndexPath != nil) { - [super selectItemAtIndexPath:viewIndexPath animated:animated scrollPosition:scrollPosition]; - } else { - NSLog(@"Warning: Ignoring request to select item at index path %@ because the item did not reach the collection view.", indexPath); - } -} - #pragma mark Internal /** diff --git a/AsyncDisplayKit/ASTableNode.h b/AsyncDisplayKit/ASTableNode.h index 83ae540cc7..e656f1b083 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/AsyncDisplayKit/ASTableNode.h @@ -291,7 +291,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated; - #pragma mark - Querying Data /** @@ -327,6 +326,74 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; +/** + * Similar to -[UITableView rectForRowAtIndexPath:] + * + * @param indexPath An index path identifying a row in the table view. + * + * @return A rectangle defining the area in which the table view draws the row or CGRectZero if indexPath is invalid. + * + * @discussion This method must be called from the main thread. + */ +- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; + +/** + * Similar to -[UITableView cellForRowAtIndexPath:] + * + * @param indexPath An index path identifying a row in the table view. + * + * @return An object representing a cell of the table, or nil if the cell is not visible or indexPath is out of range. + * + * @discussion This method must be called from the main thread. + */ +- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; + +/** + * Similar to -[UITableView indexPathForSelectedRow] + * + * @return The value of this property is an index path identifying the row and section + * indexes of the selected row, or nil if the index path is invalid. If there are multiple selections, + * this property contains the first index-path object in the array of row selections; + * this object has the lowest index values for section and row. + * + * @discussion This method must be called from the main thread. + */ +- (nullable NSIndexPath *)indexPathForSelectedRow AS_WARN_UNUSED_RESULT; + +/** + * Similar to -[UITableView indexPathForRowAtPoint:] + * + * @param point A point in the local coordinate system of the table view (the table view’€™s bounds). + * + * @return An index path representing the row and section associated with point, + * or nil if the point is out of the bounds of any row. + * + * @discussion This method must be called from the main thread. + */ +- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point AS_WARN_UNUSED_RESULT; + +/** + * Similar to -[UITableView indexPathsForRowsInRect:] + * + * @param rect A rectangle defining an area of the table view in local coordinates. + * + * @return An array of NSIndexPath objects each representing a row and section index identifying a row within rect. + * Returns an empty array if there aren’t any rows to return. + * + * @discussion This method must be called from the main thread. + */ +- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect AS_WARN_UNUSED_RESULT; + +/** + * Similar to -[UITableView indexPathsForVisibleRows] + * + * @return The value of this property is an array of NSIndexPath objects each representing a row index and section index + * that together identify a visible row in the table view. If no rows are visible, the value is nil. + * + * @discussion This method must be called from the main thread. + */ +- (NSArray *)indexPathsForVisibleRows AS_WARN_UNUSED_RESULT; + @end /** diff --git a/AsyncDisplayKit/ASTableNode.mm b/AsyncDisplayKit/ASTableNode.mm index 954a2ae26e..6fd58035a7 100644 --- a/AsyncDisplayKit/ASTableNode.mm +++ b/AsyncDisplayKit/ASTableNode.mm @@ -346,17 +346,28 @@ ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) - (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition { ASDisplayNodeAssertMainThread(); - // TODO: Solve this in a way to be able to remove this restriction (https://github.com/facebook/AsyncDisplayKit/pull/2453#discussion_r84515457) - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded before calling selectRowAtIndexPath"); - [self.view selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; + ASTableView *tableView = self.view; + + indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; + if (indexPath != nil) { + [tableView selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; + } else { + NSLog(@"Failed to select row at index path %@ because the row never reached the view.", indexPath); + } + } - (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { ASDisplayNodeAssertMainThread(); - // TODO: Solve this in a way to be able to remove this restriction (https://github.com/facebook/AsyncDisplayKit/pull/2453#discussion_r84515457) - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded before calling deselectRowAtIndexPath"); - [self.view deselectRowAtIndexPath:indexPath animated:animated]; + ASTableView *tableView = self.view; + + indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; + if (indexPath != nil) { + [tableView deselectRowAtIndexPath:indexPath animated:animated]; + } else { + NSLog(@"Failed to deselect row at index path %@ because the row never reached the view.", indexPath); + } } - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated @@ -411,6 +422,71 @@ ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) return [self.dataController nodeAtIndexPath:indexPath]; } +- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssertMainThread(); + ASTableView *tableView = self.view; + + indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; + return [tableView rectForRowAtIndexPath:indexPath]; +} + +- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssertMainThread(); + ASTableView *tableView = self.view; + + indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; + if (indexPath == nil) { + return nil; + } + return [tableView cellForRowAtIndexPath:indexPath]; +} + +- (nullable NSIndexPath *)indexPathForSelectedRow +{ + ASDisplayNodeAssertMainThread(); + ASTableView *tableView = self.view; + + NSIndexPath *indexPath = tableView.indexPathForSelectedRow; + if (indexPath != nil) { + return [tableView convertIndexPathToTableNode:indexPath]; + } + return indexPath; +} + +- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point +{ + ASDisplayNodeAssertMainThread(); + ASTableView *tableView = self.view; + + NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:point]; + if (indexPath != nil) { + return [tableView convertIndexPathToTableNode:indexPath]; + } + return indexPath; +} + +- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect +{ + ASDisplayNodeAssertMainThread(); + ASTableView *tableView = self.view; + return [tableView convertIndexPathsToTableNode:[tableView indexPathsForRowsInRect:rect]]; +} + +- (NSArray *)indexPathsForVisibleRows +{ + ASDisplayNodeAssertMainThread(); + NSMutableArray *indexPathsArray = [NSMutableArray new]; + for (ASCellNode *cell in [self visibleNodes]) { + NSIndexPath *indexPath = [self indexPathForNode:cell]; + if (indexPath) { + [indexPathsArray addObject:indexPath]; + } + } + return indexPathsArray; +} + #pragma mark - Editing - (void)reloadDataWithCompletion:(void (^)())completion diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 2bed257651..b454e497ed 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -133,6 +133,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); +- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition; ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); + /** * Similar to -visibleCells. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index e51a92cec1..8a3334eac5 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -486,6 +486,23 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (NSArray *)convertIndexPathsToTableNode:(NSArray *)indexPaths +{ + if (indexPaths == nil) { + return nil; + } + + NSMutableArray *indexPathsArray = [NSMutableArray new]; + + for (NSIndexPath *indexPathInView in indexPaths) { + NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; + if (indexPath != nil) { + [indexPathsArray addObject:indexPath]; + } + } + return indexPathsArray; +} + - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode { return [self indexPathForNode:cellNode waitingIfNeeded:NO]; @@ -540,28 +557,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_dataController waitUntilAllUpdatesAreCommitted]; } -/** - * TODO: This method was built when the distinction between data source - * index paths and view index paths was unclear. For compatibility, it - * still expects data source index paths for the time being. - */ -- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition -{ - ASDisplayNodeAssertMainThread(); - // If they passed nil, just forward it and be done. - if (indexPath == nil) { - [super selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; - return; - } - - indexPath = [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - if (indexPath != nil) { - [super selectRowAtIndexPath:indexPath animated:YES scrollPosition:scrollPosition]; - } else { - NSLog(@"Warning: Ignoring request to select row at index path %@ because the item did not reach the table view.", indexPath); - } -} - - (void)layoutSubviews { if (_nodesConstrainedWidth != self.bounds.size.width) { diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/AsyncDisplayKit/ASTableViewInternal.h index 0a9cbf63aa..8b36014a52 100644 --- a/AsyncDisplayKit/ASTableViewInternal.h +++ b/AsyncDisplayKit/ASTableViewInternal.h @@ -45,4 +45,18 @@ */ - (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait; +/** + * Attempt to get the node index path given the view-layer index path. + * + * @param indexPath The index path of the row. + */ +- (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath; + +/** + * Attempt to get the node index paths given the view-layer index paths. + * + * @param indexPaths An array of index paths in the view space + */ +- (NSArray *)convertIndexPathsToTableNode:(NSArray *)indexPaths; + @end diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.h b/AsyncDisplayKit/Details/ASCollectionInternal.h index 1b4e7cc7a3..c17c8d689a 100644 --- a/AsyncDisplayKit/Details/ASCollectionInternal.h +++ b/AsyncDisplayKit/Details/ASCollectionInternal.h @@ -34,6 +34,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait; +/** + * Attempt to get the node index path given the view-layer index path. + * + * @param indexPath The index path of the row. + */ +- (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath; + @end NS_ASSUME_NONNULL_END