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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
AsyncDisplayKit 2.0 Tutorial: Automatic Layout
20 mins
- The Problem with Auto Layout
- Getting Started
- Introducing ASLayoutSpec
- Layout Specs
- ASLayoutElement Protocol
- Laying Out the Animal Image
- Adding the Gradient
- Adding the Animal Name Text
- Introducing ASRelativeLayoutSpec
- Introducing ASInsetLayoutSpec
- The Bottom Half
- Intrinsic Content Sizes
- Introducing ASStackLayoutSpec
- Introducing ASBackgroundLayoutSpec
- Where To Go From Here?
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.
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.
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, andalignItems
determines their spacing along the opposite axis. This spec is configured similar to UIKit’sUIStackView
. - 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.
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!
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.
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:
- 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.
-
Create Ratio Layout Spec: Next, you create a a new
ASRatioLayoutSpec
using the calculated ratio and a child, theanimalImageNode
. -
Return a Spec: Returning the
imageRatioSpec
defines the cell’s height and width.
Build and run to see how your layout spec looks:
Pretty easy, eh? Since the image is the only thing that has a size, the cells grew to accommodate that size.
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.