From da7a2a5d481f18d73a9770e7638c3add6422b7e3 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Tue, 29 Sep 2015 08:59:40 -0700 Subject: [PATCH] Further implement data controller support and layout introspection --- AsyncDisplayKit/ASCollectionView.h | 11 +++++-- AsyncDisplayKit/ASCollectionView.mm | 15 +++++++-- .../Details/ASCollectionDataController.h | 9 +++-- .../Details/ASCollectionDataController.mm | 18 +++++++--- .../ASCollectionViewFlowLayoutInspector.h | 31 +++++++++++++++++ .../ASCollectionViewFlowLayoutInspector.m | 33 +++++++++++++++++++ .../Details/ASDataController+Subclasses.h | 5 ++- AsyncDisplayKit/Details/ASDataController.mm | 15 +++++---- .../ASCollectionView/Sample/ViewController.m | 4 +-- 9 files changed, 121 insertions(+), 20 deletions(-) create mode 100644 AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h create mode 100644 AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 7d3ea56fc9..91226a5d64 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -17,7 +17,7 @@ @class ASCellNode; @protocol ASCollectionViewDataSource; @protocol ASCollectionViewDelegate; - +@protocol ASCollectionViewLayoutInspecting; /** * Node-based collection view. @@ -80,6 +80,13 @@ */ @property (nonatomic, assign) CGFloat leadingScreensForBatching; +/** + * Optional introspection object for the collection view's layout. + * + * TODO: Discuss more about this delegate + */ +@property (nonatomic, weak) id layoutDelegate; + /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. * The asyncDataSource must be updated to reflect the changes before the update block completes. @@ -119,7 +126,7 @@ */ - (void)reloadData; -- (void)registerSupplementaryViewOfKind:(NSString *)elementKind; +- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind; /** * Inserts one or more sections. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 4d52ef5e19..fb9dbeae1a 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -16,6 +16,7 @@ #import "ASBatchFetching.h" #import "UICollectionViewLayout+ASConvenience.h" #import "ASInternalHelpers.h" +#import "ASCollectionViewFlowLayoutInspector.h" // FIXME: Temporary nonsense import until method names are finalized and exposed #import "ASDisplayNode+Subclasses.h" @@ -137,6 +138,7 @@ static BOOL _isInterceptedSelector(SEL sel) ASCollectionDataController *_dataController; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; + ASCollectionViewFlowLayoutInspector *_flowLayoutInspector; BOOL _performingBatchUpdates; NSMutableArray *_batchUpdateBlocks; @@ -202,7 +204,11 @@ static BOOL _isInterceptedSelector(SEL sel) _dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; _dataController.delegate = _rangeController; _dataController.dataSource = self; - + + _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] init]; + // TODO: Implement a better path of falling-back to a flow layout + _flowLayoutInspector.layout = (UICollectionViewFlowLayout *)layout; + _batchContext = [[ASBatchContext alloc] init]; _leadingScreensForBatching = 1.0; @@ -377,7 +383,7 @@ static BOOL _isInterceptedSelector(SEL sel) [self performBatchAnimated:YES updates:updates completion:completion]; } -- (void)registerSupplementaryViewOfKind:(NSString *)elementKind +- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind { [self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:[self __reuseIdentifierForKind:elementKind]]; @@ -677,6 +683,11 @@ static BOOL _isInterceptedSelector(SEL sel) return constrainedSize; } +- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + return [self.layoutDelegate collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; +} + - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section { return [_asyncDataSource collectionView:self numberOfItemsInSection:section]; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index c938955608..fddab0eb46 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -17,9 +17,14 @@ @protocol ASCollectionDataControllerSource -- (ASDisplayNode *)dataController:(ASDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; +- (ASDisplayNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; -- (NSArray *)supplementaryKindsInDataController:(ASCollectionDataController *)dataController; +/** + The constrained size range for layout. + */ +- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; - (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryKind:(NSString *)kind; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 8863d0b3b9..243b1b4612 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -14,6 +14,12 @@ #import "ASDisplayNodeInternal.h" #import "ASDataController+Subclasses.h" +@interface ASCollectionDataController () + +- (id)collectionDataSource; + +@end + @implementation ASCollectionDataController { NSMutableDictionary *_completedSupplementaryNodes; NSMutableDictionary *_editingSupplementaryNodes; @@ -24,21 +30,23 @@ [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); [self accessDataSourceWithBlock:^{ - NSArray *elementKinds = [self.collectionDataSource supplementaryKindsInDataController:self]; + NSArray *elementKinds = [self.collectionDataSource supplementaryNodeKindsInDataController:self]; [elementKinds enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL * _Nonnull stop) { _completedSupplementaryNodes[kind] = [NSMutableArray array]; _editingSupplementaryNodes[kind] = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array]; NSMutableArray *nodes = [NSMutableArray array]; - [self _populateAllNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; - [self batchLayoutNodes:nodes atIndexPaths:indexPaths completion:nil]; + [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; + [self batchLayoutNodes:nodes atIndexPaths:indexPaths constrainedSize:^ASSizeRange(NSIndexPath *indexPath) { + return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; + } completion:nil]; }]; }]; }]; } -- (void)_populateAllNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths +- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths { NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryKind:kind]; for (NSUInteger i = 0; i < sectionCount; i++) { @@ -65,6 +73,7 @@ #pragma mark - Internal Data Querying +// TODO: Reduce code duplication by exposing generic insert/delete helpers from ASDataController - (void)_insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) @@ -78,6 +87,7 @@ }); } +// TODO: Reduce code duplication by exposing generic insert/delete helpers from ASDataController - (void)_deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { if (indexPaths.count == 0) diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h new file mode 100644 index 0000000000..911a000e5c --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -0,0 +1,31 @@ +// +// ASCollectionViewFlowLayoutInspector.h +// Pods +// +// Created by Levi McCallum on 9/29/15. +// +// + +#import + +#import + +@class ASCollectionView; + +@protocol ASCollectionViewLayoutInspecting + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryViewsOfKind:(NSString *)kind inSection:(NSUInteger)section; + +@end + +@interface ASCollectionViewFlowLayoutInspector : NSObject + +@property (nonatomic, weak) UICollectionViewFlowLayout *layout; + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryViewsOfKind:(NSString *)kind inSection:(NSUInteger)section; + +@end diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m new file mode 100644 index 0000000000..274f576c61 --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -0,0 +1,33 @@ +// +// ASCollectionViewFlowLayoutInspector.m +// Pods +// +// Created by Levi McCallum on 9/29/15. +// +// + +#import "ASCollectionViewFlowLayoutInspector.h" + +#import "ASCollectionView.h" + +@implementation ASCollectionViewFlowLayoutInspector + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + // TODO: Implement some heuristic that follows the width/height constraints of header and footer supplementary views + return ASSizeRangeMake(CGSizeZero, CGSizeMake(FLT_MAX, FLT_MAX)); +} + +- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryViewsOfKind:(NSString *)kind inSection:(NSUInteger)section +{ + NSUInteger count = 0; + if (self.layout.headerReferenceSize.width > 0 || self.layout.headerReferenceSize.height > 0) { + count++; + } + if (self.layout.footerReferenceSize.width > 0 || self.layout.footerReferenceSize.height > 0) { + count++; + } + return count; +} + +@end diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 1e14ccc6eb..e07bb5d031 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -25,6 +25,9 @@ */ - (void)accessDataSourceWithBlock:(dispatch_block_t)block; -- (void)batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))block; +/** + * Measure and layout the given nodes in optimized batches, constraining each to a given size. + */ +- (void)batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths constrainedSize:(ASSizeRange (^)(NSIndexPath *indexPath))constraintedSizeBlock completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; @end diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index ed79598d55..844e61706b 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -113,7 +113,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)_layoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *, NSArray *))block +- (void)_layoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths constrainedSize:(ASSizeRange (^)(NSIndexPath *indexPath))constraintedSizeBlock completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"Cell node layout must be initiated from edit transaction queue"); @@ -129,7 +129,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger k = j; k < j + batchCount; k++) { ASCellNode *node = nodes[k]; if (!node.isNodeLoaded) { - nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]]; + nodeBoundSizes[k] = constraintedSizeBlock(indexPaths[k]); } } @@ -151,10 +151,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); free(nodeBoundSizes); - block(nodes, indexPaths); + completionBlock(nodes, indexPaths); } -- (void)batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))block +- (void)batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths constrainedSize:(ASSizeRange (^)(NSIndexPath *indexPath))constraintedSizeBlock completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; @@ -164,14 +164,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes atIndexPaths:batchedIndexPaths completion:block]; + [self _layoutNodes:batchedNodes atIndexPaths:batchedIndexPaths constrainedSize:constraintedSizeBlock completion:completionBlock]; } } - (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - - [self batchLayoutNodes:nodes atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodes:nodes atIndexPaths:indexPaths constrainedSize:^ASSizeRange(NSIndexPath *indexPath) { + return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; + } completion:^(NSArray *nodes, NSArray *indexPaths) { // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index 7e97f9bb53..885ef3e00f 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -39,8 +39,8 @@ _collectionView.asyncDelegate = self; _collectionView.backgroundColor = [UIColor whiteColor]; - [_collectionView registerSupplementaryViewOfKind:UICollectionElementKindSectionHeader]; - [_collectionView registerSupplementaryViewOfKind:UICollectionElementKindSectionFooter]; + [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; + [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; return self; }