[ASPagerNode] New API tweaks. Support setting delegate + dataSource on ASCollectionNode and ASTableNode without triggering view creation.

This commit is contained in:
Scott Goodson
2015-12-24 17:06:57 -08:00
parent 985e47a7b7
commit 27c151095b
11 changed files with 230 additions and 39 deletions

View File

@@ -17,6 +17,9 @@
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout;
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
@property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
@property (nonatomic, readonly) ASCollectionView *view; @property (nonatomic, readonly) ASCollectionView *view;
/** /**

View File

@@ -9,7 +9,19 @@
#import "ASCollectionNode.h" #import "ASCollectionNode.h"
#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+Subclasses.h"
@interface ASCollectionView (Internal) @interface _ASCollectionPendingState : NSObject
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
@property (weak, nonatomic) id <ASCollectionDataSource> dataSource;
@end
@implementation _ASCollectionPendingState
@end
@interface ASCollectionNode ()
@property (nonatomic) _ASCollectionPendingState *pendingState;
@end
@interface ASCollectionView ()
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@end @end
@@ -29,12 +41,59 @@
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
{ {
if (self = [super initWithViewBlock:^UIView *{ return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout]; }]) { ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{
return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout];
};
if (self = [super initWithViewBlock:collectionViewBlock]) {
return self; return self;
} }
return nil; return nil;
} }
- (void)didLoad
{
[super didLoad];
if (_pendingState) {
_ASCollectionPendingState *pendingState = _pendingState;
self.pendingState = nil;
ASCollectionView *view = self.view;
view.asyncDelegate = pendingState.delegate;
view.asyncDataSource = pendingState.dataSource;
}
}
- (_ASCollectionPendingState *)pendingState
{
if (!_pendingState && ![self isNodeLoaded]) {
self.pendingState = [[_ASCollectionPendingState alloc] init];
}
ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASCollectionNode should not have a pendingState once it is loaded");
return _pendingState;
}
- (void)setDelegate:(id <ASCollectionDelegate>)delegate
{
if ([self pendingState]) {
_pendingState.delegate = delegate;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.asyncDelegate = delegate;
}
}
- (void)setDataSource:(id <ASCollectionDataSource>)dataSource
{
if ([self pendingState]) {
_pendingState.dataSource = dataSource;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.asyncDataSource = dataSource;
}
}
- (ASCollectionView *)view - (ASCollectionView *)view
{ {
return (ASCollectionView *)[super view]; return (ASCollectionView *)[super view];

View File

@@ -286,6 +286,8 @@
/** /**
* This is a node-based UICollectionViewDataSource. * This is a node-based UICollectionViewDataSource.
*/ */
@protocol ASCollectionDataSource <ASCollectionViewDataSource>
@end
@protocol ASCollectionViewDataSource <ASCommonCollectionViewDataSource, NSObject> @protocol ASCollectionViewDataSource <ASCommonCollectionViewDataSource, NSObject>
/** /**
@@ -347,6 +349,8 @@
/** /**
* This is a node-based UICollectionViewDelegate. * This is a node-based UICollectionViewDelegate.
*/ */
@protocol ASCollectionDelegate <ASCollectionViewDelegate>
@end
@protocol ASCollectionViewDelegate <ASCommonCollectionViewDelegate, NSObject> @protocol ASCollectionViewDelegate <ASCommonCollectionViewDelegate, NSObject>
@optional @optional

View File

@@ -12,7 +12,21 @@
@interface ASPagerNode : ASCollectionNode @interface ASPagerNode : ASCollectionNode
@property (weak, nonatomic) id<ASPagerNodeDataSource> dataSource; // Configures a default horizontal, paging flow layout with 0 inter-item spacing.
- (instancetype)init;
// Initializer with custom-configured flow layout properties.
- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout;
// The underlying ASCollectionView object.
- (ASCollectionView *)collectionView;
// Delegate is optional, and uses the same protocol as ASCollectionNode.
// This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay...
@property (weak, nonatomic) id <ASCollectionDelegate> delegate;
// Data Source is required, and uses a different protocol from ASCollectionNode.
@property (weak, nonatomic) id <ASPagerNodeDataSource> dataSource;
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated;
@@ -20,8 +34,10 @@
@protocol ASPagerNodeDataSource <NSObject> @protocol ASPagerNodeDataSource <NSObject>
// This method replaces -collectionView:numberOfItemsInSection:
- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode;
// This method replaces -collectionView:nodeForItemAtIndexPath:
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index;
@end @end

View File

@@ -7,11 +7,14 @@
// //
#import "ASPagerNode.h" #import "ASPagerNode.h"
#import "ASDelegateProxy.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h> #import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface ASPagerNode () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout> { @interface ASPagerNode () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout> {
UICollectionViewFlowLayout *_flowLayout; UICollectionViewFlowLayout *_flowLayout;
ASPagerNodeProxy *_proxy;
id <ASPagerNodeDataSource> _pagerDataSource;
} }
@end @end
@@ -25,6 +28,11 @@
flowLayout.minimumInteritemSpacing = 0; flowLayout.minimumInteritemSpacing = 0;
flowLayout.minimumLineSpacing = 0; flowLayout.minimumLineSpacing = 0;
return [self initWithFlowLayout:flowLayout];
}
- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout
{
self = [super initWithCollectionViewLayout:flowLayout]; self = [super initWithCollectionViewLayout:flowLayout];
if (self != nil) { if (self != nil) {
_flowLayout = flowLayout; _flowLayout = flowLayout;
@@ -32,17 +40,38 @@
return self; return self;
} }
- (ASCollectionView *)collectionView
{
return self.view;
}
- (void)setDataSource:(id <ASPagerNodeDataSource>)pagerDataSource
{
if (pagerDataSource != _pagerDataSource) {
_pagerDataSource = pagerDataSource;
_proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil;
super.dataSource = _proxy;
}
}
- (id <ASPagerNodeDataSource>)dataSource
{
return _pagerDataSource;
}
- (void)didLoad - (void)didLoad
{ {
[super didLoad]; [super didLoad];
self.view.asyncDataSource = self; ASCollectionView *cv = self.view;
self.view.asyncDelegate = self; cv.asyncDataSource = self;
cv.asyncDelegate = self;
self.view.pagingEnabled = YES; cv.pagingEnabled = YES;
self.view.allowsSelection = NO; cv.allowsSelection = NO;
self.view.showsVerticalScrollIndicator = NO; cv.showsVerticalScrollIndicator = NO;
self.view.showsHorizontalScrollIndicator = NO; cv.showsHorizontalScrollIndicator = NO;
cv.scrollsToTop = NO;
ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 };
ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 };
@@ -62,14 +91,14 @@
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
{ {
ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes");
return [self.dataSource pagerNode:self nodeAtIndex:indexPath.item]; return [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item];
} }
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{ {
ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes");
return [self.dataSource numberOfPagesInPagerNode:self]; return [_pagerDataSource numberOfPagesInPagerNode:self];
} }
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath

View File

@@ -18,4 +18,8 @@
@property (nonatomic, readonly) ASTableView *view; @property (nonatomic, readonly) ASTableView *view;
// These properties can be set without triggering the view to be created, so it's fine to set them in -init.
@property (weak, nonatomic) id <ASTableDelegate> delegate;
@property (weak, nonatomic) id <ASTableDataSource> dataSource;
@end @end

View File

@@ -8,18 +8,71 @@
#import "ASTableNode.h" #import "ASTableNode.h"
@interface _ASTablePendingState : NSObject
@property (weak, nonatomic) id <ASTableDelegate> delegate;
@property (weak, nonatomic) id <ASTableDataSource> dataSource;
@end
@implementation _ASTablePendingState
@end
@interface ASTableNode ()
@property (nonatomic) _ASTablePendingState *pendingState;
@end
@implementation ASTableNode @implementation ASTableNode
- (instancetype)initWithStyle:(UITableViewStyle)style - (instancetype)initWithStyle:(UITableViewStyle)style
{ {
if (self = [super initWithViewBlock:^UIView *{ if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] initWithFrame:CGRectZero style:style]; }]) {
return [[ASTableView alloc] initWithFrame:CGRectZero style:style];
}]) {
return self; return self;
} }
return nil; return nil;
} }
- (void)didLoad
{
[super didLoad];
if (_pendingState) {
_ASTablePendingState *pendingState = _pendingState;
self.pendingState = nil;
ASTableView *view = self.view;
view.asyncDelegate = pendingState.delegate;
view.asyncDataSource = pendingState.dataSource;
}
}
- (_ASTablePendingState *)pendingState
{
if (!_pendingState && ![self isNodeLoaded]) {
self.pendingState = [[_ASTablePendingState alloc] init];
}
ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode should not have a pendingState once it is loaded");
return _pendingState;
}
- (void)setDelegate:(id <ASTableDelegate>)delegate
{
if ([self pendingState]) {
_pendingState.delegate = delegate;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
self.view.asyncDelegate = delegate;
}
}
- (void)setDataSource:(id <ASTableDataSource>)dataSource
{
if ([self pendingState]) {
_pendingState.dataSource = dataSource;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
self.view.asyncDataSource = dataSource;
}
}
- (ASTableView *)view - (ASTableView *)view
{ {
return (ASTableView *)[super view]; return (ASTableView *)[super view];

View File

@@ -280,6 +280,8 @@
/** /**
* This is a node-based UITableViewDataSource. * This is a node-based UITableViewDataSource.
*/ */
@protocol ASTableDataSource <ASTableViewDataSource>
@end
@protocol ASTableViewDataSource <ASCommonTableViewDataSource, NSObject> @protocol ASTableViewDataSource <ASCommonTableViewDataSource, NSObject>
/** /**
@@ -324,6 +326,8 @@
* Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are
* responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:.
*/ */
@protocol ASTableDelegate <ASTableViewDelegate>
@end
@protocol ASTableViewDelegate <ASCommonTableViewDelegate, NSObject> @protocol ASTableViewDelegate <ASCommonTableViewDelegate, NSObject>
@optional @optional

View File

@@ -49,3 +49,7 @@
@interface ASCollectionViewProxy : ASDelegateProxy @interface ASCollectionViewProxy : ASDelegateProxy
@end @end
@interface ASPagerNodeProxy : ASDelegateProxy
@end

View File

@@ -60,6 +60,20 @@
@end @end
@implementation ASPagerNodeProxy
- (BOOL)interceptsSelector:(SEL)selector
{
return (
// handled by ASPagerNodeDataSource node<->cell machinery
selector == @selector(collectionView:nodeForItemAtIndexPath:) ||
selector == @selector(collectionView:numberOfItemsInSection:) ||
selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:)
);
}
@end
@implementation ASDelegateProxy { @implementation ASDelegateProxy {
id <NSObject> __weak _target; id <NSObject> __weak _target;
id <ASDelegateProxyInterceptor> __weak _interceptor; id <ASDelegateProxyInterceptor> __weak _interceptor;

View File

@@ -39,33 +39,34 @@
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{ {
CGSize size = { CGSize maxConstrainedSize = CGSizeMake(constrainedSize.max.width, constrainedSize.max.height);
constrainedSize.max.width,
constrainedSize.max.height NSArray *children = self.children;
}; NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count];
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:self.children.count]; for (id<ASLayoutable> child in children) {
for (id<ASLayoutable> child in self.children) { CGPoint layoutPosition = child.layoutPosition;
CGSize autoMaxSize = { CGSize autoMaxSize = CGSizeMake(maxConstrainedSize.width - layoutPosition.x,
constrainedSize.max.width - child.layoutPosition.x, maxConstrainedSize.height - layoutPosition.y);
constrainedSize.max.height - child.layoutPosition.y
}; ASRelativeSizeRange childSizeRange = child.sizeRange;
ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.sizeRange) BOOL childIsUnconstrained = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, childSizeRange);
? ASSizeRangeMake({0, 0}, autoMaxSize) ASSizeRange childConstraint = childIsUnconstrained ? ASSizeRangeMake({0, 0}, autoMaxSize)
: ASRelativeSizeRangeResolve(child.sizeRange, size); : ASRelativeSizeRangeResolve(childSizeRange, maxConstrainedSize);
ASLayout *sublayout = [child measureWithSizeRange:childConstraint]; ASLayout *sublayout = [child measureWithSizeRange:childConstraint];
sublayout.position = child.layoutPosition; sublayout.position = layoutPosition;
[sublayouts addObject:sublayout]; [sublayouts addObject:sublayout];
} }
size.width = constrainedSize.min.width; CGSize size = CGSizeMake(constrainedSize.min.width, constrainedSize.min.height);
for (ASLayout *sublayout in sublayouts) {
size.width = MAX(size.width, sublayout.position.x + sublayout.size.width);
}
size.height = constrainedSize.min.height;
for (ASLayout *sublayout in sublayouts) { for (ASLayout *sublayout in sublayouts) {
size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); CGPoint sublayoutPosition = sublayout.position;
CGSize sublayoutSize = sublayout.size;
size.width = MAX(size.width, sublayoutPosition.x + sublayoutSize.width);
size.height = MAX(size.height, sublayoutPosition.y + sublayoutSize.height);
} }
return [ASLayout layoutWithLayoutableObject:self return [ASLayout layoutWithLayoutableObject:self
@@ -75,12 +76,12 @@
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier - (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier
{ {
ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports setChildren"); ASDisplayNodeAssert(NO, @"ASStaticLayoutSpec only supports setChildren");
} }
- (id<ASLayoutable>)childForIdentifier:(NSString *)identifier - (id<ASLayoutable>)childForIdentifier:(NSString *)identifier
{ {
ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports children"); ASDisplayNodeAssert(NO, @"ASStaticLayoutSpec only supports children");
return nil; return nil;
} }