From 00d9372ee4031d7d7c4666190c0aae9d6c06b6d3 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 17 May 2016 18:39:34 +0200 Subject: [PATCH] Add a "Null Object" for a layout inspector if no no flow layout is given The purpose of the ASCollectionViewCustomLayoutInspector is to provide a default implementation if given a custom layout, but no layout inspector. --- AsyncDisplayKit/ASCollectionView.mm | 63 ++++++++----------- .../ASCollectionViewFlowLayoutInspector.h | 11 ++++ .../ASCollectionViewFlowLayoutInspector.m | 36 ++++++++++- AsyncDisplayKitTests/ASCollectionViewTests.m | 5 +- 4 files changed, 74 insertions(+), 41 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ffb3854014..6586e9504a 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -98,7 +98,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASCollectionDataController *_dataController; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; - ASCollectionViewFlowLayoutInspector *_flowLayoutInspector; + id _defaultLayoutInspector; NSMutableSet *_cellsForVisibilityUpdates; id _layoutFacilitator; @@ -244,11 +244,19 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // and should not trigger a relayout. _ignoreMaxSizeChange = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero); - // Register the default layout inspector delegate for flow layouts only, custom layouts - // will need to roll their own ASCollectionViewLayoutInspecting implementation and set a layout delegate if ([layout asdk_isFlowLayout]) { - _layoutInspector = [self flowLayoutInspector]; + // Register the default layout inspector delegate for flow layouts only + UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; + ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector"); + _defaultLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout]; + } else { + // Custom layouts will need to roll their own ASCollectionViewLayoutInspecting implementation and set a layout + // delegate. In the meantime ASDK provides a custom layout inspector that does not provide any implementation + // and asserts for methods that should be implemented in the + _defaultLayoutInspector = [[ASCollectionViewCustomLayoutInspector alloc] init]; } + _layoutInspector = _defaultLayoutInspector; + _layoutFacilitator = layoutFacilitator; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; @@ -275,19 +283,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [self setAsyncDataSource:nil]; } -/** - * A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts - */ -- (ASCollectionViewFlowLayoutInspector *)flowLayoutInspector -{ - if (_flowLayoutInspector == nil) { - UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; - ASDisplayNodeAssertNotNil(layout, @"Collection view layout must be a flow layout to use the built-in inspector"); - _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self flowLayout:layout]; - } - return _flowLayoutInspector; -} - #pragma mark - #pragma mark Overrides. @@ -405,7 +400,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; super.delegate = (id)_proxyDelegate; - [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; + if ([_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDelegate:)]) { + [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; + } } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -864,28 +861,21 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - ASSizeRange constrainedSize = kInvalidSizeRange; - if (_layoutInspector) { - constrainedSize = [_layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; - } - + ASSizeRange constrainedSize = [_layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; if (!ASSizeRangeEqualToSizeRange(constrainedSize, kInvalidSizeRange)) { return constrainedSize; } - // TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't - // implement a custom inspector. - if (_asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode) { - constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; - } else { - CGSize maxSize = CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero) ? self.bounds.size : _maxSizeForNodesConstrainedSize; - if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { - maxSize.width = FLT_MAX; - } else { - maxSize.height = FLT_MAX; - } - constrainedSize = ASSizeRangeMake(CGSizeZero, maxSize); + CGSize maxSize = _maxSizeForNodesConstrainedSize; + if (CGSizeEqualToSize(_maxSizeForNodesConstrainedSize, CGSizeZero)) { + maxSize = self.bounds.size; } + if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { + maxSize.width = FLT_MAX; + } else { + maxSize.height = FLT_MAX; + } + constrainedSize = ASSizeRangeMake(CGSizeZero, maxSize); return constrainedSize; } @@ -947,19 +937,16 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); return [_layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; } - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section { - ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); return [_layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; } - (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind; { - ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); return [_layoutInspector collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind]; } diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index cd640c3e2a..28fcafa1ab 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -47,6 +47,17 @@ @end +/** + * Simple "Null Object" inspector for non-flow layouts that does not implement a custom inspector, provides a zero + * constrained size and throws an exception if methods are called from + */ +@interface ASCollectionViewCustomLayoutInspector : NSObject + +@end + +/** + * A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts + */ @interface ASCollectionViewFlowLayoutInspector : NSObject @property (nonatomic, weak) UICollectionViewFlowLayout *layout; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index 3fe377f3a3..50f5aa5b35 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -13,6 +13,35 @@ #import "ASAssert.h" #import "ASEqualityHelpers.h" + + +@implementation ASCollectionViewCustomLayoutInspector + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + return ASSizeRangeMake(CGSizeZero, CGSizeZero); +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); + return ASSizeRangeMake(CGSizeZero, CGSizeZero); +} + +- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind +{ + ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); + return 0; +} + +- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section +{ + ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); + return 0; +} + +@end + @implementation ASCollectionViewFlowLayoutInspector { BOOL _delegateImplementsReferenceSizeForHeader; BOOL _delegateImplementsReferenceSizeForFooter; @@ -50,8 +79,11 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - // TODO: Provide constrained size for flow layout item nodes - return ASSizeRangeMake(CGSizeZero, CGSizeZero); + if ([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]) { + return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + } else { + return ASSizeRangeMake(_layout.itemSize, _layout.itemSize); + } } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m index 8c7f258a5a..e08d33a1cf 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -108,7 +108,10 @@ { UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - XCTAssert(collectionView.layoutInspector == nil, @"should not set a layout delegate for custom layouts"); + XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for custom layouts"); + XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewCustomLayoutInspector class]], @"should have a internal custom layout inspector by default"); + XCTAssert(ASSizeRangeEqualToSizeRange([collectionView.layoutInspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], ASSizeRangeMake(CGSizeZero, CGSizeZero)), @"should return a zero constrainted size range in internal custom layout inspector"); + XCTAssertThrows([collectionView.layoutInspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0], @"should throw an exception for methods"); } - (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection