diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 8148e55d7b..4d52ef5e19 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -11,7 +11,7 @@ #import "ASAssert.h" #import "ASCollectionViewLayoutController.h" #import "ASRangeController.h" -#import "ASDataController.h" +#import "ASCollectionDataController.h" #import "ASDisplayNodeInternal.h" #import "ASBatchFetching.h" #import "UICollectionViewLayout+ASConvenience.h" @@ -37,9 +37,7 @@ static BOOL _isInterceptedSelector(SEL sel) // handled by ASCollectionView node<->cell machinery sel == @selector(collectionView:cellForItemAtIndexPath:) || sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) || - - // TODO: Supplementary views are currently not supported. An assertion is triggered if the _asyncDataSource implements this method. - // sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) || + sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) || // handled by ASRangeController sel == @selector(numberOfSectionsInCollectionView:) || @@ -136,7 +134,7 @@ static BOOL _isInterceptedSelector(SEL sel) _ASCollectionViewProxy *_proxyDataSource; _ASCollectionViewProxy *_proxyDelegate; - ASDataController *_dataController; + ASCollectionDataController *_dataController; ASRangeController *_rangeController; ASCollectionViewLayoutController *_layoutController; @@ -201,7 +199,7 @@ static BOOL _isInterceptedSelector(SEL sel) _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - _dataController = [[ASDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; + _dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; _dataController.delegate = _rangeController; _dataController.dataSource = self; @@ -381,8 +379,8 @@ static BOOL _isInterceptedSelector(SEL sel) - (void)registerSupplementaryViewOfKind:(NSString *)elementKind { - NSString *identifier = [NSString stringWithFormat:@"_ASCollectionSupplementaryView_%@", elementKind]; - [self registerClass:[UIView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:identifier]; + [self registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:elementKind + withReuseIdentifier:[self __reuseIdentifierForKind:elementKind]]; } - (void)insertSections:(NSIndexSet *)sections @@ -433,6 +431,16 @@ static BOOL _isInterceptedSelector(SEL sel) [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; } +- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return [_dataController nodeAtIndexPath:indexPath]; +} + +- (NSString *)__reuseIdentifierForKind:(NSString *)kind +{ + return [NSString stringWithFormat:@"_ASCollectionSupplementaryView_%@", kind]; +} + #pragma mark - #pragma mark Intercepted selectors. @@ -443,7 +451,8 @@ static BOOL _isInterceptedSelector(SEL sel) _ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - [_rangeController configureContentView:cell.contentView forCellNode:node]; + + [_rangeController configureContentView:cell.contentView forNode:node]; cell.node = node; @@ -455,6 +464,15 @@ static BOOL _isInterceptedSelector(SEL sel) return [[_dataController nodeAtIndexPath:indexPath] calculatedSize]; } +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + NSString *identifier = [self __reuseIdentifierForKind:kind]; + UICollectionReusableView *view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:identifier forIndexPath:indexPath]; + ASDisplayNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath]; + [_rangeController configureContentView:view forNode:node]; + return view; +} + - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { _superIsPendingDataLoad = NO; @@ -613,6 +631,11 @@ static BOOL _isInterceptedSelector(SEL sel) return node; } +- (ASDisplayNode *)dataController:(ASDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + return [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; +} + - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { ASSizeRange constrainedSize; @@ -659,7 +682,7 @@ static BOOL _isInterceptedSelector(SEL sel) return [_asyncDataSource collectionView:self numberOfItemsInSection:section]; } -- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController { +- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) { return [_asyncDataSource numberOfSectionsInCollectionView:self]; } else { diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 20c058d658..a8dc8c6f9d 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -530,7 +530,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { } ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - [_rangeController configureContentView:cell.contentView forCellNode:node]; + [_rangeController configureContentView:cell.contentView forNode:node]; cell.node = node; cell.backgroundColor = node.backgroundColor; @@ -859,7 +859,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { return [_asyncDataSource tableView:self numberOfRowsInSection:section]; } -- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController +- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { if ([_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) { return [_asyncDataSource numberOfSectionsInTableView:self]; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h new file mode 100644 index 0000000000..c623dfbbcf --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -0,0 +1,26 @@ +// +// ASCollectionDataController.h +// Pods +// +// Created by Levi McCallum on 9/22/15. +// +// + +#import + +@class ASDisplayNode, ASCollectionDataController; +@protocol ASDataControllerSource; + +@protocol ASCollectionDataControllerSource + +- (ASDisplayNode *)dataController:(ASDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +- (NSArray *)supplementaryKindsInDataController:(ASCollectionDataController *)dataController; + +@end + +@interface ASCollectionDataController : ASDataController + +- (ASDisplayNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + +@end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.m b/AsyncDisplayKit/Details/ASCollectionDataController.m new file mode 100644 index 0000000000..cf772c3b67 --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionDataController.m @@ -0,0 +1,72 @@ +// +// ASCollectionDataController.m +// Pods +// +// Created by Levi McCallum on 9/22/15. +// +// + +#import "ASCollectionDataController.h" + +#import "ASAssert.h" + +@interface ASDataController (Subclasses) + +/** + * Queues the given operation until an `endUpdates` synchronize update is completed. + * + * If this method is called outside of a begin/endUpdates batch update, the block is + * executed immediately. + */ +- (void)performEditCommandWithBlock:(void (^)(void))block; + +/** + * Safely locks access to the data source and executes the given block, unlocking once complete. + * + * When `asyncDataFetching` is enabled, the block is executed on a background thread. + */ +- (void)accessDataSourceWithBlock:(dispatch_block_t)block; + +@end + +@implementation ASCollectionDataController { + NSMutableDictionary *_completedSupplementaryNodes; + NSMutableDictionary *_editingSupplementaryNodes; +} + +- (void)initialSupplementaryLoading +{ + [self performEditCommandWithBlock:^{ + ASDisplayNodeAssertMainThread(); + [self accessDataSourceWithBlock:^{ + NSArray *elementKinds = [self.collectionDataSource supplementaryKindsInDataController:self]; + }]; + }]; +} + +- (ASDisplayNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + ASDisplayNodeAssertMainThread(); + return _completedSupplementaryNodes[kind][indexPath.section][indexPath.item]; +} + +- (id)collectionDataSource +{ + return (id)self.dataSource; +} + +#pragma mark - Internal Data Querying + +- (void)_insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +{ + if (indexPaths.count == 0) + return; +} + +- (void)_deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +{ + if (indexPaths.count == 0) + return; +} + +@end diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index ebb725bfb2..39d0b78792 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -40,7 +40,7 @@ typedef NSUInteger ASDataControllerAnimationOptions; /** Fetch the number of sections. */ -- (NSUInteger)dataControllerNumberOfSections:(ASDataController *)dataController; +- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; /** Lock the data source for data fetching. diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 6a5b361ff0..a963f44ae2 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -239,7 +239,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDisplayNodeAssertMainThread(); [self accessDataSourceWithBlock:^{ NSMutableArray *indexPaths = [NSMutableArray array]; - NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; + NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; // insert sections [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; @@ -266,7 +266,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSUInteger sectionCount = [_dataSource dataControllerNumberOfSections:self]; + NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; @@ -335,7 +335,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)_populateFromEntireDataSourceWithMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths { - NSUInteger sectionNum = [_dataSource dataControllerNumberOfSections:self]; + NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; for (NSUInteger i = 0; i < sectionNum; i++) { NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index 9d5bbe57f0..a7fc3e54df 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -41,9 +41,9 @@ * * @param contentView UIView to add a (sized) node's view to. * - * @param node The ASCellNode to be added. + * @param node The node to be added. Often an ASCellNode. */ -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node; +- (void)configureContentView:(UIView *)contentView forNode:(ASDisplayNode *)node; /** * Delegate and ultimate data source. Must not be nil. diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index b22db853bf..985e6f60cc 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -48,7 +48,7 @@ #pragma mark - View manipulation -- (void)moveNode:(ASCellNode *)node toView:(UIView *)view +- (void)moveNode:(ASDisplayNode *)node toView:(UIView *)view { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); @@ -158,7 +158,7 @@ return rangeType == ASLayoutRangeTypeRender; } -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)cellNode +- (void)configureContentView:(UIView *)contentView forNode:(ASDisplayNode *)cellNode { if (cellNode.view.superview == contentView) { // this content view is already correctly configured diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index 0363abba2f..0b6efd8568 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -64,7 +64,7 @@ // This happens if the UITableViewCell is reused after scrolling offscreen. Because the node has already been given the opportunity to display, we do not // proactively re-host it within the workingWindow (improving efficiency). Some time later, it may fall outside the working range, in which case calling // -recursivelyClearContents is critical. If the user scrolls back and it is re-hosted in a UITableViewCell, the content will still exist as it is not cleared - // by simply being removed from the cell. The code that usually triggers this condition is the -removeFromSuperview in -[ASRangeController configureContentView:forCellNode:]. + // by simply being removed from the cell. The code that usually triggers this condition is the -removeFromSuperview in -[ASRangeController configureContentView:forNode:]. // Condition #4 is suboptimal in some cases, as it is conceivable that memory warnings could trigger clearing content that is inside the working range. However, enforcing the // preservation of this content could result in the app being killed, which is not likely preferable over briefly seeing placeholders in the event the user scrolls backwards. // Nonetheless, future changes to the implementation will likely eliminate this behavior to simplify debugging and extensibility of working range functionality.