Upgrading to Layout 2.0 (Beta)
A list of the changes:
- Introduction of true flex factors
ASStackLayoutSpec
.alignItems
property default changed toASStackLayoutAlignItemsStretch
- Rename
ASStaticLayoutSpec
toASAbsoluteLayoutSpec
- Rename
ASLayoutable
toASLayoutElement
- Set
ASLayoutElement
properties viastyle
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
toASDimension
- 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.
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.
// 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
.
- (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.
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.
// 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. TheminWidth
andmaxWidth
properties will overridewidth
. The height will be set to Auto unless provided. -
-[ASLayoutElementStyle minWidth]
: specifies the minimum width of an ASLayoutElement. This prevents the used value of thewidth
property from becoming smaller than the specified forminWidth
. -
-[ASLayoutElementStyle maxWidth]
: specifies the maximum width of an ASLayoutElement. It prevents the used value of thewidth
property from becoming larger than the specified formaxWidth
. -
-[ASLayoutElementStyle height]
: specifies the height of an ASLayoutElement. TheminHeight
andmaxHeight
properties will overrideheight
. The width will be set to Auto unless provided. -
-[ASLayoutElementStyle minHeight]
: specifies the minimum height of an ASLayoutElement. It prevents the used value of theheight
property from becoming smaller than the specified forminHeight
. -
-[ASLayoutElementStyle maxHeight]
: specifies the maximum height of an ASLayoutElement. It prevents the used value of theheight
property from becoming larger than the specified formaxHeight
.
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
:
// 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.
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
:
// 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
:
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.
// 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:
// 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
:
- (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.
- (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
:
// 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:
-[ASLayoutElement width]
-[ASLayoutElement height]
-[ASLayoutElement minWidth]
-[ASLayoutElement minHeight]
-[ASLayoutElement maxWidth]
-[ASLayoutElement maxHeight]
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.
// 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.
// 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);