The official document links: texturegroup.org/docs/gettin…

Layout Quick Start

AsyncDisplayKit/Texture

Develop original intention and advantage

The Layout API is designed to provide a high-performance alternative to UIKit Auto Layout, which increases exponentially in complex view structures. The Texture Layout scheme has the following advantages over Auto Layout:

  • Fast: The Texture layout calculation is as fast as the handwritten Frame;
  • Asynchronous and concurrent: Layouts can be computed on background threads without interrupting user interactions;
  • Declarative rendering: Layouts are declared using immutable data structures, which makes layout code easier to develop, maintain, debug, test, and review;
  • Cacheable: If the layout is immutable, it can be pre-computed and cached in the background, which makes the user feel faster.
  • Extensible: It becomes convenient to use the same layout in different classes;

Inspired by CSS Flexbox

Those familiar with Flexbox will notice that there are many similarities between the two systems, but the Layout API does not reimplement all CSS.

The basic concept

Texture’s layout is built around two main concepts:

  1. Layout rules
  2. Layout elements

Layout rules /Layout Specs

The layout rule, which has no physical existence, arranges the placement of LayoutElements by acting as a container for LayoutElements, understanding the relationships between multiple LayoutElements. Texture provides several subclasses of ASLayoutSpec, ranging from simple rules for inserting a single layout element to complex rules that can vary the stack arrangement configuration to include multiple layout elements.

/Layout Elements

LayoutSpecs contain and tidy up LayoutElements.

All AsDisplayNodes and AslayoutSpecs comply with the

protocol, which means you can generate or combine a new LayoutSpecs from two Nodes and other LayoutSpecs.

The

protocol has some properties for creating very complex layouts. In addition, LayoutSpecs have their own set of attributes that adjust the arrangement of layout elements.

Create complex interfaces that combine layout rules and layout elements

Here you can see how the yellow highlighted ASTextNodes, the top image ASVideoNode and the box layout rule ASStackLayoutSpec are combined to create a complex interface.

ASCenterLayoutSpec and ASOverlayLayoutSpec are used to place the play button in the top image ASVideoNode.

Some nodes require size

Elements have an inherent size based on their immediate availability. For example, ASTextNode can determine its own size based on the.string attribute. Other nodes have an inherent size:

  • ASImageNode
  • ASTextNode
  • ASButtonNode

All other nodes either have no intrinsic size or lack a intrinsic size before loading external resources. For example, the ASNetworkImageNode does not determine the size of an image until it is downloaded from a URL. These elements include:

  • ASVideoNode
  • ASVideoPlayerNode
  • ASNetworkImageNode
  • ASEditableTextNode

These nodes that lack an initial inherent size must have their initial size set using ASRatioLayoutSpec(scale layout rule), ASAbsoluteLayoutSpec(Absolute layout rule), or the.size property of the style object.

Layout Debugging /Layout Debugging

Calling -asciiArtString on any ASDisplayNode or ASLayoutSpec returns a character graph of the object and its children. You can also set.debugName in any Node or layoutSpec, which will also contain character diagrams. Here’s an example:

-----------------------ASStackLayoutSpec---------------------- | -----ASStackLayoutSpec----- -----ASStackLayoutSpec-----  | | | ASImageNode | | ASImageNode | | | | ASImageNode | | ASImageNode | | | --------------------------- --------------------------- | --------------------------------------------------------------Copy the code

You can also print style objects on any ASLayoutElement, such as Node and layoutSpec, which is especially useful when debugging the.size property.

(lldb) po _photoImageNode.style
Layout Size = min {414pt, 414pt} <= preferred {20%, 50%} <= max {414pt, 414pt}Copy the code

Sample layout

Click to see a sample project of layoutSpec.

Simple text left and right aligned

To create a layout for this, we will use:

  • Denotes verticalASStackLayoutSpec
  • horizontalASStackLayoutSpec
  • captionedASInsetLayoutSpec

The following illustration shows a layout element consisting of nodes and LayoutSpecs:

class TZYVC: ASViewController<ASDisplayNode> {
    init() {
        let node = TZYNode(a)super.init(node: node)
    }

    override func viewDidLoad(a) {
        super.viewDidLoad()
        node.backgroundColor = UIColor.red
    }
}

///////////////////////////////////////////////////

class TZYNode: ASDisplayNode {

