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.
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 Tutorial: Node Hierarchies
25 mins
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:
- Image node property: This line adds a property to hold a reference to the card’s image subnode.
- Designated initializer: This designated initializer takes a card model object that holds the card’s image and title.
- Subnode setup: This method uses the card model object that existed in the starter project to setup subnodes.
- 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:
- 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. - 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. - 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! :]
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.
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!