From 27c151095b3a6d2454742eed62783eb17d125812 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 17:06:57 -0800 Subject: [PATCH 1/6] [ASPagerNode] New API tweaks. Support setting delegate + dataSource on ASCollectionNode and ASTableNode without triggering view creation. --- AsyncDisplayKit/ASCollectionNode.h | 3 + AsyncDisplayKit/ASCollectionNode.m | 63 +++++++++++++++++++- AsyncDisplayKit/ASCollectionView.h | 4 ++ AsyncDisplayKit/ASPagerNode.h | 20 ++++++- AsyncDisplayKit/ASPagerNode.m | 49 +++++++++++---- AsyncDisplayKit/ASTableNode.h | 4 ++ AsyncDisplayKit/ASTableNode.m | 59 +++++++++++++++++- AsyncDisplayKit/ASTableView.h | 4 ++ AsyncDisplayKit/Details/ASDelegateProxy.h | 4 ++ AsyncDisplayKit/Details/ASDelegateProxy.m | 14 +++++ AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm | 45 +++++++------- 11 files changed, 230 insertions(+), 39 deletions(-) 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; } From c1640c7f59d8d1cec6a18f6ec04bd87563a4078c Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 18:02:52 -0800 Subject: [PATCH 2/6] Implement getter methods for new table / collection delegate / dataSource. Make ASTableView node-backed. --- AsyncDisplayKit/ASCollectionNode.m | 18 ++++++++++++++++++ AsyncDisplayKit/ASTableNode.m | 24 +++++++++++++++++++++++- AsyncDisplayKit/ASTableView.mm | 23 ++++++++++++----------- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.m b/AsyncDisplayKit/ASCollectionNode.m index 7cf49fbc..315c3626 100644 --- a/AsyncDisplayKit/ASCollectionNode.m +++ b/AsyncDisplayKit/ASCollectionNode.m @@ -84,6 +84,15 @@ } } +- (id )delegate +{ + if ([self pendingState]) { + return _pendingState.delegate; + } else { + return self.view.asyncDelegate; + } +} + - (void)setDataSource:(id )dataSource { if ([self pendingState]) { @@ -94,6 +103,15 @@ } } +- (id )dataSource +{ + if ([self pendingState]) { + return _pendingState.dataSource; + } else { + return self.view.asyncDataSource; + } +} + - (ASCollectionView *)view { return (ASCollectionView *)[super view]; diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 136c6843..2d9bbc33 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -20,11 +20,15 @@ @property (nonatomic) _ASTablePendingState *pendingState; @end +@interface ASTableView () +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style; +@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; @@ -63,6 +67,15 @@ } } +- (id )delegate +{ + if ([self pendingState]) { + return _pendingState.delegate; + } else { + return self.view.asyncDelegate; + } +} + - (void)setDataSource:(id )dataSource { if ([self pendingState]) { @@ -73,6 +86,15 @@ } } +- (id )dataSource +{ + if ([self pendingState]) { + return _pendingState.dataSource; + } else { + return self.view.asyncDataSource; + } +} + - (ASTableView *)view { return (ASTableView *)[super view]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 8d799b5b..162a0fa1 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -8,6 +8,7 @@ #import "ASTableView.h" #import "ASTableViewInternal.h" +#import "ASTableNode.h" #import "ASAssert.h" #import "ASBatchFetching.h" @@ -159,33 +160,33 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return [self initWithFrame:frame style:style asyncDataFetching:NO]; } +// FIXME: This method is deprecated and will probably be removed in or shortly after 2.0. - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled { return [self initWithFrame:frame style:style dataControllerClass:[self.class dataControllerClass] asyncDataFetching:asyncDataFetchingEnabled]; } - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + ASTableNode *tableNode = [[ASTableNode alloc] initWithStyle:style]; + tableNode.frame = frame; + return tableNode.view; +} + +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style { if (!(self = [super initWithFrame:frame style:style])) return nil; - - // FIXME: asyncDataFetching is currently unreliable for some use cases. - // https://github.com/facebook/AsyncDisplayKit/issues/385 - asyncDataFetchingEnabled = NO; - [self configureWithDataControllerClass:dataControllerClass asyncDataFetching:asyncDataFetchingEnabled]; + [self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO]; return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - if (!(self = [super initWithCoder:aDecoder])) - return nil; - - [self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO]; - - return self; + NSLog(@"Warning: AsyncDisplayKit is not designed to be used with Interface Builder. Table properties set in IB will be lost."); + return [self initWithFrame:CGRectZero style:UITableViewStylePlain]; } - (void)dealloc From 7ece41ff645b9ee9e132b06cbc2993f3f8f84b5b Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 21:41:12 -0800 Subject: [PATCH 3/6] Delegate definition tweaks for Table and Collection; ensure Table tests run with ARC enabled. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASCollectionView.h | 18 +++++++--------- AsyncDisplayKit/ASPagerNode.h | 21 ++++++++++--------- AsyncDisplayKit/ASPagerNode.m | 10 +++++++-- AsyncDisplayKit/ASTableNode.h | 3 ++- AsyncDisplayKit/ASTableNode.m | 18 +++++++++++++--- AsyncDisplayKit/ASTableView.h | 21 +++++++------------ AsyncDisplayKit/ASTableView.mm | 20 +++++++++++++----- .../ASCollectionViewFlowLayoutInspector.h | 4 ++-- .../ASCollectionViewFlowLayoutInspector.m | 2 +- AsyncDisplayKitTests/ASTableViewTests.m | 14 +++++++------ 11 files changed, 79 insertions(+), 54 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 136a89cb..02830223 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -242,7 +242,7 @@ 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; }; 34EFC7781B701D3100AD841F /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */; }; 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; - 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; }; 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */ = {isa = PBXBuildFile; fileRef = 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; 430E7C911B4C23F100697A4C /* ASIndexPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */; }; diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index b0072e64..f7cd8da0 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -15,8 +15,8 @@ #import @class ASCellNode; -@protocol ASCollectionViewDataSource; -@protocol ASCollectionViewDelegate; +@protocol ASCollectionDataSource; +@protocol ASCollectionDelegate; @protocol ASCollectionViewLayoutInspecting; /** @@ -35,8 +35,8 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; -@property (nonatomic, weak) id asyncDataSource; -@property (nonatomic, weak) id asyncDelegate; // must not be nil +@property (nonatomic, weak) id asyncDelegate; +@property (nonatomic, weak) id asyncDataSource; /** * Tuning parameters for a range type. @@ -286,9 +286,8 @@ /** * This is a node-based UICollectionViewDataSource. */ -@protocol ASCollectionDataSource -@end -@protocol ASCollectionViewDataSource +#define ASCollectionViewDataSource ASCollectionDataSource +@protocol ASCollectionDataSource /** * Similar to -collectionView:cellForItemAtIndexPath:. @@ -349,9 +348,8 @@ /** * This is a node-based UICollectionViewDelegate. */ -@protocol ASCollectionDelegate -@end -@protocol ASCollectionViewDelegate +#define ASCollectionViewDelegate ASCollectionDelegate +@protocol ASCollectionDelegate @optional diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index f2f69b61..325eacea 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -8,7 +8,17 @@ #import -@protocol ASPagerNodeDataSource; +@class ASPagerNode; + +@protocol ASPagerNodeDataSource + +// This method replaces -collectionView:numberOfItemsInSection: +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; + +// This method replaces -collectionView:nodeForItemAtIndexPath: +- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; + +@end @interface ASPagerNode : ASCollectionNode @@ -32,12 +42,3 @@ @end -@protocol ASPagerNodeDataSource - -// This method replaces -collectionView:numberOfItemsInSection: -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; - -// This method replaces -collectionView:nodeForItemAtIndexPath: -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - -@end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index dbaaa973..d4537d0a 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -11,7 +11,7 @@ #import -@interface ASPagerNode () { +@interface ASPagerNode () { UICollectionViewFlowLayout *_flowLayout; ASPagerNodeProxy *_proxy; id _pagerDataSource; @@ -20,6 +20,7 @@ @end @implementation ASPagerNode +@dynamic delegate; - (instancetype)init { @@ -50,10 +51,15 @@ if (pagerDataSource != _pagerDataSource) { _pagerDataSource = pagerDataSource; _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; - super.dataSource = _proxy; + super.dataSource = (id )_proxy; } } +- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy +{ + [self setDataSource:nil]; +} + - (id )dataSource { return _pagerDataSource; diff --git a/AsyncDisplayKit/ASTableNode.h b/AsyncDisplayKit/ASTableNode.h index d859df67..9ab36470 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/AsyncDisplayKit/ASTableNode.h @@ -14,7 +14,8 @@ */ @interface ASTableNode : ASDisplayNode -- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; +- (instancetype)init; // UITableViewStylePlain +- (instancetype)initWithStyle:(UITableViewStyle)style; @property (nonatomic, readonly) ASTableView *view; diff --git a/AsyncDisplayKit/ASTableNode.m b/AsyncDisplayKit/ASTableNode.m index 2d9bbc33..f45f742d 100644 --- a/AsyncDisplayKit/ASTableNode.m +++ b/AsyncDisplayKit/ASTableNode.m @@ -21,19 +21,31 @@ @end @interface ASTableView () -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style; +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; @end @implementation ASTableNode -- (instancetype)initWithStyle:(UITableViewStyle)style +- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass { - if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] _initWithFrame:CGRectZero style:style]; }]) { + if (self = [super initWithViewBlock:^UIView *{ return [[ASTableView alloc] _initWithFrame:CGRectZero + style:style + dataControllerClass:dataControllerClass]; }]) { return self; } return nil; } +- (instancetype)initWithStyle:(UITableViewStyle)style +{ + return [self _initWithStyle:style dataControllerClass:nil]; +} + +- (instancetype)init +{ + return [self _initWithStyle:UITableViewStylePlain dataControllerClass:nil]; +} + - (void)didLoad { [super didLoad]; diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index f6746874..ae43552d 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -7,17 +7,14 @@ */ #import - #import #import #import #import - @class ASCellNode; -@protocol ASTableViewDataSource; -@protocol ASTableViewDelegate; - +@protocol ASTableDataSource; +@protocol ASTableDelegate; /** * Node-based table view. @@ -27,8 +24,8 @@ */ @interface ASTableView : UITableView -@property (nonatomic, weak) id asyncDelegate; // must not be nil -@property (nonatomic, weak) id asyncDataSource; +@property (nonatomic, weak) id asyncDelegate; +@property (nonatomic, weak) id asyncDataSource; /** * Initializer. @@ -280,9 +277,8 @@ /** * This is a node-based UITableViewDataSource. */ -@protocol ASTableDataSource -@end -@protocol ASTableViewDataSource +#define ASTableViewDataSource ASTableDataSource +@protocol ASTableDataSource /** * Similar to -tableView:cellForRowAtIndexPath:. @@ -326,9 +322,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 +#define ASTableViewDelegate ASTableDelegate +@protocol ASTableDelegate @optional diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 162a0fa1..a5e65178 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -21,6 +21,8 @@ #import "ASLayoutController.h" #import "ASRangeController.h" +#import + static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; //#define LOG(...) NSLog(__VA_ARGS__) @@ -80,6 +82,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark ASTableView +@interface ASTableNode () +- (instancetype)_initWithStyle:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; +@end + @interface ASTableView () { ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDelegate; @@ -168,17 +174,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled { - ASTableNode *tableNode = [[ASTableNode alloc] initWithStyle:style]; - tableNode.frame = frame; - return tableNode.view; +// ASTableNode *tableNode = [[ASTableNode alloc] _initWithStyle:style dataControllerClass:dataControllerClass]; +// tableNode.frame = frame; +// return tableNode.view; + return [self _initWithFrame:frame style:style dataControllerClass:dataControllerClass]; } -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass { if (!(self = [super initWithFrame:frame style:style])) return nil; - [self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO]; + if (!dataControllerClass) { + dataControllerClass = [self.class dataControllerClass]; + } + [self configureWithDataControllerClass:dataControllerClass asyncDataFetching:NO]; return self; } diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 2890cab8..b2ee2ae8 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -11,7 +11,7 @@ #import @class ASCollectionView; -@protocol ASCollectionViewDelegate; +@protocol ASCollectionDelegate; @protocol ASCollectionViewLayoutInspecting @@ -42,7 +42,7 @@ * * @discussion A great time to update perform selector caches! */ -- (void)didChangeCollectionViewDelegate:(id)delegate; +- (void)didChangeCollectionViewDelegate:(id)delegate; @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index f6221489..f98afbb9 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -36,7 +36,7 @@ return self; } -- (void)didChangeCollectionViewDelegate:(id)delegate; +- (void)didChangeCollectionViewDelegate:(id)delegate; { if (delegate == nil) { _delegateImplementsReferenceSizeForHeader = NO; diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index b4e137bc..a382b6be 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -52,7 +52,6 @@ if (_willDeallocBlock) { _willDeallocBlock(self); } - [super dealloc]; } @end @@ -78,7 +77,6 @@ if (_willDeallocBlock) { _willDeallocBlock(self); } - [super dealloc]; } @end @@ -130,6 +128,7 @@ @implementation ASTableViewTests +// TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; @@ -148,11 +147,11 @@ tableView.asyncDataSource = delegate; tableView.asyncDelegate = delegate; - - [delegate release]; + +// [delegate release]; XCTAssertTrue(delegateDidDealloc, @"unexpected delegate lifetime:%@", delegate); - XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView); +// XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView); XCTAssertTrue(tableViewDidDealloc, @"unexpected table view lifetime:%@", tableView); } @@ -399,7 +398,10 @@ style:UITableViewStylePlain asyncDataFetching:YES]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + tableView.asyncDelegate = dataSource; tableView.asyncDataSource = dataSource; From a0e4484ef7438d94d9a02013a919d5f91d6bb41d Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 22:27:52 -0800 Subject: [PATCH 4/6] Declare ASPagerNode dataSource property as @dynamic so that it can be a different type than ASCollectionNode. --- AsyncDisplayKit/ASPagerNode.h | 5 +---- AsyncDisplayKit/ASPagerNode.m | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 325eacea..2377d1c1 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -9,15 +9,12 @@ #import @class ASPagerNode; - -@protocol ASPagerNodeDataSource - +@protocol ASPagerNodeDataSource // This method replaces -collectionView:numberOfItemsInSection: - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; // This method replaces -collectionView:nodeForItemAtIndexPath: - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - @end @interface ASPagerNode : ASCollectionNode diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index d4537d0a..31e7bbad 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -20,7 +20,7 @@ @end @implementation ASPagerNode -@dynamic delegate; +@dynamic delegate, dataSource; - (instancetype)init { From f902b4bdc73185fecd6f7090ff098583e62812d5 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 22:47:54 -0800 Subject: [PATCH 5/6] Replace property declaration with method overrides for -dataSource. --- AsyncDisplayKit/ASPagerNode.h | 5 +++-- AsyncDisplayKit/ASPagerNode.m | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 2377d1c1..746a8055 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -9,7 +9,7 @@ #import @class ASPagerNode; -@protocol ASPagerNodeDataSource +@protocol ASPagerNodeDataSource // This method replaces -collectionView:numberOfItemsInSection: - (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; @@ -33,7 +33,8 @@ @property (weak, nonatomic) id delegate; // Data Source is required, and uses a different protocol from ASCollectionNode. -@property (weak, nonatomic) id dataSource; +- (void)setDataSource:(id )dataSource; +- (id )dataSource; - (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 31e7bbad..d4537d0a 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -20,7 +20,7 @@ @end @implementation ASPagerNode -@dynamic delegate, dataSource; +@dynamic delegate; - (instancetype)init { From 4ca97e2f4dd597d0e23747bd78ab75327b927213 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 24 Dec 2015 23:13:50 -0800 Subject: [PATCH 6/6] Optimize string handling for CALayer gravity & UIView content mode. Finally fix protocol rename. --- AsyncDisplayKit/ASTableView.h | 9 ++++--- .../Private/_ASCoreAnimationExtras.mm | 27 ++++++------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index ae43552d..410b073f 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -277,7 +277,6 @@ /** * This is a node-based UITableViewDataSource. */ -#define ASTableViewDataSource ASTableDataSource @protocol ASTableDataSource /** @@ -315,6 +314,8 @@ @end +@protocol ASTableViewDataSource +@end /** * This is a node-based UITableViewDelegate. @@ -322,7 +323,6 @@ * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. */ -#define ASTableViewDelegate ASTableDelegate @protocol ASTableDelegate @optional @@ -357,4 +357,7 @@ */ - (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView; -@end \ No newline at end of file +@end + +@protocol ASTableViewDelegate ; +@end diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm index 4ff3c52e..a6205e1d 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm +++ b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm @@ -7,7 +7,7 @@ */ #import "_ASCoreAnimationExtras.h" - +#import "ASEqualityHelpers.h" #import "ASAssert.h" extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image) @@ -87,7 +87,8 @@ static const struct _UIContentModeStringLUTEntry UIContentModeDescriptionLUT[] = {UIViewContentModeBottomRight, @"bottomRight"}, }; -NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode) { +NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode) +{ for (int i=0; i< ARRAY_COUNT(UIContentModeDescriptionLUT); i++) { if (UIContentModeDescriptionLUT[i].contentMode == contentMode) { return UIContentModeDescriptionLUT[i].string; @@ -96,16 +97,10 @@ NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode) return [NSString stringWithFormat:@"%d", (int)contentMode]; } -UIViewContentMode ASDisplayNodeUIContentModeFromNSString(NSString *string) { - // If you passed one of the constants (this is just an optimization to avoid string comparison) +UIViewContentMode ASDisplayNodeUIContentModeFromNSString(NSString *string) +{ for (int i=0; i < ARRAY_COUNT(UIContentModeDescriptionLUT); i++) { - if (UIContentModeDescriptionLUT[i].string == string) { - return UIContentModeDescriptionLUT[i].contentMode; - } - } - // If you passed something isEqualToString: to one of the constants - for (int i=0; i < ARRAY_COUNT(UIContentModeDescriptionLUT); i++) { - if ([UIContentModeDescriptionLUT[i].string isEqualToString:string]) { + if (ASObjectIsEqual(UIContentModeDescriptionLUT[i].string, string)) { return UIContentModeDescriptionLUT[i].contentMode; } } @@ -126,18 +121,12 @@ NSString *const ASDisplayNodeCAContentsGravityFromUIContentMode(UIViewContentMod UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *const contentsGravity) { - // If you passed one of the constants (this is just an optimization to avoid string comparison) for (int i=0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { - if (UIContentModeCAGravityLUT[i].string == contentsGravity) { - return UIContentModeCAGravityLUT[i].contentMode; - } - } - // If you passed something isEqualToString: to one of the constants - for (int i=0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { - if ([UIContentModeCAGravityLUT[i].string isEqualToString:contentsGravity]) { + if (ASObjectIsEqual(UIContentModeCAGravityLUT[i].string, contentsGravity)) { return UIContentModeCAGravityLUT[i].contentMode; } } + ASDisplayNodeCAssert(contentsGravity, @"Encountered an unknown contentsGravity \"%@\". Is this a new version of iOS?", contentsGravity); ASDisplayNodeCAssert(!contentsGravity, @"You passed nil to ASDisplayNodeUIContentModeFromCAContentsGravity. We're falling back to resize, but this is probably a bug."); // If asserts disabled, fall back to this