    // SAN Fran CA in the figure
    lazy var postLocationNode: ASTextNode = {
        return ASTextNode() ()}// HannahMbanana
    lazy var userNameNode: ASTextNode = {
        return ASTextNode() ()}// 30m in figure
    lazy var postTimeNode: ASTextNode = {
        return ASTextNode() ()}override init() {
        super.init(a)self.postLocationNode.attributedText = NSAttributedString(string: "san fran ca")
        self.userNameNode.attributedText = NSAttributedString(string: "hannahmbanana")
        self.postTimeNode.attributedText = NSAttributedString(string: "30m")
        addSubnode(postLocationNode)
        addSubnode(userNameNode)
        addSubnode(postTimeNode)
        postTimeNode.backgroundColor = .brown
        userNameNode.backgroundColor = .cyan
        postLocationNode.backgroundColor = .green
    }

    override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
        // Declare a vertical box
        let nameLoctionStack = ASStackLayoutSpec.vertical()
        // Defines the scaling of the project. The default is 1, that is, the project will shrink if there is not enough space
        // If all elements are 1 and space is insufficient, all elements are equally scaled
        // If one of them is 0, the element does not scale, and the other elements divide the remaining space equally
        nameLoctionStack.style.flexShrink = 1.0
        // Define the element's zoom scale. Default is 0, that is, if there is free space, no zoom
        // If all elements are 1, divide the remaining space equally
        // If one of them is 2, that element takes up twice as much space as the others
        nameLoctionStack.style.flexGrow = 1.0
        // Determine whether to add node to the view according to whether the location address is assigned a value
        ifpostLocationNode.attributedText ! =nil {
            nameLoctionStack.children = [userNameNode, postLocationNode]
        }
        else {
            nameLoctionStack.children = [userNameNode]
        }
        // Declare a horizontal box
        Horizontal the axis is horizontal
        The spacing of its children is 40
        // justifyContent:.start was left to right on the spindle
        // alignItems:.center is centered in the secondary, or vertical, axis
        // children: [nameLoctionStack, postTimeNode] contains children
        let headerStackSpec = ASStackLayoutSpec(direction: .horizontal,
                                                spacing: 40,
                                                justifyContent: .start,
                                                alignItems: .center,
                                                children: [nameLoctionStack, postTimeNode])
        // Insert layout rules
        return ASInsetLayoutSpec(insets: UIEdgeInsets(top: 0.left: 10, bottom: 0.right: 10), child: headerStackSpec)
    }
}Copy the code

Transform the sample project from portrait to landscape to see how the interval grows and shrinks.

Overlay text on the image

To create this layout, we will use:

  • Used to insert textASInsetLayoutSpec
  • Overlays the text onto the imageASOverlayLayoutSpec
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
  let photoDimension: CGFloat = constrainedSize.max.width / 4.0
  photoNode.style.preferredSize = CGSize(width: photoDimension, height: photoDimension)
  // cgfloat. infinity sets the titleNode top margin to infinity
  let insets = UIEdgeInsets(top: CGFloat.infinity, left: 12, bottom: 12.right: 12)
  let textInsetSpec = ASInsetLayoutSpec(insets: insets, child: titleNode)
  return ASOverlayLayoutSpec(child: photoNode, overlay: textInsetSpec)
}Copy the code

Overlay ICONS on pictures

To create this layout, we will use:

  • setsizepositionASLayoutableProperties;
  • Used to place pictures and ICONSASAbsoluteLayoutSpec
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
  iconNode.style.preferredSize = CGSize(width: 40, height: 40);
  iconNode.style.layoutPosition = CGPoint(x: 150, y: 0);
  photoNode.style.preferredSize = CGSize(width: 150, height: 150);
  photoNode.style.layoutPosition = CGPoint(x: 40 / 2.0, y: 40 / 2.0);
  let absoluteSpec = ASAbsoluteLayoutSpec(children: [photoNode, iconNode])
  // The sizing property of ASAbsoluteLayoutSpec recreates the ASStaticLayoutSpec of the Texture Layout API 1.0
  absoluteSpec.sizing = .sizeToFit
  return absoluteSpec;
}Copy the code

Simply insert text cells

To create a single cell layout similar to Pinterest’s search view, we’ll use:

  • Used to insert textASInsetLayoutSpec
  • Centers the text based on the specified attributeASCenterLayoutSpec
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
    let insets = UIEdgeInsets(top: 0.left: 12, bottom: 4.right: 4)
    let inset = ASInsetLayoutSpec(insets: insets, child: _titleNode)
    return ASCenterLayoutSpec(centeringOptions: .Y, sizingOptions: .minimumX, child: inset)
}Copy the code

