Further implement data controller support and layout introspection

This commit is contained in:
Levi McCallum
2015-09-29 08:59:40 -07:00
committed by Levi McCallum
parent 658b78d552
commit da7a2a5d48
9 changed files with 121 additions and 20 deletions

View File

@@ -17,7 +17,7 @@
@class ASCellNode; @class ASCellNode;
@protocol ASCollectionViewDataSource; @protocol ASCollectionViewDataSource;
@protocol ASCollectionViewDelegate; @protocol ASCollectionViewDelegate;
@protocol ASCollectionViewLayoutInspecting;
/** /**
* Node-based collection view. * Node-based collection view.
@@ -80,6 +80,13 @@
*/ */
@property (nonatomic, assign) CGFloat leadingScreensForBatching; @property (nonatomic, assign) CGFloat leadingScreensForBatching;
/**
* Optional introspection object for the collection view's layout.
*
* TODO: Discuss more about this delegate
*/
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutDelegate;
/** /**
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. * 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. * The asyncDataSource must be updated to reflect the changes before the update block completes.
@@ -119,7 +126,7 @@
*/ */
- (void)reloadData; - (void)reloadData;
- (void)registerSupplementaryViewOfKind:(NSString *)elementKind; - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind;
/** /**
* Inserts one or more sections. * Inserts one or more sections.

View File

@@ -16,6 +16,7 @@
#import "ASBatchFetching.h" #import "ASBatchFetching.h"
#import "UICollectionViewLayout+ASConvenience.h" #import "UICollectionViewLayout+ASConvenience.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASCollectionViewFlowLayoutInspector.h"
// FIXME: Temporary nonsense import until method names are finalized and exposed // FIXME: Temporary nonsense import until method names are finalized and exposed
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
@@ -137,6 +138,7 @@ static BOOL _isInterceptedSelector(SEL sel)
ASCollectionDataController *_dataController; ASCollectionDataController *_dataController;
ASRangeController *_rangeController; ASRangeController *_rangeController;
ASCollectionViewLayoutController *_layoutController; ASCollectionViewLayoutController *_layoutController;
ASCollectionViewFlowLayoutInspector *_flowLayoutInspector;
BOOL _performingBatchUpdates; BOOL _performingBatchUpdates;
NSMutableArray *_batchUpdateBlocks; NSMutableArray *_batchUpdateBlocks;
@@ -202,7 +204,11 @@ static BOOL _isInterceptedSelector(SEL sel)
_dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; _dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled];
_dataController.delegate = _rangeController; _dataController.delegate = _rangeController;
_dataController.dataSource = self; _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]; _batchContext = [[ASBatchContext alloc] init];
_leadingScreensForBatching = 1.0; _leadingScreensForBatching = 1.0;
@@ -377,7 +383,7 @@ static BOOL _isInterceptedSelector(SEL sel)
[self performBatchAnimated:YES updates:updates completion:completion]; [self performBatchAnimated:YES updates:updates completion:completion];
} }
- (void)registerSupplementaryViewOfKind:(NSString *)elementKind - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind
{ {
[self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind [self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind
withReuseIdentifier:[self __reuseIdentifierForKind:elementKind]]; withReuseIdentifier:[self __reuseIdentifierForKind:elementKind]];
@@ -677,6 +683,11 @@ static BOOL _isInterceptedSelector(SEL sel)
return constrainedSize; 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 - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
{ {
return [_asyncDataSource collectionView:self numberOfItemsInSection:section]; return [_asyncDataSource collectionView:self numberOfItemsInSection:section];

View File

@@ -17,9 +17,14 @@
@protocol ASCollectionDataControllerSource <ASDataControllerSource> @protocol ASCollectionDataControllerSource <ASDataControllerSource>
- (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; - (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryKind:(NSString *)kind;

View File

@@ -14,6 +14,12 @@
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASDataController+Subclasses.h" #import "ASDataController+Subclasses.h"
@interface ASCollectionDataController ()
- (id<ASCollectionDataControllerSource>)collectionDataSource;
@end
@implementation ASCollectionDataController { @implementation ASCollectionDataController {
NSMutableDictionary *_completedSupplementaryNodes; NSMutableDictionary *_completedSupplementaryNodes;
NSMutableDictionary *_editingSupplementaryNodes; NSMutableDictionary *_editingSupplementaryNodes;
@@ -24,21 +30,23 @@
[self performEditCommandWithBlock:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[self accessDataSourceWithBlock:^{ [self accessDataSourceWithBlock:^{
NSArray *elementKinds = [self.collectionDataSource supplementaryKindsInDataController:self]; NSArray *elementKinds = [self.collectionDataSource supplementaryNodeKindsInDataController:self];
[elementKinds enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL * _Nonnull stop) { [elementKinds enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL * _Nonnull stop) {
_completedSupplementaryNodes[kind] = [NSMutableArray array]; _completedSupplementaryNodes[kind] = [NSMutableArray array];
_editingSupplementaryNodes[kind] = [NSMutableArray array]; _editingSupplementaryNodes[kind] = [NSMutableArray array];
NSMutableArray *indexPaths = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array];
NSMutableArray *nodes = [NSMutableArray array]; NSMutableArray *nodes = [NSMutableArray array];
[self _populateAllNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths];
[self batchLayoutNodes:nodes atIndexPaths:indexPaths completion:nil]; [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]; NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryKind:kind];
for (NSUInteger i = 0; i < sectionCount; i++) { for (NSUInteger i = 0; i < sectionCount; i++) {
@@ -65,6 +73,7 @@
#pragma mark - Internal Data Querying #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 - (void)_insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths
{ {
if (indexPaths.count == 0) 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 - (void)_deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths
{ {
if (indexPaths.count == 0) if (indexPaths.count == 0)

View File

@@ -0,0 +1,31 @@
//
// ASCollectionViewFlowLayoutInspector.h
// Pods
//
// Created by Levi McCallum on 9/29/15.
//
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASDimension.h>
@class ASCollectionView;
@protocol ASCollectionViewLayoutInspecting <NSObject>
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryViewsOfKind:(NSString *)kind inSection:(NSUInteger)section;
@end
@interface ASCollectionViewFlowLayoutInspector : NSObject <ASCollectionViewLayoutInspecting>
@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

View File

@@ -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

View File

@@ -25,6 +25,9 @@
*/ */
- (void)accessDataSourceWithBlock:(dispatch_block_t)block; - (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 @end

View File

@@ -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"); 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++) { for (NSUInteger k = j; k < j + batchCount; k++) {
ASCellNode *node = nodes[k]; ASCellNode *node = nodes[k];
if (!node.isNodeLoaded) { 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); dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER);
free(nodeBoundSizes); 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; NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
@@ -164,14 +164,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange];
NSArray *batchedNodes = [nodes 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 - (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self batchLayoutNodes:nodes atIndexPaths:indexPaths constrainedSize:^ASSizeRange(NSIndexPath *indexPath) {
[self batchLayoutNodes:nodes atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
} completion:^(NSArray *nodes, NSArray *indexPaths) {
// Insert finished nodes into data storage // Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}]; }];

View File

@@ -39,8 +39,8 @@
_collectionView.asyncDelegate = self; _collectionView.asyncDelegate = self;
_collectionView.backgroundColor = [UIColor whiteColor]; _collectionView.backgroundColor = [UIColor whiteColor];
[_collectionView registerSupplementaryViewOfKind:UICollectionElementKindSectionHeader]; [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
[_collectionView registerSupplementaryViewOfKind:UICollectionElementKindSectionFooter]; [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
return self; return self;
} }