AsyncDisplayKit is now Texture! LEARN MORE

Texture

Upgrading to Layout 2.0 (Beta)

A list of the changes:

In addition to the inline examples comparing 1.x layout code vs 2.0 layout code, the example projects and layout documentation have been updated to use the new API.

All other 2.0 changes not related to the Layout API are documented here.

Introduction of true flex factors

With 1.x the flexGrow and flexShrink properties were of type BOOL.

With 2.0, these properties are now type CGFloat with default values of 0.0.

This behavior is consistent with the Flexbox implementation for web. See flexGrow and flexShrink for further information.

SwiftObjective-C
id<ASLayoutElement> layoutElement = ...;

// 1.x:
layoutElement.flexGrow = YES;
layoutElement.flexShrink = YES;

// 2.0:
layoutElement.style.flexGrow = 1.0;
layoutElement.style.flexShrink = 1.0;

ASStackLayoutSpec’s .alignItems property default changed

ASStackLayoutSpec’s .alignItems property default changed to ASStackLayoutAlignItemsStretch instead of ASStackLayoutAlignItemsStart to align with the CSS align-items property.

Rename ASStaticLayoutSpec to ASAbsoluteLayoutSpec & behavior change

ASStaticLayoutSpec has been renamed to ASAbsoluteLayoutSpec, to be consistent with web terminology and better represent the intended behavior.

SwiftObjective-C
// 1.x:
ASStaticLayoutSpec *layoutSpec = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[...]];

// 2.0:
ASAbsoluteLayoutSpec *layoutSpec = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[...]];


Please note that there has also been a behavior change introduced. The following text overlay layout was previously created using a ASStaticLayoutSpec, ASInsetLayoutSpec and ASOverlayLayoutSpec as seen in the code below.


Using INFINITY for the top value in the UIEdgeInsets property of the ASInsetLayoutSpec allowed the text inset to start at the bottom. This was possible because it would adopt the size of the static layout spec’s _photoNode.

Swift Objective-C
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  _photoNode.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT*2, USER_IMAGE_HEIGHT*2);
  ASStaticLayoutSpec *backgroundImageStaticSpec = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[_photoNode]];

  UIEdgeInsets insets = UIEdgeInsetsMake(INFINITY, 12, 12, 12);
  ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:_titleNode];

  ASOverlayLayoutSpec *textOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:backgroundImageStaticSpec
                                                                                 overlay:textInsetSpec];
  
  return textOverlaySpec;
}
  


With the new ASAbsoluteLayoutSpec and same code above, the layout would now look like the picture below. The text is still there, but at ~900 pts (offscreen).

Rename ASLayoutable to ASLayoutElement

Remember that an ASLayoutSpec contains children that conform to the ASLayoutElement protocol. Both ASDisplayNodes and ASLayoutSpecs conform to this protocol.

The protocol has remained the same as 1.x, but the name has been changed to be more descriptive.

Set ASLayoutElement properties via ASLayoutElementStyle

An ASLayoutElement’s properties are are now set via it’s ASLayoutElementStyle object.

SwiftObjective-C
id<ASLayoutElement> *layoutElement = ...;

// 1.x:
layoutElement.spacingBefore = 1.0;

// 2.0:
layoutElement.style.spacingBefore = 1.0;

However, the properties specific to an ASLayoutSpec are still set directly on the layout spec.

SwiftObjective-C
// 1.x and 2.0
ASStackLayoutSpec *stackLayoutSpec = ...;
stackLayoutSpec.direction = ASStackLayoutDirectionVertical;
stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentStart;

Setting the size of an ASLayoutElement

With 2.0 we introduce a new, easier, way to set the size of an ASLayoutElement. These methods replace the deprecated -preferredFrameSize and -sizeRange 1.x methods.

The following optional properties are provided via the layout element’s style property:

To set both the width and height with a CGSize value:

To set both the width and height with a relative (%) value (an ASRelativeSize):

For example, if you want to set a width of an ASDisplayNode:

SwiftObjective-C
// 1.x:
// no good way to set an intrinsic size

