AsyncDisplayKit 2.0 Tutorial: Automatic Layout

In part two of this AsyncDisplayKit 2.0 tutorial, learn how easy it is to build fast and flexible layouts in your iOS apps. By Luke Parham.

Leave a rating/review
Save for later
Share


Learn how to easily build flexible and fast layouts.

AsyncDisplayKit 2.0 tutorial

Welcome back to the second part of this series on AsyncDisplayKit!

AsyncDisplayKit’s layout system lets you write declarative layout code that is incredibly fast.

In addition to being fast, it will automatically adapt to the device on which your app is running. Let’s say you’re trying to build a node that could be used in a view controller in your app, or as a popover in an iPad app. If its layout is built up properly, you should be able to port the node to this new environment without having to worry about changing the underlying layout code!

In this AsyncDisplayKit 2.0 tutorial, you’ll circle back to the CardNode class you used in part one and learn about the layout specs that were used to build it up. You’ll see how easy it is to compose layout specs to achieve that hot new look you’re going for.

The Problem with Auto Layout

I hear you crying out, “What’s wrong with Auto Layout?!” With Auto Layout, each constraint you create is represented as an equation in a system of equations. This means that each constraint you add increases the running time of the constraint solver exponentially. This calculation is always run on the main thread.

One of ASDK’s design goals is to stick as closely to UIKit’s APIs as possible. Unfortunately, Auto Layout is an opaque system with no way to tell the constraint solver to do its work on another thread.

Getting Started

To get started, download the starter project here. Since you’ll be learning about the layout specs portion of things, you’ll need to start with an altered version of the finished product from Part 1 of this AsyncDisplayKit 2.0 tutorial series.

Note: Before working through this AsyncDisplayKit 2.0 tutorial, make sure to check out Part 1 for an introduction to Async Display Kit..

Introducing ASLayoutSpec

Before you begin, a little history is necessary.

Layout specs are a generalization of the layout system briefly talked about in the Building Paper Event. The idea is that the calculation and application of sizes and positions of a node and its subnodes should be unified as well as reusable.

In ASDK 1.9.X, you could create asynchronous layouts, but the layout code was similar to the pre-Auto Layout way of doing things in UIKit. The size of a node’s subnodes could be calculated in a method called -calculateSizeThatFits:. These sizes could be cached and then applied later in -layout. The positions of the nodes still had to be calculated using good old-fashioned math — and no one loves messing around with math.

AsyncDisplayKit 2.0 tutorial

OK, fine, most people don’t like messing around with math! :]

Layout Specs

With ASDK 2.0, ASDisplayNode subclasses can implement -layoutSpecThatFits:. An ASLayoutSpec object determines the size and position of all of subnodes. In doing so, the layout spec also determines the size of said parent node.

A node will return a layout spec object from -layoutSpecThatFits:. This object will determine the size of the node, and will also end up determining the sizes and positions of all of its subnodes recursively.

The ThatFits argument is an ASSizeRange. It has two CGSize properties, min and max, which define the smallest and largest sizes the node can be.

ASDK provides many different kinds of layout specs. Here are a few:

  • ASStackLayoutSpec: Allows you to define a vertical or horizontal stack of children. The justifyContent property determines spacing between children in the direction of the stack, and alignItems determines their spacing along the opposite axis. This spec is configured similar to UIKit’s UIStackView.
  • ASOverlayLayoutSpec: Allows you to stretch one layout element over another. The object which is being overlaid upon must have an intrinsic content size for this to work.
  • ASRelativeLayoutSpec: A relative layout spec places an item at a relative position inside its available space. Think of the nine sections of a nine-sliced image. You can instruct an item to live in one of those sections.
  • ASInsetLayoutSpec: An inset spec lets you wrap an existing object in some padding. You want that classic iOS 16 points of padding around your cell? No problem!

ASLayoutElement Protocol

Layout specs manage the layout of one or more children. A layout spec’s child could be a node such as an ASTextNode or an ASImageNode. Or, in addition to nodes, a layout spec’s child could also be another layout spec.

Whoa, how’s that possible?

Layout spec children must conform to ASLayoutElement. Both ASLayoutSpec and ASDisplayNode conform to ASLayoutElement; therefore both types and their subclasses can be layout spec children.

AsyncDisplayKit 2.0 tutorial

This simple concept turns out to be incredibly powerful. One of the most important layout specs is ASStackLayoutSpec. Being able to stack an image and some text is one thing, but being able to stack an image and another stack is quite another!

AsyncDisplayKit 2.0 tutorial

You’re totally right. It’s time to duel! I mean, write code…

Laying Out the Animal Image

So you’re at work and your designer sends you a screenshot of what she wants for the new animal encyclopedia app you’re working on.

AsyncDisplayKit 2.0 tutorial

The first thing to do is break the screen down into the appropriate layout specs to express the overall layout. Sometimes this can feel a little overwhelming, but remember, the power of layout specs comes from how easily they can be composed. Just start simple.

I’ll give away the ending a little by saying the top half and bottom half will work perfectly in a stack together. Now that you know that, you can lay out the two halves separately and bring them together in the end.

Unzip the starter project and open RainforestStarter.xcworkspace. Navigate to CardNode.m and go to -layoutSpecThatFits:. Right now it simply returns an empty ASLayoutSpec object.

If you build and run you’ll see the following:

Well, it’s a start. How about just showing the animal image first?

By default, a network image node has no content and therefore no intrinsic size. You’ve determined by looking at the screenshot that the animal’s image should be the full screen width and 2/3 the screen’s size.

To accomplish this, replace the existing return statement with the following:

//1
CGFloat ratio = constrainedSize.min.height/constrainedSize.min.width;

//2
ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec 
                                            ratioLayoutSpecWithRatio:ratio 
                                                               child:self.animalImageNode];
//3
return imageRatioSpec;

Taking each numbered comment in turn:

  1. Calculate Ratio: First, you define the ratio you want to apply to your image. Ratios are defined in a height/width manner. Here, you state you want this image’s height to be 2/3 the minimum height of the cell, which happens to be the screen height.
  2. Create Ratio Layout Spec: Next, you create a a new ASRatioLayoutSpec using the calculated ratio and a child, the animalImageNode.
  3. Return a Spec: Returning the imageRatioSpec defines the cell’s height and width.

Build and run to see how your layout spec looks:

AsyncDisplayKit 2.0 tutorial

Pretty easy, eh? Since the image is the only thing that has a size, the cells grew to accommodate that size.

Note: The constrainedSize passed into a table node cell consists of a min of (0, 0) and a max of (tableNodeWidth, INF) which is why you needed to use the preferredFrameSize for the image’s height. The preferredFrameSize was set in AnimalPagerController in Part 1.