Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit 3164d8d

Browse files
plarsonAdlai Holler
authored andcommitted
[ASCollectionNode] Add _ASCollectionReusableView container for supplementary nodes (#3255)
* Add _ASCollectionReusableView container for ASCollectionNode supplementary nodes with applyLayoutAttributes support • Adds support for -[ASCellNode applyLayoutAttributes:] to supplementary nodes * Ensure _ASCollectionReusableView has layoutAttributes if the collection view doesn’t set them * Use correct layoutAttributes accessor for supplementary elements
1 parent 5ef1d2b commit 3164d8d

File tree

5 files changed

+148
-4
lines changed

5 files changed

+148
-4
lines changed

AsyncDisplayKit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */; };
9494
34EFC7731B701D0700AD841F /* ASAbsoluteLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED181B17843500DA7C62 /* ASAbsoluteLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
9595
34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED191B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm */; };
96+
3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; };
97+
3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; };
9698
3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; };
9799
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; };
98100
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; };
@@ -547,6 +549,8 @@
547549
299DA1A71A828D2900162D41 /* ASBatchContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchContext.h; sourceTree = "<group>"; };
548550
299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = "<group>"; };
549551
29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASBasicImageDownloaderContextTests.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
552+
3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = "<group>"; };
553+
3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = "<group>"; };
550554
3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
551555
464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = "<group>"; };
552556
4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = "<group>"; };
@@ -1089,6 +1093,8 @@
10891093
children = (
10901094
CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */,
10911095
CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */,
1096+
3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */,
1097+
3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */,
10921098
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
10931099
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
10941100
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
@@ -1452,6 +1458,7 @@
14521458
E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */,
14531459
696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */,
14541460
690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */,
1461+
3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */,
14551462
690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */,
14561463
683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */,
14571464
698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */,
@@ -1882,6 +1889,7 @@
18821889
DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */,
18831890
9F98C0261DBE29E000476D92 /* ASControlTargetAction.m in Sources */,
18841891
9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */,
1892+
3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */,
18851893
8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */,
18861894
B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */,
18871895
690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */,

Source/ASCollectionView.mm

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#import <AsyncDisplayKit/ASRangeController.h>
2828
#import <AsyncDisplayKit/_ASCollectionViewCell.h>
2929
#import <AsyncDisplayKit/_ASDisplayLayer.h>
30+
#import <AsyncDisplayKit/_ASCollectionReusableView.h>
3031
#import <AsyncDisplayKit/ASPagerNode.h>
3132
#import <AsyncDisplayKit/ASSectionContext.h>
3233
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
@@ -838,7 +839,7 @@ - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind
838839
{
839840
ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration");
840841
[_registeredSupplementaryKinds addObject:elementKind];
841-
[self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier];
842+
[self registerClass:[_ASCollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier];
842843
}
843844

844845
- (void)insertSections:(NSIndexSet *)sections
@@ -997,7 +998,11 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
997998
ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self);
998999
view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
9991000
}
1000-
1001+
1002+
if (_ASCollectionReusableView *reusableView = ASDynamicCast(view, _ASCollectionReusableView)) {
1003+
reusableView.node = node;
1004+
}
1005+
10011006
if (node) {
10021007
[_rangeController configureContentView:view forCellNode:node];
10031008
}
@@ -1111,8 +1116,14 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(
11111116
cell.layoutAttributes = nil;
11121117
}
11131118

