From 9fb3129a0ef53b08b8412313ec4f2baf4d6e0945 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 17 Jun 2016 13:26:05 -0700 Subject: [PATCH] Add default ASCollectionViewLayoutInspecting for custom ASCollectionViewLayout --- AsyncDisplayKit/ASCollectionView.mm | 2 +- .../ASCollectionViewFlowLayoutInspector.h | 13 ++-- .../ASCollectionViewFlowLayoutInspector.m | 68 ++++++++++++++----- AsyncDisplayKitTests/ASCollectionViewTests.m | 8 +-- 4 files changed, 64 insertions(+), 27 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 0d29f7335d..1e21c387c3 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -254,7 +254,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; // Custom layouts will need to roll their own ASCollectionViewLayoutInspecting implementation and set a layout // delegate. In the meantime ASDK provides a null layout inspector that does not provide any implementation // and throws an exception for methods that should be implemented in the - _defaultLayoutInspector = [[ASCollectionViewNullLayoutInspector alloc] init]; + _defaultLayoutInspector = [[ASCollectionViewDefaultCustomLayoutInspector alloc] initWithCollectionView:self]; } _layoutInspector = _defaultLayoutInspector; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 697c562d03..6197ca3187 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -60,10 +60,14 @@ NS_ASSUME_NONNULL_BEGIN @end /** - * Simple "Null Object" inspector for non-flow layouts that does throws exceptions if methods are called - * from + * 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. */ -@interface ASCollectionViewNullLayoutInspector : NSObject +@interface ASCollectionViewDefaultCustomLayoutInspector : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView NS_DESIGNATED_INITIALIZER; @end @@ -74,7 +78,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak, readonly) UICollectionViewFlowLayout *layout; -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index 43f62f2d26..e960ec0610 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -15,14 +15,57 @@ #define kDefaultItemSize CGSizeMake(50, 50) -#pragma mark - ASCollectionViewNullLayoutInspector +#pragma mark - Helper Functions -@implementation ASCollectionViewNullLayoutInspector +// Returns a constrained size to let the cells layout itself as far as possible based on the scrollable direction +// of the collection view +static ASSizeRange ASDefaultConstrainedSizeForNodeForCollectionView(ASCollectionView *collectionView) { + CGSize maxSize = collectionView.bounds.size; + if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) { + maxSize.width = FLT_MAX; + } else { + maxSize.height = FLT_MAX; + } + return ASSizeRangeMake(CGSizeZero, maxSize); +} + +#pragma mark - ASCollectionViewDefaultCustomLayoutInspector + +@implementation ASCollectionViewDefaultCustomLayoutInspector { + struct { + unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; + } _dataSourceFlags; +} + +#pragma mark Lifecycle + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView +{ + self = [super init]; + if (self != nil) { + [self didChangeCollectionViewDataSource:collectionView.asyncDataSource]; + } + return self; +} + +#pragma mark ASCollectionViewLayoutInspecting + +- (void)didChangeCollectionViewDataSource:(id)dataSource +{ + if (dataSource == nil) { + memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); + } else { + _dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; + } +} - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - ASDisplayNodeAssert(NO, @"To support a custom collection view layout in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return ASSizeRangeMake(CGSizeZero, CGSizeZero); + if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { + return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + } + + return ASDefaultConstrainedSizeForNodeForCollectionView(collectionView); } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath @@ -64,7 +107,7 @@ } _dataSourceFlags; } -#pragma mark - Accessors +#pragma mark Lifecycle - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; { @@ -80,6 +123,8 @@ return self; } +#pragma mark ASCollectionViewLayoutInspecting + - (void)didChangeCollectionViewDelegate:(id)delegate; { if (delegate == nil) { @@ -100,29 +145,18 @@ } } -#pragma mark - ASCollectionViewLayoutInspecting - - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - // First check if delegate provides a constrained size if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; } - // Check if item size as constrained size is given CGSize itemSize = _layout.itemSize; if (!CGSizeEqualToSize(itemSize, kDefaultItemSize)) { return ASSizeRangeMake(itemSize, itemSize); } - // No constrained size is given try to let the cells layout itself as far as possible based on the scrollable direction - CGSize maxSize = collectionView.bounds.size; - if (ASScrollDirectionContainsHorizontalDirection([collectionView scrollableDirections])) { - maxSize.width = FLT_MAX; - } else { - maxSize.height = FLT_MAX; - } - return ASSizeRangeMake(CGSizeZero, maxSize); + return ASDefaultConstrainedSizeForNodeForCollectionView(collectionView); } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m index 45d70f6643..6de68d4d79 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -124,14 +124,12 @@ XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default"); } -- (void)testThatItDoesNotSetALayoutInspectorForCustomLayouts +- (void)testThatISetALayoutInspectorForCustomLayouts { UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for custom layouts"); - XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewNullLayoutInspector class]], @"should have a null layout inspector by default if no layout inspector is given for a custom layout"); - XCTAssertThrows([collectionView.layoutInspector collectionView:collectionView constrainedSizeForNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"should throw an exception for methods"); - XCTAssertThrows([collectionView.layoutInspector collectionView:collectionView supplementaryNodesOfKind:UICollectionElementKindSectionHeader inSection:0], @"should throw an exception for methods"); + XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for flow layouts"); + XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewDefaultCustomLayoutInspector class]], @"should have a flow layout inspector by default"); } - (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection