AsyncDisplayKit Tutorial: Node Hierarchies

This intermediate level AsyncDisplayKit tutorial will explain how you can make full use of the framework by exploring AsyncDisplayKit node hierarchies. By René Cacheaux.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Adding a Subnode

Open CardNode.swift and add the following code to CardNode above calculateSizeThatFits(constrainedSize:):

// 1
let imageNode: ASImageNode

// 2
init(card: Card) {
  imageNode = ASImageNode()
  super.init()
  setUpSubnodesWithCard(card)
  buildSubnodeHierarchy()
}

// 3
func setUpSubnodesWithCard(card: Card) {
  // Set up image node
  imageNode.image = card.image
}

// 4
func buildSubnodeHierarchy() {
  addSubnode(imageNode)
}

Here’s what that does:

  1. Image node property: This line adds a property to hold a reference to the card’s image subnode.
  2. Designated initializer: This designated initializer takes a card model object that holds the card’s image and title.
  3. Subnode setup: This method uses the card model object that existed in the starter project to setup subnodes.
  4. Container’s hierarchy: You can compose node hierarchies just like you can compose view hierarchies. This method builds the card node’s hierarchy by adding all the subnodes to itself.

Next, re-implement calculateSizeThatFits(constrainedSize:):

override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize {
  // 1 
  imageNode.measure(constrainedSize)

  // 2 
  let cardSize = imageNode.calculatedSize

  // 3 
  return cardSize
}

Here’s what that code does:

  1. The size of the card should match the size of the background image. This line measures the size of the background image fitting inside the constrained size. All of the node subclasses that ship with AsyncDisplayKit know how to size themselves, including ASImageNode which is used here.
  2. This line temporarily stores the imageNode’s calculated size, which is also the size of the entire card node. Specifically, it uses the image node’s measured size as the card node size to constrain subnodes. You’ll use this value when adding more subnodes.
  3. The last line returns the card node’s calculated size that fits within the constrained size provided.

Next, override layout():

override func layout() {
  imageNode.frame =
    CGRect(origin: CGPointZero, size: imageNode.calculatedSize).integerRect
}

This logic positions the image in the upper-left corner, aka zero origin, of the card node. It also makes sure that the image node’s frame doesn’t have any fractional values, so that you avoid pixel boundary display issues.

Take note of how this method uses the image node’s cached calculated size during layout.

Since the size of this image node determines the size of the card node, the image will span the entire card.

Go back to ViewController.swift, and inside createCardNode(containerRect:), replace the line that initializes CardNode with:

let cardNode = CardNode(card: card)

This line uses the new initializer you added to CardNode. The card value that passes into the initializer is simply a constant property on ViewController that stores the Taj Mahal card model.

Build and run. Boom! Huzzah! :]

ASDK_NodeHierarchies - 6

Awesome. You’ve successfully created a container node that presents a node hierarchy! 👊🎉 Sure, it’s a simple one, but it’s a node hierarchy!

Adding More Subnodes

Hey, where are you going? You’re not done yet! Just how do you expect the user to know what he’s looking at without a title? Nevermind, don’t answer that; we’re moving on now.

You need at least one more subnode to hold the title.

Open CardNode.swift and add the following titleTextNode property to the class:

let titleTextNode: ASTextNode

Initialize the titleTextNode property inside init(card:) above super.init():

titleTextNode = ASTextNode()

Add the following line to setUpSubnodesWithCard(card:):

titleTextNode.attributedString = NSAttributedString.attributedStringForTitleText(card.name)

This line gives the text node an attributed string that holds the card’s title. attributedStringForTitleText(text:) is a helper method that was added to NSAttributedString via extension. It existed in the starter project, and it creates the attributed string with the provided title and with the text styling appropriate for this app’s card titles.

Next, add the following at the end of buildSubnodeHierarchy():

addSubnode(titleTextNode)

Make sure it goes below the line adding the image node, otherwise the image would be on top of the title!

And inside calculateSizeThatFits(constrainedSize:), add the following right above the return statement:

titleTextNode.measure(cardSize)

This measures the rest of the subnodes by using this card’s size as the constrained size.

Add the following to layout():

titleTextNode.frame =
  FrameCalculator.titleFrameForSize(titleTextNode.calculatedSize, containerFrame: imageNode.frame)

This line calculates the title text node’s frame with the help of FrameCalculator, a custom class included in the starter project. FrameCalculator hides frame calculation to keep things simple.

Build and run. Now there will be no questions about the Taj Mahal.

ASDK_NodeHierarchies - 7

And that’s…how you do it! That’s a full node hierarchy.

You’ve built a node hierarchy that uses a container with two sub-nodes.

Where To Go From Here?

If you’d like to check out the final project, you can download it here.

To learn more about AsyncDisplayKit, check out the getting started guide and the sample projects in the AsyncDisplayKit Github repo.

The library is entirely open source, so if you’re wondering how something works you can delve into the minutia for yourself!

Now that you’ve got this foundation, you can compose node hierarchies into well organized and reusable containers that make your code easier to read and understand. To sweeten the deal further, you’re ensuring a smoother and more responsive UI by using nodes which perform a lot of work in the background that UIKit can only dream of doing.

If you have any questions, comments or sweet discoveries, please don’t hesitate to jump into the forum discussion below!