1114-
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
1119+
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(_ASCollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
11151120
{
1121+
// This is a safeguard similar to the behavior for cells in -[ASCollectionView collectionView:willDisplayCell:forItemAtIndexPath:]
1122+
// It ensures _ASCollectionReusableView receives layoutAttributes and calls applyLayoutAttributes.
1123+
if (view.layoutAttributes == nil) {
1124+
view.layoutAttributes = [collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath];
1125+
}
1126+
11161127
if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) {
11171128
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
11181129
ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// _ASCollectionReusableView.h
3+
// AsyncDisplayKit
4+
//
5+
// Created by Phil Larson on 4/10/17.
6+
// Copyright © 2017 Facebook. All rights reserved.
7+
//
8+
9+
#import <UIKit/UIKit.h>
10+
#import <AsyncDisplayKit/ASBaseDefines.h>
11+
12+
@class ASCellNode;
13+
14+
NS_ASSUME_NONNULL_BEGIN
15+
16+
AS_SUBCLASSING_RESTRICTED
17+
@interface _ASCollectionReusableView : UICollectionReusableView
18+
@property (nonatomic, weak) ASCellNode *node;
19+
@property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *layoutAttributes;
20+
@end
21+
22+
NS_ASSUME_NONNULL_END
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//
2+
// _ASCollectionReusableView.m
3+
// AsyncDisplayKit
4+
//
5+
// Created by Phil Larson on 4/10/17.
6+
// Copyright © 2017 Facebook. All rights reserved.
7+
//
8+
9+
#import "_ASCollectionReusableView.h"
10+
#import "ASCellNode+Internal.h"
11+
#import <AsyncDisplayKit/AsyncDisplayKit.h>
12+
13+
@implementation _ASCollectionReusableView
14+
15+
- (void)setNode:(ASCellNode *)node
16+
{
17+
ASDisplayNodeAssertMainThread();
18+
node.layoutAttributes = _layoutAttributes;
19+
_node = node;
20+
}
21+
22+
- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
23+
{
24+
_layoutAttributes = layoutAttributes;
25+
_node.layoutAttributes = layoutAttributes;
26+
}
27+
28+
- (void)prepareForReuse
29+
{
30+
self.layoutAttributes = nil;
31+
32+
// Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells
33+
self.node = nil;
34+
[super prepareForReuse];
35+
}
36+
37+
/**
38+
* In the initial case, this is called by UICollectionView during cell dequeueing, before
39+
* we get a chance to assign a node to it, so we must be sure to set these layout attributes
40+
* on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already
41+
* have our node assigned e.g. during a layout update for existing cells, we also attempt
42+
* to update it now.
43+
*/
44+
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
45+
{
46+
self.layoutAttributes = layoutAttributes;
47+
}
48+
49+
/**
50+
* Keep our node filling our content view.
51+
*/
52+
- (void)layoutSubviews
53+
{
54+
[super layoutSubviews];
55+
self.node.frame = self.bounds;
56+
}
57+
58+
@end
59+
60+
/**
61+
* A category that makes _ASCollectionReusableView conform to IGListBindable.
62+
*
63+
* We don't need to do anything to bind the view model – the cell node
64+
* serves the same purpose.
65+
*/
66+
#if __has_include(<IGListKit/IGListBindable.h>)
67+
68+
#import <IGListKit/IGListBindable.h>
69+
70+
@interface _ASCollectionReusableView (IGListBindable) <IGListBindable>
71+
@end
72+
73+
@implementation _ASCollectionReusableView (IGListBindable)
74+
75+
- (void)bindViewModel:(id)viewModel
76+
{
77+
// nop
78+
}
79+
80+
@end
81+
82+
#endif

Tests/ASCollectionViewTests.mm

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection
114114

115115
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
116116
{
117-
return [[ASCellNode alloc] init];
117+
return [[ASTextCellNodeWithSetSelectedCounter alloc] init];
118118
}
119119

120120
- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context
@@ -487,12 +487,24 @@ - (void)testCellNodeLayoutAttributes
487487
updateValidationTestPrologue
488488
NSSet *nodeBatch1 = [NSSet setWithArray:[cn visibleNodes]];
489489
XCTAssertGreaterThan(nodeBatch1.count, 0);
490+
491+
NSArray<UICollectionViewLayoutAttributes *> *visibleLayoutAttributesBatch1 = [cv.collectionViewLayout layoutAttributesForElementsInRect:cv.bounds];
492+
XCTAssertGreaterThan(visibleLayoutAttributesBatch1.count, 0);
490493

491494
// Expect all visible nodes get 1 applyLayoutAttributes and have a non-nil value.
492495
for (ASTextCellNodeWithSetSelectedCounter *node in nodeBatch1) {
493496
XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible nodes.");
494497
XCTAssertNotNil(node.layoutAttributes, @"Expected layoutAttributes to be non-nil for visible cell node.");
495498
}
499+
500+
for (UICollectionViewLayoutAttributes *layoutAttributes in visibleLayoutAttributesBatch1) {
501+
if (layoutAttributes.representedElementCategory != UICollectionElementCategorySupplementaryView) {
502+
continue;
503+
}
504+
ASTextCellNodeWithSetSelectedCounter *node = (ASTextCellNodeWithSetSelectedCounter *)[cv supplementaryNodeForElementKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath];
505+
XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible supplementary nodes.");
506+
XCTAssertNotNil(node.layoutAttributes, @"Expected layoutAttributes to be non-nil for visible supplementary node.");
507+
}
496508

497509
// Scroll to next batch of items.
498510
NSIndexPath *nextIP = [NSIndexPath indexPathForItem:nodeBatch1.count inSection:0];
@@ -508,6 +520,15 @@ - (void)testCellNodeLayoutAttributes
508520
XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible nodes, even after node is removed.");
509521
XCTAssertNil(node.layoutAttributes, @"Expected layoutAttributes to be nil for removed cell node.");
510522
}
523+
524+
for (UICollectionViewLayoutAttributes *layoutAttributes in visibleLayoutAttributesBatch1) {
525+
if (layoutAttributes.representedElementCategory != UICollectionElementCategorySupplementaryView) {
526+
continue;
527+
}
528+
ASTextCellNodeWithSetSelectedCounter *node = (ASTextCellNodeWithSetSelectedCounter *)[cv supplementaryNodeForElementKind:layoutAttributes.representedElementKind atIndexPath:layoutAttributes.indexPath];
529+
XCTAssertEqual(node.applyLayoutAttributesCount, 1, @"Expected applyLayoutAttributes to be called exactly once for visible supplementary nodes, even after node is removed.");
530+
XCTAssertNil(node.layoutAttributes, @"Expected layoutAttributes to be nil for removed supplementary node.");
531+
}
511532
}
512533

513534
- (void)testCellNodeIndexPathConsistency

0 commit comments

Comments
 (0)