The dividing line between the top and bottom

To create a layout like the one above, we need to use:

  • Used to insert textASInsetLayoutSpec
  • Used to add dividers at the top and bottom of text, verticalASStackLayoutSpec

The following illustration shows how a Layoutables is made up of layoutSpecs and nodes:

The following code can also be found in the sample project ASLayoutSpecPlayground.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
  topSeparator.style.flexGrow = 1.0
  bottomSeparator.style.flexGrow = 1.0
  textNode.style.alignSelf = .center
  let verticalStackSpec = ASStackLayoutSpec.vertical()
  verticalStackSpec.spacing = 20
  verticalStackSpec.justifyContent = .center
  verticalStackSpec.children = [topSeparator, textNode, bottomSeparator]
  return ASInsetLayoutSpec(insets:UIEdgeInsets(top: 60.left: 0, bottom: 60.right: 0), child: verticalStackSpec)
}Copy the code

Layout rules /Layout Specs

The following ASLayoutSpec subclasses can be used to compose simple or very complex layouts:

The rules describe
ASWrapperLayoutSpec Fill in the layout
ASStackLayoutSpec The box layout
ASInsetLayoutSpec Insert the layout
ASOverlayLayoutSpec Cover layout
ASBackgroundLayoutSpec Background layout
ASCenterLayoutSpec Center layout
ASRatioLayoutSpec Proportion of layout
ASRelativeLayoutSpec The vertex arrangement
ASAbsoluteLayoutSpec Absolute layout

You can also create a subclass of ASLayoutSpec to make your own layout rules.

ASWrapperLayoutSpec

ASWrapperLayoutSpec is a simple ASLayoutSpec subclass that encapsulates a LayoutElement and calculates its layout and subelement layout based on the size set on the LayoutElement.

ASWrapperLayoutSpec can easily return a SubNode from -LayoutSpecthatFits:. You can set size on the subnode, but if you need to set.position, use ASAbsoluteLayoutSpec.

// Return a subnode
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec 
{
  return ASWrapperLayoutSpec(layoutElement: _subnode)
}

// Set size, but not position.
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec 
{
  _subnode.style.preferredSize = CGSize(width: constrainedSize.max.width,
                                        height: constrainedSize.max.height / 2.0)
  return ASWrapperLayoutSpec(layoutElement: _subnode)
}Copy the code

ASStackLayoutSpec (Flexbox Container)

Of all the LayoutSpecs in Texture, the ASStackLayoutSpec is the most useful and powerful. ASStackLayoutSpec uses flexbox to determine the size and position of its children. Flexbox is designed to provide a consistent layout for different screen sizes, in a box layout, you pair its elements vertically or horizontally. A box layout can also be a child of another box, making the box layout rules applicable to almost any layout.

In addition to the ASLayoutElement attribute, ASStackLayoutSpec has seven other attributes:

  • direction

    Specifies the sorting orientation of child elements, which will be parsed again if the horizontalAlignment and verticalAlignment were set, causing justifyContent and alignItems to be updated accordingly.

  • spacing

    Describes the distances between child elements

  • horizontalAlignment

    Specifies how child elements are aligned horizontally. The actual effect of this depends on the direction. Setting an alignment would leave justifyContent or alignItems updated. Alignment remains in effect even after the direction is changed, so this is a high-priority attribute.

  • verticalAlignment

    Specifies how child elements are vertically aligned. The actual effect of this depends on the direction. Setting an alignment would leave justifyContent or alignItems updated. Alignment remains in effect even after the direction is changed, so this is a high-priority attribute.

  • justifyContent

    Describes the distances between child elements.

  • alignItems

    Describes the orientation of child elements on the cross axis.

Spacing and justifyContent both justify The amount of space between each child.

Spacing was correctly translated as I understood it, but justifyContent felt inaccurate. Readers can consult the CSS documentation to understand the attributes themselves.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec 
{
  let mainStack = ASStackLayoutSpec(direction: .horizontal,
                                    spacing: 6.0,
                                    justifyContent: .start,
                                    alignItems: .center,
                                    children: [titleNode, subtitleNode])

  // Set the box constraint size
  mainStack.style.minWidth = ASDimensionMakeWithPoints(60.0)
  mainStack.style.maxHeight = ASDimensionMakeWithPoints(40.0)

  return mainStack
}Copy the code

