Skip to content

Commit 295ed22

Browse files
author
Adlai Holler
authored
Improve Our Handling of Flow Layout Headers/Footers (facebookarchive#2939)
* Rejigger our flow layout supplementary support to make more sense. * Support old protocol * Update * Update deprecation message * Undeprecate insetForSection method, because it actually _does_ still work * Update the tests * Remove irrelevant junk * Remove cast, add pragma
1 parent 7d68ce3 commit 295ed22

13 files changed

Lines changed: 154 additions & 106 deletions

File tree

AsyncDisplayKit/ASCollectionView.h

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -416,33 +416,71 @@ ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.")
416416
@end
417417

418418
/**
419-
* Defines methods that let you coordinate with a `UICollectionViewFlowLayout` in combination with an `ASCollectionView`.
419+
* Defines methods that let you coordinate a `UICollectionViewFlowLayout` in combination with an `ASCollectionNode`.
420420
*/
421-
@protocol ASCollectionViewDelegateFlowLayout <ASCollectionDelegate>
421+
@protocol ASCollectionDelegateFlowLayout <ASCollectionDelegate>
422422

423423
@optional
424424

425425
/**
426-
* @discussion This method is deprecated and does nothing from 1.9.7 and up
427-
* Previously it applies the section inset to every cells within the corresponding section.
428-
* The expected behavior is to apply the section inset to the whole section rather than
429-
* shrinking each cell individually.
430-
* If you want this behavior, you can integrate your insets calculation into
431-
* `constrainedSizeForNodeAtIndexPath`
432-
* please file a github issue if you would like this to be restored.
426+
* Asks the delegate for the inset that should be applied to the given section.
427+
*
428+
* @see the same method in UICollectionViewDelegate.
429+
*/
430+
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
431+
432+
/**
433+
* Asks the delegate for the size range that should be used to measure the header in the given flow layout section.
434+
*
435+
* @param collectionNode The sender.
436+
* @param section The section.
437+
*
438+
* @return The size range for the header, or @c ASSizeRangeZero if there is no header in this section.
439+
*
440+
* If you want the header to completely determine its own size, return @c ASSizeRangeUnconstrained.
441+
*
442+
* @note Only the scrollable dimension of the returned size range will be used. In a vertical flow,
443+
* only the height will be used. In a horizontal flow, only the width will be used. The other dimension
444+
* will be constrained to fill the collection node.
445+
*
446+
* @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForHeaderInSection:
447+
* and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c headerReferenceSize from the layout.
433448
*/
434-
- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("This method does nothing for 1.9.7+ due to incorrect implementation previously, see the header file for more information.");
449+
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section;
450+
451+
/**
452+
* Asks the delegate for the size range that should be used to measure the footer in the given flow layout section.
453+
*
454+
* @param collectionNode The sender.
455+
* @param section The section.
456+
*
457+
* @return The size range for the footer, or @c ASSizeRangeZero if there is no footer in this section.
458+
*
459+
* If you want the footer to completely determine its own size, return @c ASSizeRangeUnconstrained.
460+
*
461+
* @note Only the scrollable dimension of the returned size range will be used. In a vertical flow,
462+
* only the height will be used. In a horizontal flow, only the width will be used. The other dimension
463+
* will be constrained to fill the collection node.
464+
*
465+
* @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForFooterInSection:
466+
* and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c footerReferenceSize from the layout.
467+
*/
468+
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section;
435469

436470
/**
437471
* Asks the delegate for the size of the header in the specified section.
438472
*/
439-
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
473+
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForHeaderInSection: instead.");
440474

441475
/**
442476
* Asks the delegate for the size of the footer in the specified section.
443477
*/
444-
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
478+
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForFooterInSection: instead.");
479+
480+
@end
445481

482+
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegateFlowLayout.")
483+
@protocol ASCollectionViewDelegateFlowLayout <ASCollectionDelegateFlowLayout>
446484
@end
447485

448486
NS_ASSUME_NONNULL_END

AsyncDisplayKit/ASCollectionView.mm

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ - (void)layoutSubviews
138138
#pragma mark -
139139
#pragma mark ASCollectionView.
140140

141-
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASCollectionDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate, ASCALayerExtendedDelegate> {
141+
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASCollectionDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate, ASCALayerExtendedDelegate, UICollectionViewDelegateFlowLayout> {
142142
ASCollectionViewProxy *_proxyDataSource;
143143
ASCollectionViewProxy *_proxyDelegate;
144144

@@ -927,6 +927,16 @@ - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollection
927927
return [[self nodeForItemAtIndexPath:indexPath] calculatedSize];
928928
}
929929

930+
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
931+
{
932+
return [self supplementaryNodeForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]].calculatedSize;
933+
}
934+
935+
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
936+
{
937+
return [self supplementaryNodeForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]].calculatedSize;
938+
}
939+
930940
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
931941
{
932942
UICollectionReusableView *view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
@@ -936,8 +946,6 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
936946
return view;
937947
}
938948

939-
940-
941949
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
942950
{
943951
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];

AsyncDisplayKit/ASPagerNode.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#import "ASCellNode.h"
1919
#import "ASCollectionView+Undeprecated.h"
2020

21-
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionViewDelegateFlowLayout, ASDelegateProxyInterceptor>
21+
@interface ASPagerNode () <ASCollectionDataSource, ASCollectionDelegate, ASCollectionDelegateFlowLayout, ASDelegateProxyInterceptor>
2222
{
2323
ASPagerFlowLayout *_flowLayout;
2424

AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ @interface ASCollectionViewFlowLayoutInspector ()
2424

2525
@implementation ASCollectionViewFlowLayoutInspector {
2626
struct {
27+
unsigned int implementsSizeRangeForHeader:1;
2728
unsigned int implementsReferenceSizeForHeader:1;
29+
unsigned int implementsSizeRangeForFooter:1;
2830
unsigned int implementsReferenceSizeForFooter:1;
2931
unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1;
3032
unsigned int implementsConstrainedSizeForItemAtIndexPath:1;
@@ -53,7 +55,9 @@ - (void)didChangeCollectionViewDelegate:(id<ASCollectionDelegate>)delegate;
5355
if (delegate == nil) {
5456
memset(&_delegateFlags, 0, sizeof(_delegateFlags));
5557
} else {
58+
_delegateFlags.implementsSizeRangeForHeader = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForHeaderInSection:)];
5659
_delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)];
60+
_delegateFlags.implementsSizeRangeForFooter = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForFooterInSection:)];
5761
_delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)];
5862
_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
5963
_delegateFlags.implementsConstrainedSizeForItemAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)];
@@ -84,65 +88,64 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSize
8488

8589
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
8690
{
87-
CGSize constrainedSize;
88-
CGSize supplementarySize = [self sizeForSupplementaryViewOfKind:kind inSection:indexPath.section collectionView:collectionView];
89-
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
90-
constrainedSize = CGSizeMake(CGRectGetWidth(collectionView.bounds), supplementarySize.height);
91-
} else {
92-
constrainedSize = CGSizeMake(supplementarySize.width, CGRectGetHeight(collectionView.bounds));
93-
}
94-
return ASSizeRangeMake(CGSizeZero, constrainedSize);
95-
}
96-
97-
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
98-
{
99-
return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0;
100-
}
101-
102-
- (ASScrollDirection)scrollableDirections
103-
{
104-
return (self.layout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections;
105-
}
106-
107-
#pragma mark - Private helpers
108-
109-
- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView
110-
{
91+
ASSizeRange result = ASSizeRangeZero;
11192
if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) {
112-
if (_delegateFlags.implementsReferenceSizeForHeader) {
113-
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section];
93+
if (_delegateFlags.implementsSizeRangeForHeader) {
94+
result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForHeaderInSection:indexPath.section];
95+
} else if (_delegateFlags.implementsReferenceSizeForHeader) {
96+
#pragma clang diagnostic push
97+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
98+
CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:indexPath.section];
99+
#pragma clang diagnostic pop
100+
result = ASSizeRangeMake(exactSize);
114101
} else {
115-
return [self.layout headerReferenceSize];
102+
result = ASSizeRangeMake(_layout.headerReferenceSize);
116103
}
117104
} else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) {
118-
if (_delegateFlags.implementsReferenceSizeForFooter) {
119-
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section];
105+
if (_delegateFlags.implementsSizeRangeForFooter) {
106+
result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForFooterInSection:indexPath.section];
107+
} else if (_delegateFlags.implementsReferenceSizeForFooter) {
108+
#pragma clang diagnostic push
109+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
110+
CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:indexPath.section];
111+
#pragma clang diagnostic pop
112+
result = ASSizeRangeMake(exactSize);
120113
} else {
121-
return [self.layout footerReferenceSize];
114+
result = ASSizeRangeMake(_layout.footerReferenceSize);
122115
}
123116
} else {
124-
return CGSizeZero;
117+
ASDisplayNodeFailAssert(@"Unexpected supplementary kind: %@", kind);
118+
return ASSizeRangeZero;
125119
}
126-
}
127120

