From 87a37a283ed9b74d30eb5f87c789295f6ffc276b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 20 Apr 2016 19:16:54 -0700 Subject: [PATCH] Add a block API to provide an ASLayoutSpec without having to subclass ASDisplayNode --- AsyncDisplayKit/ASDisplayNode+Subclasses.h | 2 ++ AsyncDisplayKit/ASDisplayNode.h | 16 +++++++++ AsyncDisplayKit/ASDisplayNode.mm | 35 +++++++++++++++---- .../ASDisplayNodeImplicitHierarchyTests.m | 14 +++----- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index eb85fdffed..f3154be92f 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -143,6 +143,8 @@ NS_ASSUME_NONNULL_BEGIN * encouraged. * * @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedLayout instead. + * + * @warning Overwriting layoutSpecThatFits: in a subclass and providing a layoutSpecBlock block is currently not supported */ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize; diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 2ec93bdad8..c631ff889d 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -42,6 +42,11 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode * _Nonnull node); */ typedef void (^ASDisplayNodeContextModifier)(_Nonnull CGContextRef context); +/** + * ASDisplayNode layout spec block. This block can be used instead of implementing layoutSpecThatFits: in subclass + */ +typedef ASLayoutSpec * _Nonnull(^ASLayoutSpecBlock)(ASSizeRange constrainedSize); + /** Interface state is available on ASDisplayNode and ASViewController, and allows checking whether a node is in an interface situation where it is prudent to trigger certain @@ -252,6 +257,17 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize; + +/** + * @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and + * implement layoutSpecThatFits: + * + * @return The block used to provide a ASLayoutSpec + * + * @warning Overwriting layoutSpecThatFits: in a subclass and providing a layoutSpecBlock block is currently not supported + */ +@property (nonatomic, readwrite, copy, nullable) ASLayoutSpecBlock layoutSpecBlock; + /** * @abstract Return the calculated size. * diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 05c433a1bc..530ad74c97 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -167,6 +167,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return overrides; } + // At most a layoutSpecBlock or one of the three layout methods is overridden +#define __ASDisplayNodeCheckForLayoutMethodOverrides \ + ASDisplayNodeAssert(_layoutSpecBlock != nil || \ + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \ + + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \ + + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0) <= 1, \ + @"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits, layoutSpecThatFits or calculateSizeThatFits", NSStringFromClass(self.class)) + + (void)initialize { if (self != [ASDisplayNode class]) { @@ -178,12 +186,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange method", NSStringFromClass(self)); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method", NSStringFromClass(self)); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearFetchedData)), @"Subclass %@ must not override recursivelyClearFetchedData method", NSStringFromClass(self)); - - // At most one of the three layout methods is overridden - ASDisplayNodeAssert((ASDisplayNodeSubclassOverridesSelector(self, @selector(calculateSizeThatFits:)) ? 1 : 0) - + (ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutSpecThatFits:)) ? 1 : 0) - + (ASDisplayNodeSubclassOverridesSelector(self, @selector(calculateLayoutThatFits:)) ? 1 : 0) <= 1, - @"Subclass %@ must override at most one of the three layout methods: calculateLayoutThatFits, layoutSpecThatFits or calculateSizeThatFits", NSStringFromClass(self)); } // Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values @@ -1848,8 +1850,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { + __ASDisplayNodeCheckForLayoutMethodOverrides; + ASDN::MutexLocker l(_propertyLock); - if (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) { + if ((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) || _layoutSpecBlock != nil) { ASLayoutSpec *layoutSpec = [self layoutSpecThatFits:constrainedSize]; layoutSpec.parent = self; // This causes upward propogation of any non-default layoutable values. layoutSpec.isMutable = NO; @@ -1876,13 +1880,22 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { + __ASDisplayNodeCheckForLayoutMethodOverrides; + ASDN::MutexLocker l(_propertyLock); return _preferredFrameSize; } - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { + __ASDisplayNodeCheckForLayoutMethodOverrides; + ASDN::MutexLocker l(_propertyLock); + + if (_layoutSpecBlock != nil) { + return _layoutSpecBlock(constrainedSize); + } + return nil; } @@ -1904,6 +1917,14 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) return _constrainedSize; } +- (void)setLayoutSpecThatFitsBlock:(ASLayoutSpecBlock)layoutSpecBlock +{ + // For now there should never be a overwrite of layoutSpecThatFits: and a layoutSpecThatFitsBlock: be provided + ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), @"Overwriting layoutSpecThatFits: and providing a layoutSpecBlock block is currently not supported"); + + _layoutSpecBlock = layoutSpecBlock; +} + - (void)setPendingTransitionID:(int32_t)pendingTransitionID { ASDN::MutexLocker l(_propertyLock); diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index b54201dc8f..f0c19773c1 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -17,8 +17,6 @@ @interface ASSpecTestDisplayNode : ASDisplayNode -@property (copy, nonatomic) ASLayoutSpec * (^layoutSpecBlock)(ASSizeRange constrainedSize, NSNumber *layoutState); - /** Simple state identifier to allow control of current spec inside of the layoutSpecBlock */ @@ -37,11 +35,6 @@ return self; } -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - return self.layoutSpecBlock(constrainedSize, _layoutState); -} - @end @interface ASDisplayNodeImplicitHierarchyTests : XCTestCase @@ -83,7 +76,7 @@ ASDisplayNode *node5 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; - node.layoutSpecBlock = ^(ASSizeRange constrainedSize, NSNumber *layoutState) { + node.layoutSpecBlock = ^(ASSizeRange constrainedSize) { ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]]; ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init]; @@ -109,8 +102,9 @@ ASDisplayNode *node3 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; - node.layoutSpecBlock = ^(ASSizeRange constrainedSize, NSNumber *layoutState){ - if ([layoutState isEqualToNumber:@1]) { + __weak ASSpecTestDisplayNode *weakNode = node; + node.layoutSpecBlock = ^(ASSizeRange constrainedSize){ + if ([weakNode.layoutState isEqualToNumber:@1]) { return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1, node2]]; } else { ASStackLayoutSpec *stackLayout = [[ASStackLayoutSpec alloc] init];