IGListKit Support II: Electric Boogaloo (#2942)

* Reimplement IGListKit support in a cleaner way

* Rename and fix some stuff

* Fix supplementaries more

* Update docs

* Update test

* Cleanup minor things

* Tweak it

* Indentation

* Remove irrelevant changes

* Break out cell into its own file

* Fix indentation

* Address feedback
This commit is contained in:
Adlai Holler
2017-01-30 11:16:59 -08:00
committed by GitHub
parent 34338cadeb
commit 38aac9d019
70 changed files with 2446 additions and 182 deletions

View File

@@ -8,12 +8,18 @@
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "ASCollectionView.h"
#import <objc/runtime.h>
#import "_ASCollectionViewCell.h"
#import "ASAssert.h"
#import "ASAvailability.h"
#import "ASBatchFetching.h"
#import "ASDelegateProxy.h"
#import "ASCellNode+Internal.h"
#import "ASCollectionDataController.h"
#import "ASCollectionInternal.h"
#import "ASCollectionViewLayoutController.h"
#import "ASCollectionViewFlowLayoutInspector.h"
#import "ASDisplayNodeExtras.h"
@@ -27,6 +33,7 @@
#import "ASSectionContext.h"
#import "ASCollectionView+Undeprecated.h"
#import "_ASHierarchyChangeSet.h"
#import "ASCollectionInteropProtocols.h"
/**
* A macro to get self.collectionNode and assign it to a local variable, or return
@@ -62,79 +69,6 @@ static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimation
/// Used for all cells and supplementaries. UICV keys by supp-kind+reuseID so this is plenty.
static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
#pragma mark -
#pragma mark ASCellNode<->UICollectionViewCell bridging.
@class _ASCollectionViewCell;
@interface _ASCollectionViewCell : UICollectionViewCell
@property (nonatomic, weak) ASCellNode *node;
@property (nonatomic, strong) UICollectionViewLayoutAttributes *layoutAttributes;
@end
@implementation _ASCollectionViewCell
- (void)setNode:(ASCellNode *)node
{
ASDisplayNodeAssertMainThread();
node.layoutAttributes = _layoutAttributes;
_node = node;
self.backgroundColor = node.backgroundColor;
self.clipsToBounds = node.clipsToBounds;
[node __setSelectedFromUIKit:self.selected];
[node __setHighlightedFromUIKit:self.highlighted];
}
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
[_node __setSelectedFromUIKit:selected];
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
[_node __setHighlightedFromUIKit:highlighted];
}
- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
_layoutAttributes = layoutAttributes;
_node.layoutAttributes = layoutAttributes;
}
- (void)prepareForReuse
{
self.layoutAttributes = nil;
// Need to clear node pointer before UIKit calls setSelected:NO / setHighlighted:NO on its cells
self.node = nil;
[super prepareForReuse];
}
/**
* In the initial case, this is called by UICollectionView during cell dequeueing, before
* we get a chance to assign a node to it, so we must be sure to set these layout attributes
* on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already
* have our node assigned e.g. during a layout update for existing cells, we also attempt
* to update it now.
*/
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
self.layoutAttributes = layoutAttributes;
}
/**
* Keep our node filling our content view.
*/
- (void)layoutSubviews
{
[super layoutSubviews];
self.node.frame = self.contentView.bounds;
}
@end
#pragma mark -
#pragma mark ASCollectionView.
@@ -243,8 +177,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
unsigned int collectionNodeWillBeginBatchFetch:1;
unsigned int collectionNodeWillDisplaySupplementaryElement:1;
unsigned int collectionNodeDidEndDisplayingSupplementaryElement:1;
unsigned int shouldBatchFetchForCollectionNode:1;
// Whether this delegate conforms to ASCollectionDataSourceInterop
unsigned int interop:1;
} _asyncDelegateFlags;
struct {
@@ -256,9 +191,13 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
unsigned int collectionNodeNodeForItem:1;
unsigned int collectionNodeNodeBlockForItem:1;
unsigned int collectionNodeNodeForSupplementaryElement:1;
unsigned int collectionNodeSupplementaryElementKindsInSection:1;
unsigned int numberOfSectionsInCollectionNode:1;
unsigned int collectionNodeNumberOfItemsInSection:1;
unsigned int collectionNodeContextForSection:1;
// Whether this data source conforms to ASCollectionDataSourceInterop
unsigned int interop:1;
} _asyncDataSourceFlags;
struct {
@@ -267,8 +206,6 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
} _layoutInspectorFlags;
}
@property (nonatomic, weak) ASCollectionNode *collectionNode;
@end
@interface ASCollectionNode ()
@@ -426,14 +363,14 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
- (void)setDataSource:(id<UICollectionViewDataSource>)dataSource
{
// UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil.
ASDisplayNodeAssert(dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property.");
// UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. We also allow this when we're doing interop.
ASDisplayNodeAssert(_asyncDelegateFlags.interop || dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property.");
}
- (void)setDelegate:(id<UICollectionViewDelegate>)delegate
{
// Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here.
ASDisplayNodeAssert(delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property.");
// Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. We also allow this when we're doing interop.
ASDisplayNodeAssert(_asyncDelegateFlags.interop || delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property.");
}
- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
@@ -464,8 +401,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
if (asyncDataSource == nil) {
_asyncDataSource = nil;
_proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags));
_asyncDataSourceFlags = {};
} else {
_asyncDataSource = asyncDataSource;
_proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
@@ -482,7 +419,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:numberOfItemsInSection:)];
_asyncDataSourceFlags.collectionNodeContextForSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:contextForSection:)];
_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)];
_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)];
_asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)];
ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection || _asyncDataSourceFlags.collectionViewNumberOfItemsInSection, @"Data source must implement collectionNode:numberOfItemsInSection:");
ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNodeBlockForItem
@@ -520,8 +458,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
if (asyncDelegate == nil) {
_asyncDelegate = nil;
_proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags));
_asyncDataSourceFlags = {};
} else {
_asyncDelegate = asyncDelegate;
_proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
@@ -561,6 +498,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
_asyncDelegateFlags.collectionNodeShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldShowMenuForItemAtIndexPath:)];
_asyncDelegateFlags.collectionNodeCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:canPerformAction:forItemAtIndexPath:sender:)];
_asyncDelegateFlags.collectionNodePerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:performAction:forItemAtIndexPath:sender:)];
_asyncDelegateFlags.interop = [_asyncDelegate conformsToProtocol:@protocol(ASCollectionDelegateInterop)];
}
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
@@ -939,7 +877,13 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
UICollectionReusableView *view;
if (_asyncDataSource && _asyncDataSourceFlags.interop) {
view = [(id<ASCollectionDataSourceInterop>)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath];
} else {
view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
}
ASCellNode *node = [_dataController supplementaryNodeOfKind:kind atIndexPath:indexPath];
ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self);
[_rangeController configureContentView:view forCellNode:node];
@@ -948,7 +892,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
_ASCollectionViewCell *cell;
if (_asyncDataSource && _asyncDataSourceFlags.interop) {
cell = [(id<ASCollectionDataSourceInterop>)_asyncDataSource collectionView:collectionView cellForItemAtIndexPath:indexPath];
} else {
cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath];
}
ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
cell.node = node;
@@ -979,7 +928,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath);
if (_asyncDelegateFlags.collectionNodeWillDisplayItem) {
if (_asyncDelegateFlags.interop) {
[(id<ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
} else if (_asyncDelegateFlags.collectionNodeWillDisplayItem) {
if (ASCollectionNode *collectionNode = self.collectionNode) {
[_asyncDelegate collectionNode:collectionNode willDisplayItemWithNode:cellNode];
}
@@ -1004,7 +955,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
ASCellNode *cellNode = [cell node];
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) {
if (_asyncDelegateFlags.interop) {
[(id<ASCollectionDelegateInterop>)_asyncDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath];
} else if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) {
if (ASCollectionNode *collectionNode = self.collectionNode) {
[_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode];
}
@@ -1544,10 +1497,20 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return node;
}
// TODO: Lock this
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController
- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController sections:(NSIndexSet *)sections
{
return [_registeredSupplementaryKinds allObjects];
if (_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection) {
NSMutableSet *kinds = [NSMutableSet set];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, @[]);
[sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) {
NSArray *kindsForSection = [_asyncDataSource collectionNode:collectionNode supplementaryElementKindsInSection:section];
[kinds addObjectsFromArray:kindsForSection];
}];
return [kinds allObjects];
} else {
// TODO: Lock this
return [_registeredSupplementaryKinds allObjects];
}
}
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath