diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 14ec90f894..2b8f902933 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -90,7 +90,7 @@ * 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. */ -@property (nonatomic, weak) id layoutDelegate; +@property (nonatomic, weak) id layoutInspector; /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b312b86c10..bf761b04d7 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -153,7 +153,7 @@ static BOOL _isInterceptedSelector(SEL sel) CGSize _maxSizeForNodesConstrainedSize; 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. @@ -229,10 +229,10 @@ static BOOL _isInterceptedSelector(SEL sel) // 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]) { - _layoutDelegate = [self flowLayoutInspector]; + _layoutInspector = [self flowLayoutInspector]; } - _registeredSupplementaryKinds = [NSMutableArray array]; + _registeredSupplementaryKinds = [NSMutableSet set]; self.backgroundColor = [UIColor whiteColor]; @@ -256,6 +256,7 @@ static BOOL _isInterceptedSelector(SEL sel) { 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]; } @@ -333,16 +334,7 @@ static BOOL _isInterceptedSelector(SEL sel) _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); } - [_flowLayoutInspector cacheSelectorsForCollectionView:self]; -} - -- (void)setCollectionViewLayout:(UICollectionViewLayout *)collectionViewLayout -{ - [super setCollectionViewLayout:collectionViewLayout]; - if ([collectionViewLayout asdk_isFlowLayout]) { - _flowLayoutInspector = nil; - _layoutDelegate = [self flowLayoutInspector]; - } + [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -745,25 +737,25 @@ static BOOL _isInterceptedSelector(SEL sel) - (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController { - return _registeredSupplementaryKinds; + return [_registeredSupplementaryKinds allObjects]; } - (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.)"); - return [_layoutDelegate collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath: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(_layoutDelegate != 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]; + 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(_layoutDelegate != 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]; + 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]; } #pragma mark - ASRangeControllerDelegate. diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 45f1d3af37..1e3f7029d4 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -61,7 +61,7 @@ NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; 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]; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 5aab03e012..179b880827 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -11,6 +11,7 @@ #import @class ASCollectionView; +@protocol ASCollectionViewDelegate; @protocol ASCollectionViewLayoutInspecting @@ -29,6 +30,13 @@ */ - (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)delegate; + @end @interface ASCollectionViewFlowLayoutInspector : NSObject @@ -37,6 +45,4 @@ - (instancetype)initWithCollectionView:(ASCollectionView *)collectionView flowLayout:(UICollectionViewFlowLayout *)flowLayout; -- (void)cacheSelectorsForCollectionView:(ASCollectionView *)collectionView; - @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index e68020490d..5ce3b9bfb6 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -11,6 +11,8 @@ #import "ASCollectionViewFlowLayoutInspector.h" #import "ASCollectionView.h" +#import "ASAssert.h" +#import "ASEqualityHelpers.h" @implementation ASCollectionViewFlowLayoutInspector { BOOL _delegateImplementsReferenceSizeForHeader; @@ -24,24 +26,24 @@ self = [super init]; if (flowLayout == nil) { - return nil; + ASDisplayNodeAssert(NO, @"Should never create a layout inspector without a layout"); } if (self != nil) { - [self cacheSelectorsForCollectionView:collectionView]; + [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; _layout = flowLayout; } return self; } -- (void)cacheSelectorsForCollectionView:(ASCollectionView *)collectionView +- (void)didChangeCollectionViewDelegate:(id)delegate; { - if (collectionView == nil) { - _delegateImplementsReferenceSizeForHeader = nil; - _delegateImplementsReferenceSizeForFooter = nil; + if (delegate == nil) { + _delegateImplementsReferenceSizeForHeader = NO; + _delegateImplementsReferenceSizeForFooter = NO; } else { - _delegateImplementsReferenceSizeForHeader = [[self delegateForCollectionView:collectionView] respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; - _delegateImplementsReferenceSizeForFooter = [[self delegateForCollectionView:collectionView] respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; + _delegateImplementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; + _delegateImplementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; } } @@ -77,13 +79,13 @@ - (CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind inSection:(NSUInteger)section collectionView:(ASCollectionView *)collectionView { - if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { + if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) { if (_delegateImplementsReferenceSizeForHeader) { return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:section]; } else { return [self.layout headerReferenceSize]; } - } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { + } else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) { if (_delegateImplementsReferenceSizeForFooter) { return [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:section]; } else { diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 3e33449389..23c28e9aa4 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -74,8 +74,8 @@ /** * 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 - * thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or + * @discussion This method will be performed before the data controller enters its editing queue. + * 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. */ - (void)prepareForReloadData; @@ -92,8 +92,8 @@ /** * 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 - * thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or + * @discussion This method will be performed before the data controller enters its editing queue. + * 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. * * @param sections Indices of sections to be inserted diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 400a1161fd..009d143273 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -15,6 +15,7 @@ #import "ASDisplayNode.h" #import "ASMultidimensionalArrayUtils.h" #import "ASDisplayNodeInternal.h" +#import "ASLayout.h" //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -185,8 +186,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); free(nodeBoundSizes); - if (completionBlock) + if (completionBlock) { completionBlock(nodes, indexPaths); + } } - (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 { - if (indexPaths.count == 0) + if (indexPaths.count == 0) { return; + } + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); NSMutableArray *editingNodes = _editingNodes[kind]; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); @@ -818,8 +822,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); + ASLayout *layout = [node measureWithSizeRange:constrainedSize]; + 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 { - return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : [NSArray array]; + return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; } - (NSMutableArray *)editingNodesOfKind:(NSString *)kind diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m index db993713b5..59a081ec21 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -91,15 +91,15 @@ { UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - XCTAssert(collectionView.layoutDelegate != 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 != nil, @"should automatically set a layout delegate for flow layouts"); + XCTAssert([collectionView.layoutInspector isKindOfClass:[ASCollectionViewFlowLayoutInspector class]], @"should have a flow layout inspector by default"); } - (void)testThatItDoesNotSetALayoutInspectorForCustomLayouts { UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; 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