Merge pull request #762 from levi/supplementary-views

Respond to supplementary node feedback
This commit is contained in:
appleguy 2015-10-25 19:45:00 -07:00
commit 7d013a70ac
8 changed files with 50 additions and 46 deletions

View File

@ -90,7 +90,7 @@
* collection view layout subclasses will need to provide their own implementation of an inspector object for their * collection view layout subclasses will need to provide their own implementation of an inspector object for their
* supplementary views to be compatible with `ASCollectionView`'s supplementary node support. * supplementary views to be compatible with `ASCollectionView`'s supplementary node support.
*/ */
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutDelegate; @property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
/** /**
* 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.

View File

@ -153,7 +153,7 @@ static BOOL _isInterceptedSelector(SEL sel)
CGSize _maxSizeForNodesConstrainedSize; CGSize _maxSizeForNodesConstrainedSize;
BOOL _ignoreMaxSizeChange; BOOL _ignoreMaxSizeChange;
NSMutableArray *_registeredSupplementaryKinds; NSMutableSet *_registeredSupplementaryKinds;
/** /**
* If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it. * If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it.
@ -229,10 +229,10 @@ static BOOL _isInterceptedSelector(SEL sel)
// Register the default layout inspector delegate for flow layouts only, custom layouts // 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 // will need to roll their own ASCollectionViewLayoutInspecting implementation and set a layout delegate
if ([layout asdk_isFlowLayout]) { if ([layout asdk_isFlowLayout]) {
_layoutDelegate = [self flowLayoutInspector]; _layoutInspector = [self flowLayoutInspector];
} }
_registeredSupplementaryKinds = [NSMutableArray array]; _registeredSupplementaryKinds = [NSMutableSet set];
self.backgroundColor = [UIColor whiteColor]; self.backgroundColor = [UIColor whiteColor];
@ -256,6 +256,7 @@ static BOOL _isInterceptedSelector(SEL sel)
{ {
if (_flowLayoutInspector == nil) { if (_flowLayoutInspector == nil) {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout; 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 _flowLayoutInspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:self
flowLayout:layout]; flowLayout:layout];
} }
@ -333,16 +334,7 @@ static BOOL _isInterceptedSelector(SEL sel)
_asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0);
} }
[_flowLayoutInspector cacheSelectorsForCollectionView:self]; [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate];
}
- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout
{
[super setCollectionViewLayout:collectionViewLayout];
if ([collectionViewLayout asdk_isFlowLayout]) {
_flowLayoutInspector = nil;
_layoutDelegate = [self flowLayoutInspector];
}
} }
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
@ -745,25 +737,25 @@ static BOOL _isInterceptedSelector(SEL sel)
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController - (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController
{ {
return _registeredSupplementaryKinds; return [_registeredSupplementaryKinds allObjects];
} }
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath - (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{ {
ASDisplayNodeAssert(_layoutDelegate != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutDelegate collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; return [_layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
} }
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{ {
ASDisplayNodeAssert(_layoutDelegate != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutDelegate collectionView:self supplementaryNodesOfKind:kind inSection:section]; return [_layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section];
} }
- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind; - (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind;
{ {
ASDisplayNodeAssert(_layoutDelegate != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); ASDisplayNodeAssert(_layoutInspector != nil, @"To support supplementary nodes in ASCollectionView, it must have a layoutDelegate for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)");
return [_layoutDelegate collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind]; return [_layoutInspector collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind];
} }
#pragma mark - ASRangeControllerDelegate. #pragma mark - ASRangeControllerDelegate.

View File

@ -61,7 +61,7 @@
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) { for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]]; [sections addObject:[NSMutableArray array]];
} }
[self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil];

View File

@ -11,6 +11,7 @@
#import <AsyncDisplayKit/ASDimension.h> #import <AsyncDisplayKit/ASDimension.h>
@class ASCollectionView; @class ASCollectionView;
@protocol ASCollectionViewDelegate;
@protocol ASCollectionViewLayoutInspecting <NSObject> @protocol ASCollectionViewLayoutInspecting <NSObject>
@ -29,6 +30,13 @@
*/ */
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
/**
* Allow the inspector to respond to delegate changes.
*
* @discussion A great time to update perform selector caches!
*/
- (void)didChangeCollectionViewDelegate:(id<ASCollectionViewDelegate>)delegate;
@end @end
@interface ASCollectionViewFlowLayoutInspector : NSObject <ASCollectionViewLayoutInspecting> @interface ASCollectionViewFlowLayoutInspector : NSObject <ASCollectionViewLayoutInspecting>
@ -37,6 +45,4 @@
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout;
- (void)cacheSelectorsForCollectionView:(ASCollectionView *)collectionView;
@end @end

View File

@ -11,6 +11,8 @@
#import "ASCollectionViewFlowLayoutInspector.h" #import "ASCollectionViewFlowLayoutInspector.h"
#import "ASCollectionView.h" #import "ASCollectionView.h"
#import "ASAssert.h"
#import "ASEqualityHelpers.h"
@implementation ASCollectionViewFlowLayoutInspector { @implementation ASCollectionViewFlowLayoutInspector {
BOOL _delegateImplementsReferenceSizeForHeader; BOOL _delegateImplementsReferenceSizeForHeader;
@ -24,24 +26,24 @@
self = [super init]; self = [super init];
if (flowLayout == nil) { if (flowLayout == nil) {
return nil; ASDisplayNodeAssert(NO, @"Should never create a layout inspector without a layout");
} }
if (self != nil) { if (self != nil) {
[self cacheSelectorsForCollectionView:collectionView]; [self didChangeCollectionViewDelegate:collectionView.asyncDelegate];
_layout = flowLayout; _layout = flowLayout;
} }
return self; return self;
} }
- (void)cacheSelectorsForCollectionView:(ASCollectionView *)collectionView - (void)didChangeCollectionViewDelegate:(id<ASCollectionViewDelegate>)delegate;
{ {
if (collectionView == nil) { if (delegate == nil) {
_delegateImplementsReferenceSizeForHeader = nil; _delegateImplementsReferenceSizeForHeader = NO;
_delegateImplementsReferenceSizeForFooter = nil; _delegateImplementsReferenceSizeForFooter = NO;
} else { } else {
_delegateImplementsReferenceSizeForHeader = [[self delegateForCollectionView:collectionView] respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; _delegateImplementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)];
_delegateImplementsReferenceSizeForFooter = [[self delegateForCollectionView:collectionView] respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; _delegateImplementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)];
} }
} }
@ -77,13 +79,13 @@
- (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView - (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView
{ {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) {
if (_delegateImplementsReferenceSizeForHeader) { if (_delegateImplementsReferenceSizeForHeader) {
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section]; return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section];
} else { } else {
return [self.layout headerReferenceSize]; return [self.layout headerReferenceSize];
} }
} else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { } else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) {
if (_delegateImplementsReferenceSizeForFooter) { if (_delegateImplementsReferenceSizeForFooter) {
return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section]; return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section];
} else { } else {

View File

@ -74,8 +74,8 @@
/** /**
* Notifies the subclass to perform any work needed before the data controller is reloaded entirely * Notifies the subclass to perform any work needed before the data controller is reloaded entirely
* *
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main * @discussion This method will be performed before the data controller enters its editing queue.
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread. * data stores before entering into editing the backing store on a background thread.
*/ */
- (void)prepareForReloadData; - (void)prepareForReloadData;
@ -92,8 +92,8 @@
/** /**
* Notifies the subclass to perform setup before sections are inserted in the data controller * Notifies the subclass to perform setup before sections are inserted in the data controller
* *
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main * @discussion This method will be performed before the data controller enters its editing queue.
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread. * data stores before entering into editing the backing store on a background thread.
* *
* @param sections Indices of sections to be inserted * @param sections Indices of sections to be inserted

View File

@ -15,6 +15,7 @@
#import "ASDisplayNode.h" #import "ASDisplayNode.h"
#import "ASMultidimensionalArrayUtils.h" #import "ASMultidimensionalArrayUtils.h"
#import "ASDisplayNodeInternal.h" #import "ASDisplayNodeInternal.h"
#import "ASLayout.h"
//#define LOG(...) NSLog(__VA_ARGS__) //#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...) #define LOG(...)
@ -185,8 +186,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER);
free(nodeBoundSizes); free(nodeBoundSizes);
if (completionBlock) if (completionBlock) {
completionBlock(nodes, indexPaths); completionBlock(nodes, indexPaths);
}
} }
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
@ -218,8 +220,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock - (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{ {
if (indexPaths.count == 0) if (indexPaths.count == 0) {
return; return;
}
LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind]));
NSMutableArray *editingNodes = _editingNodes[kind]; NSMutableArray *editingNodes = _editingNodes[kind];
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
@ -818,8 +822,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) { [section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
[node measureWithSizeRange:constrainedSize]; ASLayout *layout = [node measureWithSizeRange:constrainedSize];
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); node.frame = CGRectMake(0.0f, 0.0f, layout.size.width, layout.size.height);
}]; }];
}]; }];
}]; }];
@ -849,7 +853,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind - (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind
{ {
return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : [NSArray array]; return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil;
} }
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind - (NSMutableArray *)editingNodesOfKind:(NSString *)kind

View File

@ -91,15 +91,15 @@
{ {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
XCTAssert(collectionView.layoutDelegate != nil, @"should automatically set a layout delegate for flow layouts"); XCTAssert(collectionView.layoutInspector != nil, @"should automatically set a layout delegate for flow layouts");
XCTAssert([collectionView.layoutDelegate isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default"); XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default");
} }
- (void)testThatItDoesNotSetALayoutInspectorForCustomLayouts - (void)testThatItDoesNotSetALayoutInspectorForCustomLayouts
{ {
UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init];
ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
XCTAssert(collectionView.layoutDelegate == nil, @"should not set a layout delegate for custom layouts"); XCTAssert(collectionView.layoutInspector == nil, @"should not set a layout delegate for custom layouts");
} }
- (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection - (void)testThatRegisteringASupplementaryNodeStoresItForIntrospection