128-
- (BOOL)layoutHasSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView
129-
{
130-
CGSize size = [self sizeForSupplementaryViewOfKind:kind inSection:section collectionView:collectionView];
131-
return [self usedLayoutValueForSize:size] > 0;
121+
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
122+
result.min.width = result.max.width = CGRectGetWidth(collectionView.bounds);
123+
} else {
124+
result.min.height = result.max.height = CGRectGetHeight(collectionView.bounds);
125+
}
126+
return result;
132127
}
133128

134-
- (CGFloat)usedLayoutValueForSize:(CGSize)size
129+
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
135130
{
131+
ASSizeRange constraint = [self collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
136132
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
137-
return size.height;
133+
return (constraint.max.height > 0 ? 1 : 0);
138134
} else {
139-
return size.width;
135+
return (constraint.max.width > 0 ? 1 : 0);
140136
}
141137
}
142138

143-
- (id<ASCollectionViewDelegateFlowLayout>)delegateForCollectionView:(ASCollectionView *)collectionView
139+
- (ASScrollDirection)scrollableDirections
140+
{
141+
return (self.layout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections;
142+
}
143+
144+
#pragma mark - Private helpers
145+
146+
- (id<ASCollectionDelegateFlowLayout>)delegateForCollectionView:(ASCollectionView *)collectionView
144147
{
145-
return (id<ASCollectionViewDelegateFlowLayout>)collectionView.asyncDelegate;
148+
return (id<ASCollectionDelegateFlowLayout>)collectionView.asyncDelegate;
146149
}
147150

148151
@end

AsyncDisplayKit/Details/ASDelegateProxy.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ - (BOOL)interceptsSelector:(SEL)selector
6868
// handled by ASCollectionView node<->cell machinery
6969
selector == @selector(collectionView:cellForItemAtIndexPath:) ||
7070
selector == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
71+
selector == @selector(collectionView:layout:referenceSizeForHeaderInSection:) ||
72+
selector == @selector(collectionView:layout:referenceSizeForFooterInSection:) ||
7173
selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||
7274

7375
// Selection, highlighting, menu

AsyncDisplayKit/Layout/ASDimension.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ typedef struct {
201201
CGSize max;
202202
} ASSizeRange;
203203

204+
/**
205+
* A size range with all dimensions zero.
206+
*/
207+
extern ASSizeRange const ASSizeRangeZero;
208+
209+
/**
210+
* A size range from zero to infinity in both directions.
211+
*/
212+
extern ASSizeRange const ASSizeRangeUnconstrained;
213+
204214
/**
205215
* Creates an ASSizeRange with provided min and max size.
206216
*/

AsyncDisplayKit/Layout/ASDimension.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension)
6161

6262
#pragma mark - ASSizeRange
6363

64+
ASSizeRange const ASSizeRangeZero = {};
65+
66+
ASSizeRange const ASSizeRangeUnconstrained = { {0, 0}, { INFINITY, INFINITY }};
67+
6468
struct _Range {
6569
CGFloat min;
6670
CGFloat max;

0 commit comments

Comments
 (0)