From a13b36d63fd66160eb7ced1e80d5516114e1ff95 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 8 Feb 2017 22:03:13 -0800 Subject: [PATCH] Allow UICollectionViewLayout to Provide Layout Inspector, Always Update Delegate (#3003) * Allow UICollectionViewLayout to give us a layout inspector, always call the didChangeDelegate/didChangeDataSource on binding * Make an assertion * Update the tests * Tests use actual layout inspector * Be more consistent --- AsyncDisplayKit/ASCollectionView.mm | 20 +++++++------- .../Details/ASCollectionViewLayoutInspector.h | 5 ++-- .../Details/ASCollectionViewLayoutInspector.m | 6 +---- .../UICollectionViewLayout+ASConvenience.h | 14 +++++++--- .../UICollectionViewLayout+ASConvenience.m | 15 ++++++++--- .../ASCollectionViewFlowLayoutInspector.h | 2 +- .../ASCollectionViewFlowLayoutInspector.m | 4 +-- ...ASCollectionViewFlowLayoutInspectorTests.m | 26 +++++++++---------- 8 files changed, 53 insertions(+), 39 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 011dd3bfd8..2103d7b3f0 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -533,19 +533,14 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - (id)layoutInspector { if (_layoutInspector == nil) { - UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + UICollectionViewLayout *layout = self.collectionViewLayout; if (layout == nil) { // Layout hasn't been set yet, we're still init'ing return nil; } - - if ([layout asdk_isFlowLayout]) { - // Register the default layout inspector delegate for flow layouts only - _defaultLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout]; - } else { - // Register the default layout inspector delegate for custom collection view layouts - _defaultLayoutInspector = [[ASCollectionViewLayoutInspector alloc] initWithCollectionView:self]; - } + + _defaultLayoutInspector = [layout asdk_layoutInspector]; + ASDisplayNodeAssertNotNil(_defaultLayoutInspector, @"You must not return nil from -asdk_layoutInspector. Return [super asdk_layoutInspector] if you have to! Layout: %@", layout); // Explicitly call the setter to wire up the _layoutInspectorFlags self.layoutInspector = _defaultLayoutInspector; @@ -560,6 +555,13 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; _layoutInspectorFlags.didChangeCollectionViewDataSource = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDataSource:)]; _layoutInspectorFlags.didChangeCollectionViewDelegate = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDelegate:)]; + + if (_layoutInspectorFlags.didChangeCollectionViewDataSource) { + [_layoutInspector didChangeCollectionViewDataSource:self.asyncDataSource]; + } + if (_layoutInspectorFlags.didChangeCollectionViewDelegate) { + [_layoutInspector didChangeCollectionViewDelegate:self.asyncDelegate]; + } } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h index 5b8c901218..62476e95d1 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.h @@ -72,11 +72,12 @@ extern ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *colle * A layout inspector for non-flow layouts that returns a constrained size to let the cells layout itself as * far as possible based on the scrollable direction of the collection view. It throws exceptions for delegate * methods that are related to supplementary node's management. + * + * @warning This class is not meant to be subclassed and will be restricted in the future. */ @interface ASCollectionViewLayoutInspector : NSObject -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Use -init instead."); @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m index 395c850504..777b856930 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutInspector.m @@ -39,11 +39,7 @@ ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionVi - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView { - self = [super init]; - if (self != nil) { - [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; - } - return self; + return [self init]; } #pragma mark ASCollectionViewLayoutInspecting diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h index 461c0966fd..2b1c4b28f3 100644 --- a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h +++ b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h @@ -8,13 +8,21 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import + +@protocol ASCollectionViewLayoutInspecting; NS_ASSUME_NONNULL_BEGIN -@interface UICollectionViewLayout (ASConvenience) +@interface UICollectionViewLayout (ASLayoutInspectorProviding) -- (BOOL)asdk_isFlowLayout; +/** + * You can override this method on your @c UICollectionViewLayout subclass to + * return a layout inspector tailored to your layout. + * + * It's fine to return @c self. You must not return @c nil. + */ +- (id)asdk_layoutInspector; @end diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m index ebf0e16a28..ef0ce990fc 100644 --- a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m +++ b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.m @@ -10,11 +10,20 @@ #import -@implementation UICollectionViewLayout (ASConvenience) +#import -- (BOOL)asdk_isFlowLayout +#import + +@implementation UICollectionViewLayout (ASLayoutInspectorProviding) + +- (id)asdk_layoutInspector { - return [self isKindOfClass:[UICollectionViewFlowLayout class]]; + UICollectionViewFlowLayout *flow = ASDynamicCast(self, UICollectionViewFlowLayout); + if (flow != nil) { + return [[ASCollectionViewFlowLayoutInspector alloc] initWithFlowLayout:flow]; + } else { + return [[ASCollectionViewLayoutInspector alloc] init]; + } } @end diff --git a/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.h index 58a5bec2e5..8505d3d7bd 100644 --- a/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.h @@ -24,7 +24,7 @@ AS_SUBCLASSING_RESTRICTED @property (nonatomic, weak, readonly) UICollectionViewFlowLayout *layout; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; @end diff --git a/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.m index c56033070b..2ee8159da6 100644 --- a/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Private/ASCollectionViewFlowLayoutInspector.m @@ -36,14 +36,12 @@ #pragma mark Lifecycle -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; { - NSParameterAssert(collectionView); NSParameterAssert(flowLayout); self = [super init]; if (self != nil) { - [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; _layout = flowLayout; } return self; diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index d3d0310a61..3dd8d76885 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -143,7 +143,7 @@ collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); @@ -166,7 +166,7 @@ collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation"); @@ -189,7 +189,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); @@ -210,7 +210,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(collectionView.bounds.size.width, 125.0)); ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); @@ -234,7 +234,7 @@ collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height)); ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation"); @@ -256,7 +256,7 @@ collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height)); ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the values returned in the delegate implementation"); @@ -279,7 +279,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.width)); ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); @@ -300,7 +300,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:rect collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeMake(125.0, collectionView.bounds.size.height)); ASXCTAssertEqualSizeRanges(size, sizeCompare, @"should have a size constrained by the size set on the layout"); @@ -317,7 +317,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); ASSizeRange size = [inspector collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; ASSizeRange sizeCompare = ASSizeRangeMake(CGSizeZero, CGSizeZero); XCTAssert(CGSizeEqualToSize(size.min, sizeCompare.min) && CGSizeEqualToSize(size.max, sizeCompare.max), @"should have a zero size"); @@ -336,7 +336,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0]; XCTAssert(count == 1, @"should have a header supplementary view"); @@ -353,7 +353,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0]; XCTAssert(count == 1, @"should have a footer supplementary view"); @@ -369,7 +369,7 @@ ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; collectionView.asyncDataSource = dataSource; collectionView.asyncDelegate = delegate; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; + ASCollectionViewFlowLayoutInspector *inspector = ASDynamicCast(collectionView.layoutInspector, ASCollectionViewFlowLayoutInspector); NSUInteger count = [inspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionFooter inSection:0]; XCTAssert(count == 0, @"should not have a footer supplementary view"); @@ -393,7 +393,7 @@ id delegate = [InspectorTestDataSourceDelegateWithoutNodeConstrainedSize new]; node.delegate = delegate; - ASCollectionViewLayoutInspector *inspector = [[ASCollectionViewLayoutInspector alloc] initWithCollectionView:collectionView]; + ASCollectionViewLayoutInspector *inspector = [[ASCollectionViewLayoutInspector alloc] init]; collectionView.layoutInspector = inspector; XCTAssertThrows([inspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]);