Add a block API to provide an ASLayoutSpec without having to subclass ASDisplayNode

This commit is contained in:
Michael Schneider
2016-04-20 19:16:54 -07:00
parent dd4853bf3a
commit 87a37a283e
4 changed files with 50 additions and 17 deletions

View File

@@ -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;

View File

@@ -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.
*

View File

@@ -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);

View File

@@ -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];