Skip to content

Latest commit

 

History

History
executable file
·
519 lines (385 loc) · 23.2 KB

layout2-conversion-guide.md

File metadata and controls

executable file
·
519 lines (385 loc) · 23.2 KB
title layout permalink
Upgrading to Layout 2.0 <b><i>(Beta)</i></b>
docs
/docs/layout2-conversion-guide.html

A list of the changes:

  • Introduction of true flex factors
  • ASStackLayoutSpec .alignItems property default changed to ASStackLayoutAlignItemsStretch
  • Rename ASStaticLayoutSpec to ASAbsoluteLayoutSpec
  • Rename ASLayoutable to ASLayoutElement
  • Set ASLayoutElement properties via style property
  • Easier way to size of an ASLayoutElement
  • Deprecation of -[ASDisplayNode preferredFrameSize]
  • Deprecation of -[ASLayoutElement measureWithSizeRange:]
  • Deprecation of -[ASDisplayNode measure:]
  • Removal of -[ASAbsoluteLayoutElement sizeRange]
  • Rename ASRelativeDimension to ASDimension
  • Introduction of ASDimensionUnitAuto

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:

  • -[ASLayoutElementStyle width]: specifies the width of an ASLayoutElement. The minWidth and maxWidth properties will override width. The height will be set to Auto unless provided.

  • -[ASLayoutElementStyle minWidth]: specifies the minimum width of an ASLayoutElement. This prevents the used value of the width property from becoming smaller than the specified for minWidth.

  • -[ASLayoutElementStyle maxWidth]: specifies the maximum width of an ASLayoutElement. It prevents the used value of the width property from becoming larger than the specified for maxWidth.

  • -[ASLayoutElementStyle height]: specifies the height of an ASLayoutElement. The minHeight and maxHeight properties will override height. The width will be set to Auto unless provided.

  • -[ASLayoutElementStyle minHeight]: specifies the minimum height of an ASLayoutElement. It prevents the used value of the height property from becoming smaller than the specified for minHeight.

  • -[ASLayoutElementStyle maxHeight]: specifies the maximum height of an ASLayoutElement. It prevents the used value of the height property from becoming larger than the specified for maxHeight.

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

  • -[ASLayoutElementStyle preferredSize]: Provides a suggested size for a layout element. If the optional minSize or maxSize are provided, and the preferredSize exceeds these, the minSize or maxSize will be enforced. If this optional value is not provided, the layout element’s size will default to it’s intrinsic content size provided calculateSizeThatFits:

  • -[ASLayoutElementStyle minSize]: An optional property that provides a minimum size bound for a layout element. If provided, this restriction will always be enforced. If a parent layout element’s minimum size is smaller than its child’s minimum size, the child’s minimum size will be enforced and its size will extend out of the layout spec’s.

  • -[ASLayoutElementStyle maxSize]: An optional property that provides a maximum size bound for a layout element. If provided, this restriction will always be enforced. If a child layout element’s maximum size is smaller than its parent, the child’s maximum size will be enforced and its size will extend out of the layout spec’s.

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

  • -[ASLayoutElementStyle preferredRelativeSize]: Provides a suggested RELATIVE size for a layout element. An ASRelativeSize uses percentages rather than points to specify layout. E.g. width should be 50% of the parent’s width. If the optional minRelativeSize or maxRelativeSize are provided, and the preferredRelativeSize exceeds these, the minRelativeSize or maxRelativeSize will be enforced. If this optional value is not provided, the layout element’s size will default to its intrinsic content size provided calculateSizeThatFits:

  • -[ASLayoutElementStyle minRelativeSize]: An optional property that provides a minimum RELATIVE size bound for a layout element. If provided, this restriction will always be enforced. If a parent layout element’s minimum relative size is smaller than its child’s minimum relative size, the child’s minimum relative size will be enforced and its size will extend out of the layout spec’s.

  • -[ASLayoutElementStyle maxRelativeSize]: An optional property that provides a maximum RELATIVE size bound for a layout element. If provided, this restriction will always be enforced. If a parent layout element’s maximum relative size is smaller than its child’s maximum relative size, the child’s maximum relative size will be enforced and its size will extend out of the layout spec’s.

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;

// 1.x
let size = displayNode.measure(CGSize(width: 100, height: 100))

// 2.0:
// Creates an ASSizeRange with min and max sizes.
let layout = displayNode.layoutThatFits(ASSizeRange(min: CGSizeZero, max: CGSize(width: 100, height: 100)))
// Or an exact size
// let layout = displayNode.layoutThatFits(ASSizeRangeMake(CGSize(width: 100, height: 100)))
let size = layout.size

Remove of -[ASAbsoluteLayoutElement sizeRange]

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

  • -[ASLayoutElement width]
  • -[ASLayoutElement height]
  • -[ASLayoutElement minWidth]
  • -[ASLayoutElement minHeight]
  • -[ASLayoutElement maxWidth]
  • -[ASLayoutElement maxHeight]
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 ``ASLayoutElementStylesizing properties are set toASDimensionAuto` 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);