Flexbox works the same way on the Web as it does in CSS, with some exceptions. For example, the default values are different with no Flex parameters, see Web Flexbox Differences for more information.

ASInsetLayoutSpec

During layout, ASInsetLayoutSpec passes its constrainedsize. Max minus the CGSize of its insets to its children. Once the children have determined its size, InsetSpec takes its final size as the size and margin of its children.

Since ASInsetLayoutSpec is determined by the size of its children, the children must have an inherent size or have their size explicitly set.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec{...let insets = UIEdgeInsets(top: 10.0.left: 10.0, bottom: 10.0.right: 10.0)
  let headerWithInset = ASInsetLayoutSpec(insets: insets, child: textNode)
  ...
}Copy the code

If you set a value in UIEdgeInsets to INFINITY, insetSpec will only use the inherent size of the child node, see the example of overwriting text on an image.

ASOverlayLayoutSpec

ASOverlayLayoutSpec extends the child node above it (red), overwriting a child node (blue).

The size of the overlaySpec is calculated from the size of the child node, which is the blue layer in the figure below, and then passed to the overlay layout element as constrainedSize (red), so it is important to note that, The child node (blue) must have an inherent size or be explicitly set to size.

When using ASOverlayLayoutSpec for automatic child node management, nodes sometimes show the wrong order, which is a known problem and will be fixed soon. The current solution is to add nodes manually, and the layout element (red) must be added as a child node to the child node (blue) after the parent node.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
  let backgroundNode = ASDisplayNodeWithBackgroundColor(UIColor.blue)
  let foregroundNode = ASDisplayNodeWithBackgroundColor(UIColor.red)
  return ASOverlayLayoutSpec(child: backgroundNode, overlay: foregroundNode)
}Copy the code

ASBackgroundLayoutSpec

ASBackgroundLayoutSpec sets one child node (blue) as the content and stretches the other child node behind it as the background (red).

The size of the ASBackgroundLayoutSpec is determined by the size of the child. In the following figure, the child is the blue layer, and the size of the child is passed to the background layer as constrainedSize (red), so it is important to note that The child node (blue) must have an inherent size or be explicitly set to size.

When using ASOverlayLayoutSpec for automatic child node management, nodes sometimes show the wrong order, which is a known problem and will be fixed soon. The current solution is to add nodes manually, and the layout element (blue) must be added as a child node to the child node (red) after the parent node.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
  let backgroundNode = ASDisplayNodeWithBackgroundColor(UIColor.red)
  let foregroundNode = ASDisplayNodeWithBackgroundColor(UIColor.blue)
  return ASBackgroundLayoutSpec(child: foregroundNode, background: backgroundNode)
}Copy the code

Note: The order in which children are added is important for this layout rule. The background object must be added as a child node to the parent node before the foreground object, and currently using ASM does not guarantee that this order is correct!

ASCenterLayoutSpec

ASCenterLayoutSpec sets the center of its child node to the center of the largest constrainedSize.

If ASCenterLayoutSpec has no constraints on its width or height, it will scale to match the width or height of the child node.

ASCenterLayoutSpec has two properties:

  • CenteringOptions:

    Determines how the child nodes are centered in ASCenterLayoutSpec. The options include: None, X, Y, XY.

  • SizingOptions:

    Determines how much space ASCenterLayoutSpec occupies. Options include: Default, minimum X, minimum Y, minimum XY.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
  let subnode = ASDisplayNodeWithBackgroundColor(UIColor.green, CGSize(width: 60.0, height: 100.0))
  let centerSpec = ASCenterLayoutSpec(centeringOptions: .XY, sizingOptions: [], child: subnode)
  return centerSpec
}Copy the code

ASRatioLayoutSpec

ASRatioLayoutSpec scales child nodes at a fixed aspect ratio. This rule must pass it a width or height as a constrainedSize because it uses this value for its calculations.

It is very common to use ASRatioLayoutSpec to provide a proper size for ASNetworkImageNode or ASVideoNode, as neither has a proper size until the content is returned from the server.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
  // Scale the subnode by half
  let subnode = ASDisplayNodeWithBackgroundColor(UIColor.green, CGSize(width: 100, height: 100.0))
  let ratioSpec = ASRatioLayoutSpec(ratio: 0.5, child: subnode)
  return ratioSpec
}Copy the code

ASRelativeLayoutSpec

Place a child node in any of the nine grid layout rules, depending on the horizontal and vertical positions.

This is a very powerful layout rule, but it is too complex to cover in detail in this overview, see the -CalculatelayOutThatFits: Methods and Properties of ASRelativeLayoutSpec for more information.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec{...let backgroundNode = ASDisplayNodeWithBackgroundColor(UIColor.blue)
  let foregroundNode = ASDisplayNodeWithBackgroundColor(UIColor.red, CGSize(width: 70.0, height: 100.0))

  let relativeSpec = ASRelativeLayoutSpec(horizontalPosition: .start,
                                          verticalPosition: .start,
                                          sizingOption: [],
                                          child: foregroundNode)

  let backgroundSpec = ASBackgroundLayoutSpec(child: relativeSpec, background: backgroundNode)
  ...
}Copy the code

ASAbsoluteLayoutSpec

ASAbsoluteLayoutSpec you can specify the horizontal and vertical coordinates of their children by setting their layoutPosition property. Absolute layouts are less flexible and difficult to maintain than other types of layouts.

ASAbsoluteLayoutSpec has one property:

  • Sizing:

    Determine how much space ASAbsoluteLayoutSpec will occupy. Possible values include Default, Size to Fit. Note that Size to Fit will copy the behavior of the old ASStaticLayoutSpec.

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec
{
  let maxConstrainedSize = constrainedSize.max

  // In a static layout, use ASAbsoluteLayoutSpec to lay out all child nodes
  guitarVideoNode.style.layoutPosition = CGPoint.zero
  guitarVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width, height: maxConstrainedSize.height / 3.0)

  nicCageVideoNode.style.layoutPosition = CGPoint(x: maxConstrainedSize.width / 2.0, y: maxConstrainedSize.height / 3.0)
  nicCageVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width / 2.0, height: maxConstrainedSize.height / 3.0)

  simonVideoNode.style.layoutPosition = CGPoint(x: 0.0, y: maxConstrainedSize.height - (maxConstrainedSize.height / 3.0))
  simonVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width / 2.0, height: maxConstrainedSize.height / 3.0)

  hlsVideoNode.style.layoutPosition = CGPoint(x: 0.0, y: maxConstrainedSize.height / 3.0)
  hlsVideoNode.style.preferredSize = CGSize(width: maxConstrainedSize.width / 2.0, height: maxConstrainedSize.height / 3.0)

  return ASAbsoluteLayoutSpec(children: [guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode])
}Copy the code

ASLayoutSpec

ASLayoutSpec is the parent class of all layout rules. Its main job is to handle and manage all the subclasses. It can also be used to create custom layout rules. However, creating a custom subclass of ASLayoutSpec is a super advanced level operation. If you need to do this, we recommend that you try to combine the layout rules we provide to create a more advanced layout.

Another use of ASLayoutSpec is to apply.flexshrink or.flexgrow, which is used in ASStackLayoutSpec as a spacer with other child nodes,

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec{...let spacer = ASLayoutSpec()
  spacer.style.flexGrow = 1.0

  stack.children = [imageNode, spacer, textNode]
  ...
}Copy the code

/Layout Element Properties

  • ASStackLayoutElement Properties: only in the box layoutsubnodelayoutSpecIn effect;
  • ASAbsoluteLayoutElement Properties: Only in absolute layoutsubnodelayoutSpecIn effect;
  • ASLayoutElement Properties: Applies to allNodelayoutSpec;

ASStackLayoutElement Properties

Please note that the following properties are available only inASStackLayoutsubnodeThe Settings will only take effect.

.style.spacingBefore

Type CGFloat, spacing from previous node in direction.

.style.spacingAfter

Type CGFloat, spacing with the following node on direction.

.style.flexGrow

Bool indicates whether to enlarge the size of the child nodes if the total size of the child nodes is less than minimum.

.style.flexShrink

Bool indicates whether the total number of child nodes is greater than maximum.

.style.flexBasis

ASDimension type, which describes the initial vertical or horizontal size of the object in the box before applying the flexGrow or flexShrink attribute if the remaining space is evenly divided,

.style.alignSelf

The ASStackLayoutAlignSelf type describes the orientation of the object on the cross. This property overrides alignItems.

  • ASStackLayoutAlignSelfAuto
  • ASStackLayoutAlignSelfStart
  • ASStackLayoutAlignSelfEnd
  • ASStackLayoutAlignSelfCenter
  • ASStackLayoutAlignSelfStretch

.style.ascender

The CGFloat type, used for baseline alignment, describes the distance of the object from the top to its baseline.

.style.descender

The CGFloat type, used for baseline alignment, describes the distance of the object from the baseline to its bottom.

ASAbsoluteLayoutElement Properties

Please note that the following properties are available only inAbsoluteLayoutsubnodeThe Settings will only take effect.

.style.layoutPosition

CGPoint type that describes the position of the object in the ASAbsoluteLayoutSpec parent rule.

ASLayoutElement Properties

Note that the following attributes apply to all layout elements.

.style.width

The ASDimension, width property describes the width of the ASLayoutElement’s content area. The minWidth and maxWidth attributes override width, and the default is ASDimensionAuto.

.style.height

The ASDimension, height property describes the height of the ASLayoutElement’s content area. The minHeight and maxHeight attributes override height, with the default value ASDimensionAuto.

.style.minWidth

Type ASDimension. The minWidth property is used to set the minimum width of a particular layout element. It prevents the width property from using values less than those specified by minWidth, which overrides both maxWidth and width. The default value is ASDimensionAuto.

.style.maxWidth

Type ASDimension. The maxWidth property is used to set the maximum width of a particular layout element. It prevents the width attribute from using a value greater than that specified by maxWidth, which overwrites width, and minWidth which overwrites maxWidth. The default value is ASDimensionAuto.

.style.minHeight

The ASDimension, minHeight property sets the minimum height of a particular layout element. It prevents the height property from using a value less than minHeight specifies. MinHeight overrides maxHeight and height. The default value is ASDimensionAuto.

.style.maxHeight

Type ASDimension, which sets the maximum height of a particular layout element. This prevents the height property from being used with a value greater than that specified by maxHeight. MaxHeight overrides height, minHeight overrides maxHeight. The default value is ASDimensionAuto.

.style.preferredSize

CGSize indicates what size the layout elements should be. If minSize or maxSize is provided and preferredSize exceeds these values, minSize or maxSize is mandatory. If preferredSize is not provided, the size of the layout element defaults to the inherent size provided by the calculateSizeThatFits: method.

This method is optional, but for nodes that have no inherent size or need to be laid out with a size different from the inherent size, you must specify either preferredSize or preferredLayoutSize. For example, if this property is not available on the ASImageNode, Make the size of this node different from the image size.

Warning: Calling the getter when the width or height of size is relative will assert.

.style.minSize

The CGSize type, an optional attribute, provides a minimum size for layout elements, if provided, minSize will enforce it. If the minSize of the parent layout element is smaller than the minSize of its children, the child’s minSize is forced, and its size extends beyond the layout rules.

For example, if you set an element in a full-screen container to a preferredSize 50% relative width and a minSize 200pt width, preferredSize will produce a width of 160pt on the iPhone screen, But since 160pt is less than the minSize width of 200pt, the element ends up being 200pt wide.

.style.maxSize

CGSize type, optional attribute, provides the maximum size for layout elements, if provided, maxSize will be mandatory. If the maxSize of a child layout element is smaller than its parent’s maxSize, the child’s maxSize is forced and its size extends beyond the layout rules.

For example, if you set an element in a full-screen container to a preferredSize 50% relative width and a maxSize 120pt width, preferredSize will produce a width of 160pt on the iPhone screen, But because 160pt is larger than the maxSize width of 120pt, the element ends up being 120pt wide.

.style.preferredLayoutSize

The ASLayoutSize type, which provides suggested relative sizes for layout elements. ASLayoutSize uses percentages instead of points to specify the layout. For example, a child layout element should be 50% as wide as its parent. If an optional minLayoutSize or maxLayoutSize is provided and the preferredLayoutSize exceeds these values, minLayoutSize or maxLayoutSize will be used. If this optional value is not provided, the size of the layout element defaults to calculateSizeThatFits: the inherent size provided.

.style.minLayoutSize

Type ASLayoutSize, an optional property that provides a minimum relative size for layout elements. If provided, minLayoutSize will be mandatory. If the minLayoutSize of the parent layout element is smaller than the minLayoutSize of its children, the child’s minLayoutSize is forced and its size extends beyond the layout rules.

.style.maxLayoutSize

