diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index 8109c897..8dcdb793 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -17,6 +17,9 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; +@property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id dataSource; + @property (nonatomic, readonly) ASCollectionView *view; /** diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index 8c74d62c..7cf49fbc 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -9,7 +9,19 @@ #import "ASCollectionNode.h" #import "ASDisplayNode+Subclasses.h" -@interface ASCollectionView (Internal) +@interface _ASCollectionPendingState : NSObject +@property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id dataSource; +@end + +@implementation _ASCollectionPendingState +@end + +@interface ASCollectionNode () +@property (nonatomic) _ASCollectionPendingState *pendingState; +@end + +@interface ASCollectionView () - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @end @@ -29,12 +41,59 @@ - (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 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 )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 )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 { return (ASCollectionView *)[super view]; diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 5d93fdcd..b0072e64 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -286,6 +286,8 @@ /** * This is a node-based UICollectionViewDataSource. */ +@protocol ASCollectionDataSource +@end @protocol ASCollectionViewDataSource /** @@ -347,6 +349,8 @@ /** * This is a node-based UICollectionViewDelegate. */ +@protocol ASCollectionDelegate +@end @protocol ASCollectionViewDelegate @optional diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 36355c91..f2f69b61 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -12,7 +12,21 @@ @interface ASPagerNode : ASCollectionNode -@property (weak, nonatomic) id 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 delegate; + +// Data Source is required, and uses a different protocol from ASCollectionNode. +@property (weak, nonatomic) id dataSource; - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; @@ -20,8 +34,10 @@ @protocol ASPagerNodeDataSource +// This method replaces -collectionView:numberOfItemsInSection: - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; +// This method replaces -collectionView:nodeForItemAtIndexPath: - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index eead9a30..dbaaa973 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -7,11 +7,14 @@ // #import "ASPagerNode.h" +#import "ASDelegateProxy.h" #import @interface ASPagerNode () { UICollectionViewFlowLayout *_flowLayout; + ASPagerNodeProxy *_proxy; + id _pagerDataSource; } @end @@ -25,6 +28,11 @@ flowLayout.minimumInteritemSpacing = 0; flowLayout.minimumLineSpacing = 0; + return [self initWithFlowLayout:flowLayout]; +} + +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout +{ self = [super initWithCollectionViewLayout:flowLayout]; if (self != nil) { _flowLayout = flowLayout; @@ -32,17 +40,38 @@ return self; } +- (ASCollectionView *)collectionView +{ + return self.view; +} + +- (void)setDataSource:(id )pagerDataSource +{ + if (pagerDataSource != _pagerDataSource) { + _pagerDataSource = pagerDataSource; + _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; + super.dataSource = _proxy; + } +} + +- (id )dataSource +{ + return _pagerDataSource; +} + - (void)didLoad { [super didLoad]; - self.view.asyncDataSource = self; - self.view.asyncDelegate = self; + ASCollectionView *cv = self.view; + cv.asyncDataSource = self; + cv.asyncDelegate = self; - self.view.pagingEnabled = YES; - self.view.allowsSelection = NO; - self.view.showsVerticalScrollIndicator = NO; - self.view.showsHorizontalScrollIndicator = NO; + cv.pagingEnabled = YES; + cv.allowsSelection = NO; + cv.showsVerticalScrollIndicator = NO; + cv.showsHorizontalScrollIndicator = NO; + cv.scrollsToTop = NO; ASRangeTuningParameters preloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; ASRangeTuningParameters renderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; @@ -62,14 +91,14 @@ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath { - ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); - return [self.dataSource pagerNode:self nodeAtIndex:indexPath.item]; + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); + return [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - ASDisplayNodeAssert(self.dataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); - return [self.dataSource numberOfPagesInPagerNode:self]; + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load paging nodes"); + return [_pagerDataSource numberOfPagesInPagerNode:self]; } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASTableNode.h b/AsyncDisplayKit/ASTableNode.h index 026414be..d859df67 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/AsyncDisplayKit/ASTableNode.h @@ -18,4 +18,8 @@ @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 delegate; +@property (weak, nonatomic) id dataSource; + @end diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 11287a83..136c6843 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -8,18 +8,71 @@ #import "ASTableNode.h" +@interface _ASTablePendingState : NSObject +@property (weak, nonatomic) id delegate; +@property (weak, nonatomic) id dataSource; +@end + +@implementation _ASTablePendingState +@end + +@interface ASTableNode () +@property (nonatomic) _ASTablePendingState *pendingState; +@end + @implementation ASTableNode - (instancetype)initWithStyle:(UITableViewStyle)style { - if (self = [super initWithViewBlock:^UIView *{ - return [[ASTableView alloc] initWithFrame:CGRectZero style:style]; - }]) { + if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] initWithFrame:CGRectZero style:style]; }]) { return self; } 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 )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 )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 { return (ASTableView *)[super view]; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 67c4b852..f6746874 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -280,6 +280,8 @@ /** * This is a node-based UITableViewDataSource. */ +@protocol ASTableDataSource +@end @protocol ASTableViewDataSource /** @@ -324,6 +326,8 @@ * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. */ +@protocol ASTableDelegate +@end @protocol ASTableViewDelegate @optional diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.h b/AsyncDisplayKit/Details/ASDelegateProxy.h index ca5ae5da..850328a8 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.h +++ b/AsyncDisplayKit/Details/ASDelegateProxy.h @@ -49,3 +49,7 @@ @interface ASCollectionViewProxy : ASDelegateProxy @end + +@interface ASPagerNodeProxy : ASDelegateProxy +@end + diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index cd64c482..8054b2b0 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -60,6 +60,20 @@ @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 { id __weak _target; id __weak _interceptor; diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm index f3460173..ae957559 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -39,33 +39,34 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - CGSize size = { - constrainedSize.max.width, - constrainedSize.max.height - }; + CGSize maxConstrainedSize = CGSizeMake(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 child in self.children) { - CGSize autoMaxSize = { - constrainedSize.max.width - child.layoutPosition.x, - constrainedSize.max.height - child.layoutPosition.y - }; - ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.sizeRange) - ? ASSizeRangeMake({0, 0}, autoMaxSize) - : ASRelativeSizeRangeResolve(child.sizeRange, size); + for (id child in children) { + CGPoint layoutPosition = child.layoutPosition; + CGSize autoMaxSize = CGSizeMake(maxConstrainedSize.width - layoutPosition.x, + maxConstrainedSize.height - layoutPosition.y); + + ASRelativeSizeRange childSizeRange = child.sizeRange; + BOOL childIsUnconstrained = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, childSizeRange); + ASSizeRange childConstraint = childIsUnconstrained ? ASSizeRangeMake({0, 0}, autoMaxSize) + : ASRelativeSizeRangeResolve(childSizeRange, maxConstrainedSize); + ASLayout *sublayout = [child measureWithSizeRange:childConstraint]; - sublayout.position = child.layoutPosition; + sublayout.position = layoutPosition; [sublayouts addObject:sublayout]; } - size.width = constrainedSize.min.width; - for (ASLayout *sublayout in sublayouts) { - size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); - } + CGSize size = CGSizeMake(constrainedSize.min.width, constrainedSize.min.height); - size.height = constrainedSize.min.height; 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 @@ -75,12 +76,12 @@ - (void)setChild:(id)child forIdentifier:(NSString *)identifier { - ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports setChildren"); + ASDisplayNodeAssert(NO, @"ASStaticLayoutSpec only supports setChildren"); } - (id)childForIdentifier:(NSString *)identifier { - ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports children"); + ASDisplayNodeAssert(NO, @"ASStaticLayoutSpec only supports children"); return nil; }