STOP THE FRAME DROP – Texture (AsyncDisplayKit)

Jan, 19, 2018 • Laurens Wuyts

Categories: iOS

The last couple of days I’ve been working with Texture (AsyncDisplayKit), it’s a cool framework that basically makes your app run more efficiently. It’s all about the performance gains. Texture works with nodes which are thread-safe. Unlike views, which can only be used on the main thread.

Today I will show you how you can create a really efficient tableView with Texture, and you’ll clearly see the performance gains from it. Since Texture keeps expensive UI operations off the main thread it is available to respond to user interactions at any given time.

Below you can see the difference between the two:

video-1
video-2

You can clearly see that the the first image moves smoother than the second one. It’s because all images and text fields are rendered in the background instead of the main thread.

Texture also makes it easy to add placeholders. The reason you’ll want to use placeholders is that it can take a few milliseconds to load the actual views from the background thread.

Installation

To install it you can just use CocoaPods:

pod "Texture"

 

Or Carthage:

Github “texturegroup/texture”

Usage

First, let’s import the dependency:

import AsyncDisplayKit

 

After that we will create the class itself:

class NodeViewController: ASViewController<ASDisplayNode>, ASTableDataSource, ASTableDelegate {

var tableNode: ASTableNode {
    return node as! ASTableNode
}

init() {
    super.init(node: ASTableNode())
    self.tableNode.delegate = self
    self.tableNode.dataSource = self
}

required init?(coder aDecoder: NSCoder) {
    fatalError("storyboards are incompatible with truth and beauty")
}

There are a few big changes here that probably going to need some explanation:

  1. Instead of creating a UIViewController class we will create an ASViewController class which is a subclass of UIViewController. It does a few things automatically for you like if the view controller goes off screen it will automatically reduce the size of the fetch data and display ranges of any of its children.
  2. A UITableView doesn’t exist anymore, we will use an ASTableNode which uses ASTableDataSource and ASTableDelegate. They are very similar to UITableViewDataSource and UITableViewDelegate.

 

Conform to ASTableDataSource and ASTableDelegate:

func numberOfSectionsInTableNode(tableNode: ASTableNode) -> Int {
     return 1
}

func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
     return 10
}

func tableNode(_ tableNode: ASTableNode, nodeForRowAt indexPath: IndexPath) -> ASCellNode {
     return CellNode(image: UIImage(named: "image")!)
}

func tableNode(_ tableNode: ASTableNode, constrainedSizeForRowAt indexPath: IndexPath) -> ASSizeRange {
     let min = CGSize(width: UIScreen.main.bounds.size.width, height: 500)
     let max = CGSize(width: UIScreen.main.bounds.size.width, height: 1000)
     return ASSizeRange(min: min, max: max)
}

As you can see it looks quite the same as before. Instead of an UITableViewCell we will use an ASCellNode and instead of heightForRowAt we will use constrainedSizeForRowAt where you will need to give a range instead of a float.

 

Create the ASCellNode:

class TableNode: ASCellNode {

    fileprivate let backgroundImageNode: ASImageNode

    fileprivate let topTextNode: ASTextNode
    fileprivate let bottomTextNode: ASTextNode

    init(image: UIImage) {
        backgroundImageNode = ASImageNode()

        topTextNode = ASTextNode()
        bottomTextNode = ASTextNode()

        super.init()

        clipsToBounds = true

        //Background Image
        backgroundImageNode.image = image
        backgroundImageNode.clipsToBounds = true
        backgroundImageNode.placeholderColor = UIColor.lightGray
        backgroundImageNode.placeholderFadeDuration = 0.15
        backgroundImageNode.contentMode = .scaleAspectFill

        //Top Text
        let attributedStringTitle = NSAttributedString(string: "...", attributes: [])
        topTextNode.attributedText = attributedStringTitle
        topTextNode.placeholderEnabled = true
        topTextNode.placeholderFadeDuration = 0.15
        topTextNode.placeholderColor = UIColor(white: 0.777, alpha: 1.0)

        //Bottom Text
        let attributedStringDescription = NSAttributedString(string: "...", attributes: [])
        bottomTextNode.attributedText = attributedStringDescription
        bottomTextNode.backgroundColor = UIColor.clear
        bottomTextNode.placeholderEnabled = true
        bottomTextNode.placeholderFadeDuration = 0.15
        bottomTextNode.placeholderColor = UIColor(white: 0.777, alpha: 1.0)
        bottomTextNode.backgroundColor = UIColor.black
        bottomTextNode.alpha = 0.5

        addSubnode(backgroundImageNode)

        addSubnode(topTextNode)
        addSubnode(bottomTextNode)
}

Again some big changes that probably going to need some explanation:

  1. Instead of an UIImageView we will use an ASImageNode and instead of an UITextField we will use an ASTextNode.
  2. Instead of addSubview we will use addSubnode.

The cool thing about this is that it’s super easy to add placeholders. You can add a color, a fade duration, …

The next part is a little bit more complex and for some can be deal-breaker deciding to use this framework or not. UIKit Auto Layout and InterfaceBuilder are not supported by Texture. You’ll have to use their Layout API. Let me show you:

override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
     let relativeSpec = ASRelativeLayoutSpec(horizontalPosition: .start, verticalPosition: .end, sizingOption: [], child: topTextNode)
     let topInsetSpec = ASInsetLayoutSpec(insets: UIEdgeInsetsMake(0, 16, 0, 0), child: relativeSpec)
     let bottomInsetSpec = ASInsetLayoutSpec(insets: UIEdgeInsetsMake(16, 0, 0, 0), child: bottomTextNode)

     let verticalStackSpec = ASStackLayoutSpec(direction: .vertical, spacing: 0, justifyContent: .end, alignItems: .end, children: [topInsetSpec, bottomInsetSpec])

     let backgroundLayoutSpec = ASBackgroundLayoutSpec(child: verticalStackSpec, background: backgroundImageNode)

     return backgroundLayoutSpec
}

It sure has a lot of benefits over UIKit Auto Layout but it’s a lot of new stuff. Be sure to check it out in their docs.

Conclusion

It looks really cool and it has huge performance gains. But it can be a disadvantage that UIKit Auto Layout and the InterfaceBuilder are not supported. There is a lot of new stuff that you gonna have to learn but a lot of that looks quite similar than the things you’re using right now.

If you’re interested in it, make sure to check out the documentation.

Continue reading

View all articles

Swift Optionals

This blog post is all about Optionals, a language concept baked into Apple’s Swift programming language. We’ll start off with a practical use case, in which we explain how optionals are used. We will then extend the example to make the code shorter, and hopefully, more readab[...]

React Native Storybook

The last couple of days I’ve been working with Texture (AsyncDisplayKit), it’s a cool framework that basically makes your app run more efficiently. It’s all about the performance gains. Texture works with nodes which are thread-safe. Unlike views, which can on[...]