Type ASLayoutSize, an optional property that provides the maximum relative size for layout elements. If provided, maxLayoutSize will be mandatory. If the maxLayoutSize of the parent layout element is smaller than the maxLayoutSize of its children, the child’s maxLayoutSize is forced and its size extends beyond the layout rules.

Layout API Sizing

The easiest way to understand the various types of Layout API is to look at how all the units relate to each other.

ASDimension

ASDimension is basically a normal CGFloat that supports representing a pt value, a relative percentage value, or an automatic value. This unit allows an API to use both fixed and relative values.

// Return a relative value
ASDimensionMake("50%")
ASDimensionMakeWithFraction(0.5)

// Return a pt value
ASDimensionMake("70pt")
ASDimensionMake(70)
ASDimensionMakeWithPoints(70)Copy the code

Examples using ASDimension:

ASDimension is used to set the flexBasis property of the ASStackLayoutSpec child element. The flexBasis property specifies the initial size of the object based on whether the sorting direction in the box is horizontal or vertical. In the following view, we want the left box to occupy 40% of the horizontal width and the right box to occupy 60% of the width. This effect can be achieved by setting the.flexbasis property on both childen of the horizontal box container:

self.leftStack.style.flexBasis = ASDimensionMake("40%")
self.rightStack.style.flexBasis = ASDimensionMake("60%")

horizontalStack.children = [self.leftStack, self.rightStack]]Copy the code

CGSize, ASLayoutSize

ASLayoutSize is similar to CGSize, but its width and height can be both pt or percentage values. The width and height types are independent, and their value types can be different.

ASLayoutSizeMake(_ width: ASDimension._ height: ASDimension)Copy the code

ASLayoutSize describes the.preferredLayoutSize,.minlayoutSize, and.maxLayoutSize attributes of layout elements, which allow both fixed and relative values to be used in an API.

ASDimensionMake

The ASDimension type Auto indicates that layout elements can be selected in the most sensible way based on the situation.

let width = ASDimensionMake(.auto, 0)
let height = ASDimensionMake("50%")

layoutElement.style.preferredLayoutSize = ASLayoutSizeMake(width, height)Copy the code

You can also use fixed values to set the.preferredSize,.minsize,.maxsize attributes of layout elements.

layoutElement.style.preferredSize = CGSize(width: 30, height: 60)Copy the code

In most cases, you don’t need to limit width and height. If you want, you can set the size property of the layout element separately using the ASDimension value:

layoutElement.style.width     = ASDimensionMake("50%")
layoutElement.style.minWidth  = ASDimensionMake("50%")
layoutElement.style.maxWidth  = ASDimensionMake("50%")

layoutElement.style.height    = ASDimensionMake("50%")
layoutElement.style.minHeight = ASDimensionMake("50%")
layoutElement.style.maxHeight = ASDimensionMake("50%")Copy the code

ASSizeRange

UIKit does not provide a mechanism to bind the minimum and maximum CGsizes. Therefore, to support the minimum and maximum CGsizes, we created ASSizeRange, which is mainly applied inside the Llayout API. But the layoutSpecThatFits: method’s input parameter, constrainedSize, is of type ASSizeRange.

func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpecCopy the code

Pass to the ASDisplayNode subclass layoutSpecThatFits: ConstrainedSize of the method is the minimum and maximum size that is best for Node, and you can use the minimum and maximum CGSizes contained in constrainedSize on layout elements.

Layout Transition API

The Layout Transition API is designed to make all Texture animations easy – even converting a view set to a completely different view set!

With this system, you just specify the layout you want, and Texture automatically finds differences based on the current layout, automatically adds new elements, removes unwanted elements when the animation is over, and updates the positions of existing elements.

There are also very easy to use apis that allow you to completely customize the starting position of a new element and the ending position of a removed element.

You must use the Layout Transition APIAutomatic child node managementFunction.

Animation between layouts

The Layout Transition API makes it easy to animate a Layout made with Node as the internal state of node changes.

Imagine that you want to implement the registration form and animate the new input field when you click Next:

The standard way to do this is to create a container node called SignupNode, which contains two editable Text field nodes and a Button node as child nodes. We will include a property on SignupNode named fieldState, which will be used for which Text field node to display when evaluating the layout.

The internal layoutSpec of the SignupNode container looks like this:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
  let fieldNode: FieldNode

  if self.fieldState == .signupNodeName {
      fieldNode = self.nameField
  } else {
      fieldNode = self.ageField
  }

  let stack = ASStackLayoutSpec()
  stack.children = [fieldNode, buttonNode]

  let insets = UIEdgeInsets(top: 15.left: 15, bottom: 15.right: 15)
  return ASInsetLayoutSpec(insets: insets, child: stack)
}Copy the code