// 2.0:
ASDisplayNode *ASDisplayNode = ...;

// width 100 points, height: auto
displayNode.style.width = ASDimensionMakeWithPoints(100);

// width 50%, height: auto
displayNode.style.width = ASDimensionMakeWithFraction(0.5);

ASLayoutSpec *layoutSpec = ...;

// width 100 points, height 100 points
layoutSpec.style.preferredSize = CGSizeMake(100, 100);

If you previously wrapped an ASLayoutElement with an ASStaticLayoutSpec just to give it a specific size (without setting the layoutPosition property on the element too), you don’t have to do that anymore.

SwiftObjective-C
ASStackLayoutSpec *stackLayoutSpec = ...;
id<ASLayoutElement> *layoutElement = ...;

// 1.x:
layoutElement.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(50, 50));
ASStaticLayoutSpec *staticLayoutSpec = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[layoutElement]];
stackLayoutSpec.children = @[staticLayoutSpec];

// 2.0:
layoutElement.style.preferredSizeRange = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(50, 50));
stackLayoutSpec.children = @[layoutElement];

If you previously wrapped a ASLayoutElement within a ASStaticLayoutSpec just to return any layout spec from within layoutSpecThatFits: there is a new layout spec now that is called ASWrapperLayoutSpec. ASWrapperLayoutSpec is an ASLayoutSpec subclass that can wrap a ASLayoutElement and calculates the layout of the child based on the size given to the ASLayoutElement:

SwiftObjective-C
// 1.x - ASStaticLayoutSpec used as a "wrapper" to return subnode from layoutSpecThatFits: 
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[subnode]];
}

// 2.0
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  return [ASWrapperLayoutSpec wrapperWithLayoutElement:subnode];
}

// 1.x - ASStaticLayoutSpec used to set size (but not position) of subnode
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  ASDisplayNode *subnode = ...;
  subnode.preferredSize = ...;
  return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[subnode]];
}

// 2.0
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
  ASDisplayNode *subnode = ...;
  subnode.style.preferredSize = CGSizeMake(constrainedSize.max.width, constrainedSize.max.height / 2.0);
  return [ASWrapperLayoutSpec wrapperWithLayoutElement:subnode];
}

Deprecation of -[ASDisplayNode preferredFrameSize]

With the introduction of new sizing properties there is no need anymore for the -[ASDisplayNode preferredFrameSize] property. Therefore it is deprecated in 2.0. Instead, use the size values on the style object of an ASDisplayNode:

SwiftObjective-C
 
ASDisplayNode *ASDisplayNode = ...;

// 1.x:
displayNode.preferredFrameSize = CGSize(100, 100);

// 2.0
displayNode.style.preferredSize = CGSize(100, 100);

-[ASDisplayNode preferredFrameSize] was not supported properly and was often more confusing than helpful. The new sizing methods should be easier and more clear to implment.

Deprecation of -[ASLayoutElement measureWithSizeRange:]

-[ASLayoutElement measureWithSizeRange:] is deprecated in 2.0.

Calling measureWithSizeRange:

If you previously called -[ASLayoutElement measureWithSizeRange:] to receive an ASLayout, call -[ASLayoutElement layoutThatFits:] now instead.

SwiftObjective-C
// 1.x:
ASLayout *layout = [layoutElement measureWithSizeRange:someSizeRange];

// 2.0:
ASLayout *layout = [layoutElement layoutThatFits:someSizeRange];

Implementing measureWithSizeRange:

If you are implementing a custom class that conforms to ASLayoutElement (e.g. creating a custom ASLayoutSpec) , replace -measureWithSizeRange: with -calculateLayoutThatFits:

SwiftObjective-C
// 1.x:
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize {}

// 2.0:
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize {}

-calculateLayoutThatFits: takes an ASSizeRange that specifies a min size and a max size of type CGSize. Choose any size in the given range, to calculate the children’s size and position and return a ASLayout structure with the layout of child components.

Besides -calculateLayoutThatFits: there are two additional methods on ASLayoutElement that you should know about if you are implementing classes that conform to ASLayoutElement:

SwiftObjective-C
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
                     restrictedToSize:(ASLayoutElementSize)size
                 relativeToParentSize:(CGSize)parentSize;

In certain advanced cases, you may want to override this method. Overriding this method allows you to receive the layoutElement’s size, parent size, and constrained size. With these values you could calculate the final constrained size and call -calculateLayoutThatFits: with the result.

SwiftObjective-C
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize
                  parentSize:(CGSize)parentSize;

Call this on childrenlayoutElements to compute their layouts within your implementation of -calculateLayoutThatFits:.

For sample implementations of layout specs and the usage of the calculateLayoutThatFits: family of methods, check out the layout specs in Texture itself!

Deprecation of -[ASDisplayNode measure:]

Use -[ASDisplayNode layoutThatFits:] instead to get an ASLayout and call size on the returned ASLayout:

SwiftObjective-C
// 1.x:
CGSize size = [displayNode measure:CGSizeMake(100, 100)];

// 2.0:
// Creates an ASSizeRange with min and max sizes.
ASLayout *layout = [displayNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 100))];
// Or an exact size
// ASLayout *layout = [displayNode layoutThatFits:ASSizeRangeMake(CGSizeMake(100, 100))];
CGSize size = layout.size;

Remove of -[ASAbsoluteLayoutElement sizeRange]

The sizeRange property was removed from the ASAbsoluteLayoutElement protocol. Instead set the one of the following:

SwiftObjective-C
id<ASLayoutElement> layoutElement = ...;

// 1.x:
layoutElement.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(50, 50));

// 2.0:
layoutElement.style.preferredSizeRange = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(50, 50));

Due to the removal of -[ASAbsoluteLayoutElement sizeRange], we also removed the ASRelativeSizeRange, as the type was no longer needed.

Rename ASRelativeDimension to ASDimension

To simplify the naming and support the fact that dimensions are widely used in Texture now, ASRelativeDimension was renamed to ASDimension. Having a shorter name and handy functions to create it was an important goal for us.

ASRelativeDimensionTypePercent and associated functions were renamed to use Fraction to be consistent with Apple terminology.

SwiftObjective-C
// 2.0:
// Handy functions to create ASDimensions
ASDimension dimensionInPoints;
dimensionInPoints = ASDimensionMake(ASDimensionTypePoints, 5.0)
dimensionInPoints = ASDimensionMake(5.0)
dimensionInPoints = ASDimensionMakeWithPoints(5.0)
dimensionInPoints = ASDimensionMake("5.0pt");

ASDimension dimensionInFractions;
dimensionInFractions = ASDimensionMake(ASDimensionTypeFraction, 0.5)
dimensionInFractions = ASDimensionMakeWithFraction(0.5)
dimensionInFractions = ASDimensionMake("50%");

Introduction of ASDimensionUnitAuto

Previously ASDimensionUnitPoints and ASDimensionUnitFraction were the only two ASDimensionUnit enum values available. A new dimension type called ASDimensionUnitAuto now exists. All of the ``ASLayoutElementStyle sizing properties are set to ASDimensionAuto` by default.

ASDimensionUnitAuto means more or less: “I have no opinion” and may be resolved in whatever way makes most sense given the circumstances.

Most of the time this is the intrinsic content size of the ASLayoutElement.

For example, if an ASImageNode has a width set to ASDimensionUnitAuto, the width of the linked image file will be used. For an ASTextNode the intrinsic content size will be calculated based on the text content. If an ASLayoutElement cannot provide any intrinsic content size like ASVideoNode for example the size needs to set explicitly.

SwiftObjective-C
// 2.0:
// No specific size needs to be set as the imageNode's size 
// will be calculated from the content (the image in this case)
ASImageNode *imageNode = [ASImageNode new];
imageNode.image = ...;

// Specific size must be set for ASLayoutElement objects that
// do not have an intrinsic content size (ASVideoNode does not
// have a size until it's video downloads)
ASVideoNode *videoNode = [ASVideoNode new];
videoNode.style.preferredSize = CGSizeMake(200, 100);

Edit on GitHub