From 54cda5fdbb14c9c6235e2dbe1a51e05f5ee9abd3 Mon Sep 17 00:00:00 2001 From: george-gw Date: Fri, 28 Oct 2016 01:56:00 +0200 Subject: [PATCH] [ASCellNode] Added indexPath property. (#2468) * [ASCellNode] Added indexPath property. * [ASCellNode] Cached the type of scrollView we're using in the node, and placed that logic in setScrollView. * [ASCellNode] Removed table and collection view from indexPath, since they return the index paths from the view-space. * Changed the logic for getting indexPath so that it works even when the cell is not displayed. * [VerticalWithinHorizontalScrolling] Explicitally synthesized indexPath property. --- AsyncDisplayKit/ASCellNode+Internal.h | 2 + AsyncDisplayKit/ASCellNode.h | 14 +++++++ AsyncDisplayKit/ASCellNode.mm | 32 +++++++++++++++ .../Details/ASCollectionDataController.mm | 16 ++++---- AsyncDisplayKit/Details/ASDataController.mm | 10 ++--- .../Details/ASIndexedNodeContext.h | 3 +- .../Details/ASIndexedNodeContext.mm | 6 ++- AsyncDisplayKitTests/ASCollectionViewTests.mm | 39 +++++++++++++++++++ .../Sample/RandomCoreGraphicsNode.m | 2 + 9 files changed, 106 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode+Internal.h b/AsyncDisplayKit/ASCellNode+Internal.h index 0bf032b0fb..99066aa123 100644 --- a/AsyncDisplayKit/ASCellNode+Internal.h +++ b/AsyncDisplayKit/ASCellNode+Internal.h @@ -63,6 +63,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy, nullable) NSIndexPath *cachedIndexPath; +@property (weak, nonatomic, nullable) ASDisplayNode *owningNode; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 003e19da2e..0a6ccf3fb3 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -108,6 +108,20 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ @property (nonatomic, assign, getter=isHighlighted) BOOL highlighted; +/** + * The current index path of this cell node, or @c nil if this node is + * not a valid item inside a table node or collection node. + * + * @note This property must be accessed on the main thread. + */ +@property (nonatomic, readonly, nullable) NSIndexPath *indexPath; + +/** + * The owning node (ASCollectionNode/ASTableNode) of this cell node, or @c nil if this node is + * not a valid item inside a table node or collection node or if those nodes are nil. + */ +@property (weak, nonatomic, readonly, nullable) ASDisplayNode *owningNode; + /* * ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding * these methods (e.g. for highlighting) requires the super method be called. diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 2e14030c92..84c81b6bf6 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -36,6 +36,12 @@ ASDisplayNode *_viewControllerNode; UIViewController *_viewController; BOOL _suspendInteractionDelegate; + + struct { + unsigned int isTableNode:1; + unsigned int isCollectionNode:1; + } _owningNodeType; + } @end @@ -186,6 +192,19 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init } } +- (void)setOwningNode:(ASDisplayNode *)owningNode +{ + _owningNode = owningNode; + + memset(&_owningNodeType, 0, sizeof(_owningNodeType)); + + if ([owningNode isKindOfClass:[ASTableNode class]]) { + _owningNodeType.isTableNode = 1; + } else if ([owningNode isKindOfClass:[ASCollectionNode class]]) { + _owningNodeType.isCollectionNode = 1; + } +} + - (void)__setSelectedFromUIKit:(BOOL)selected; { if (selected != _selected) { @@ -204,6 +223,19 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init } } +- (NSIndexPath *)indexPath +{ + ASDisplayNodeAssertMainThread(); + + if (_owningNodeType.isTableNode) { + return [(ASTableNode *)self.owningNode indexPathForNode:self]; + } else if (_owningNodeType.isCollectionNode) { + return [(ASCollectionNode *)self.owningNode indexPathForNode:self]; + } + + return nil; +} + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-missing-super-calls" diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 8ffa75ca0c..444d8fc7aa 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -233,15 +233,14 @@ - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts { - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - + __weak id environment = [self.environmentDelegate dataControllerEnvironment]; + [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; for (NSUInteger i = 0; i < itemCount; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; } } }]; @@ -249,8 +248,7 @@ - (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths mutableContexts:(NSMutableArray *)contexts { - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + __weak id environment = [self.environmentDelegate dataControllerEnvironment]; NSMutableIndexSet *sections = [NSMutableIndexSet indexSet]; for (NSIndexPath *indexPath in indexPaths) { @@ -262,13 +260,13 @@ NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; for (NSUInteger i = 0; i < itemCount; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; + [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; } } }]; } -- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection +- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environment:(id)environment { ASCellNodeBlock supplementaryCellBlock; if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { @@ -283,7 +281,7 @@ indexPath:indexPath supplementaryElementKind:kind constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]; + environment:environment]; [contexts addObject:context]; } diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7638a4e1d4..4de4eee949 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -476,8 +476,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; { ASDisplayNodeAssertMainThread(); - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + __weak id environment = [self.environmentDelegate dataControllerEnvironment]; std::vector counts = [self itemCountsFromDataSource]; NSMutableArray *contexts = [NSMutableArray array]; @@ -493,7 +492,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; indexPath:indexPath supplementaryElementKind:nil constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]]; + environment:environment]]; } } }]; @@ -743,8 +742,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + __weak id environment = [self.environmentDelegate dataControllerEnvironment]; for (NSIndexPath *indexPath in sortedIndexPaths) { ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; @@ -753,7 +751,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; indexPath:indexPath supplementaryElementKind:nil constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]]; + environment:environment]]; } ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths, contexts); diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.h b/AsyncDisplayKit/Details/ASIndexedNodeContext.h index 6e7992135d..2bf7822221 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.h +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.h @@ -24,13 +24,14 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, strong) NSIndexPath *indexPath; @property (nonatomic, readonly, copy, nullable) NSString *supplementaryElementKind; @property (nonatomic, readonly, assign) ASSizeRange constrainedSize; +@property (weak, nonatomic) id environment; @property (nonatomic, readonly, assign) ASEnvironmentTraitCollection environmentTraitCollection; - (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock indexPath:(NSIndexPath *)indexPath supplementaryElementKind:(nullable NSString *)supplementaryElementKind constrainedSize:(ASSizeRange)constrainedSize - environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection; + environment:(id)environment; /** * @return The node, running the node block if necessary. The node block will be discarded diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm b/AsyncDisplayKit/Details/ASIndexedNodeContext.mm index 38624e3a42..15020eecb1 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.mm @@ -31,7 +31,7 @@ indexPath:(NSIndexPath *)indexPath supplementaryElementKind:(nullable NSString *)supplementaryElementKind constrainedSize:(ASSizeRange)constrainedSize - environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection + environment:(id)environment { NSAssert(nodeBlock != nil && indexPath != nil, @"Node block and index path must not be nil"); self = [super init]; @@ -40,7 +40,8 @@ _indexPath = indexPath; _supplementaryElementKind = [supplementaryElementKind copy]; _constrainedSize = constrainedSize; - _environmentTraitCollection = environmentTraitCollection; + _environment = environment; + _environmentTraitCollection = environment.environmentTraitCollection; } return self; } @@ -57,6 +58,7 @@ } node.cachedIndexPath = _indexPath; node.supplementaryElementKind = _supplementaryElementKind; + node.owningNode = (ASDisplayNode *)_environment; ASEnvironmentStatePropagateDown(node, _environmentTraitCollection); _node = node; } diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.mm b/AsyncDisplayKitTests/ASCollectionViewTests.mm index f19d8fa0fb..f0f509195e 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.mm +++ b/AsyncDisplayKitTests/ASCollectionViewTests.mm @@ -504,6 +504,45 @@ } } +- (void)testCellNodeIndexPathConsistency +{ + updateValidationTestPrologue + + // Test with a visible cell + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:2 inSection:0]; + ASCellNode *cell = [cn nodeForItemAtIndexPath:indexPath]; + + // Check if cell's indexPath corresponds to the indexPath being tested + XCTAssertTrue(cell.indexPath.section == indexPath.section && cell.indexPath.item == indexPath.item, @"Expected the cell's indexPath to be the same as the indexPath being tested."); + + // Remove an item prior to the cell's indexPath from the same section and check for indexPath consistency + --del->_itemCounts[indexPath.section]; + [cn deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:indexPath.section]]]; + XCTAssertTrue(cell.indexPath.section == indexPath.section && cell.indexPath.item == (indexPath.item - 1), @"Expected the cell's indexPath to be updated once a cell with a lower index is deleted."); + + // Remove the section that includes the indexPath and check if the cell's indexPath is now nil + del->_itemCounts.erase(del->_itemCounts.begin()); + [cn deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]]; + XCTAssertNil(cell.indexPath, @"Expected the cell's indexPath to be nil once the section that contains the node is deleted."); + + // Run the same tests but with a non-displayed cell + indexPath = [NSIndexPath indexPathForItem:2 inSection:(del->_itemCounts.size() - 1)]; + cell = [cn nodeForItemAtIndexPath:indexPath]; + + // Check if cell's indexPath corresponds to the indexPath being tested + XCTAssertTrue(cell.indexPath.section == indexPath.section && cell.indexPath.item == indexPath.item, @"Expected the cell's indexPath to be the same as the indexPath in question."); + + // Remove an item prior to the cell's indexPath from the same section and check for indexPath consistency + --del->_itemCounts[indexPath.section]; + [cn deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:indexPath.section]]]; + XCTAssertTrue(cell.indexPath.section == indexPath.section && cell.indexPath.item == (indexPath.item - 1), @"Expected the cell's indexPath to be updated once a cell with a lower index is deleted."); + + // Remove the section that includes the indexPath and check if the cell's indexPath is now nil + del->_itemCounts.pop_back(); + [cn deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]]; + XCTAssertNil(cell.indexPath, @"Expected the cell's indexPath to be nil once the section that contains the node is deleted."); +} + /** * https://github.com/facebook/AsyncDisplayKit/issues/2011 * diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m index 74dd517084..16d40df7aa 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/RandomCoreGraphicsNode.m @@ -22,6 +22,8 @@ @implementation RandomCoreGraphicsNode +@synthesize indexPath=_indexPath; + + (UIColor *)randomColor { CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0