In order to in this case, the trigger from nameField to ageField conversion, we will update SignupNode. FieldState attributes, and transitionLayoutWithAnimation method is used to trigger the animation.

This method invalidates the currently calculated layout and recalculates the layout of the ageField in the box.

self.signupNode.fieldState = .signupNodeName
self.signupNode.transitionLayout(withAnimation: true, shouldMeasureAsync: true)Copy the code

In the default implementation of this API, the layout is recalculated and its sublayouts are used to set the size and position of SignupNode children, but without animation. Future versions of the API will most likely include default animations between layouts, and we welcome your feedback on what you’d like to see here, but for now we need to implement a custom animation block to handle this animation.

The following example represents the overwrite of animateLayoutTransition: in SignupNode.

This method through transitionLayoutWithAnimation: calculate new called after layout, in the implementation, we will be implemented according to animation fieldState attribute of the trigger before setting a specific animation.

override func animateLayoutTransition(_ context: ASContextTransitioning) {
  if fieldState == .signupNodeName {
    let initialNameFrame = context.initialFrame(for: ageField)

    nameField.frame = initialNameFrame
    nameField.alpha = 0

    var finalAgeFrame = context.finalFrame(for: nameField)
    finalAgeFrame.origin.x -= finalAgeFrame.size.width

    UIView.animate(withDuration: 0.4, animations: { 
        self.nameField.frame = context.finalFrame(for: self.nameField)
        self.nameField.alpha = 1
        self.ageField.frame = finalAgeFrame
        self.ageField.alpha = 0
    }, completion: { finished in
        context.completeTransition(finished)
    })
  } else {
    var initialAgeFrame = context.initialFrame(for: nameField)
    initialAgeFrame.origin.x += initialAgeFrame.size.width

    ageField.frame = initialAgeFrame
    ageField.alpha = 0

    var finalNameFrame = context.finalFrame(for: ageField)
    finalNameFrame.origin.x -= finalNameFrame.size.width

    UIView.animate(withDuration: 0.4, animations: { 
        self.ageField.frame = context.finalFrame(for: self.ageField)
        self.ageField.alpha = 1
        self.nameField.frame = finalNameFrame
        self.nameField.alpha = 0
    }, completion: { finished in
        context.completeTransition(finished)
    })
  }
}Copy the code

The ASContextTransitioning context object passed in this method contains information that helps you determine the node state before and after the transition. It includes old and new constraint sizes, insert and delete nodes, and even old and new ASLayout raw objects. In the SignupNode example, we use it to determine the frame of each node and animate them in one place.

Once the animation is complete, the context object’s completeTransition: must be called, because it will perform the necessary steps internally for the new layout to take effect.

Note that addSubnode: or removeFromSupernode: are not used in this process. The Layout Transition API analyzes node hierarchies between old and new layouts, implicitly performing node insertions and deletions through automatic child node management.

Inserting nodes before animateLayoutTransition: is a good place to manually manage the hierarchy before starting the animation. CompleteTransition: after the carrying out of the context object to remove in didCompleteLayoutTransition:.

If you need to manually delete, please rewrite didCompleteLayoutTransition: and implement custom operations. Note that doing this overrides the default deletion behavior, and it is recommended that you call super or iterate over removedSubnodes in the context object to perform the cleanup.

Pass NO transitionLayoutWithAnimation: through animateLayoutTransition: and didCompleteLayoutTransition: And set the [context isAnimated] property to NO. How you handle such a situation is up to you – if any. An easy way to provide a default implementation is to call super:

override func animateLayoutTransition(_ context: ASContextTransitioning) {
  if context.isAnimated() {

  } else {
      super.animateLayoutTransition(context)
  }
}Copy the code

Animation constrainedSize changed

Sometimes you just want to recalculate a node’s layout in response to a change in its bounds. This kind of circumstance, can call on the node transitionLayoutWithSizeRange: animated:.

This method is similar to transitionLayoutWithAnimation: but if the passed ASSizeRange is equal to the current constrainedSizeForCalculatedLayout, will not trigger the animation. This is useful in response to rotation events and controller size changes.

An example using the Layout Transition API

  • ASDKLayoutTransition

To be continued