Texture (AsyncDisplayKit): “This isn't supposed to do that”

Profile photo of Rihards Baumanis
Rihards Baumanis
Feb 22, 2018
6 min
Categories: Development
A person holding a cell phone in their hand
A person holding a cell phone in their hand

Once upon a time, there was a small group of iOS devs who decided that it would be awesome to try out AsyncDisplayKit to do a whole development project from A to Z. Why not.

First of all, why do even bother? Well, for us these were the reasons:
  • We wanted to come as close as possible to writing full layout in code;
  • Laying out UI in code is 'the thing';
  • 60 fps custom CollectionViewLayout sounds eye-pleasing;
  • Just lacked a challenge? Well, not really, but it's motivating to say.

Anyway — nodes, nodes, nodes everywhere. Everything is a node. And nodes go into other nodes. Which have nodes of their own.

The main purpose of this article is just to give our thoughts about this framework and maybe share a couple of lines of code that we felt came out nice.

The likes:
  • Laying out of elements with AsyncKit makes you feel good. The syntax has a good flow, the elements chain well together. It all comes down to having a good understanding of what are you doing and what do you need to be doing to have a good relationship with AsyncKit.

An example: We had a necessity to build a somewhat specific loading screen. Animating is a whole another story, but the general layout looks like this:

1-min.png

The cubes themselves animate up and down, while some action is being performed. The code to layout elements in this way looks something like:

topArtefactNode.style.preferredSize = CGSize(width: topNodeWidth + 10, height: topNodeHeight) middleLeftArtefactNode.style.preferredSize = CGSize(width: smallNodeWidth, height: smallerNodeHeight) middleRightArtefactNode.style.preferredSize = CGSize(width: smallNodeWidth, height: largerNodeHeight) bottomRightArtefactNode.style.preferredSize = CGSize(width: smallNodeWidth, height: smallerNodeHeight) bottomLeftArtefactNode.style.preferredSize = CGSize(width: smallNodeWidth, height: largerNodeHeight) let rightStack = ASStackLayoutSpec(direction: .vertical, spacing: 10, justifyContent: .center, alignItems: .center, children: [middleRightArtefactNode, bottomRightArtefactNode]) let leftStack = ASStackLayoutSpec(direction: .vertical, spacing: 10, justifyContent: .center, alignItems: .center, children: [middleLeftArtefactNode, bottomLeftArtefactNode]) let middleStack = ASStackLayoutSpec(direction: .horizontal, spacing: 10, justifyContent: .center, alignItems: .center, children: [leftStack, rightStack]) let globalStack = ASStackLayoutSpec(direction: .vertical, spacing: 10, justifyContent: .center, alignItems: .center, children: [topArtefactNode, middleStack]) let center = ASCenterLayoutSpec(centeringOptions: .XY, sizingOptions: [], child: globalStack) let backgroundInsets = ASInsetLayoutSpec(insets: .zero, child: backgroundNode) return ASOverlayLayoutSpec(child: backgroundInsets, overlay: center)

Grasping the concept of how layout elements work is the main thing that needs to be learned when working with AsyncDisplayKit. The rest just comes somewhat naturally.

  • IPad and iPhone layouts are easy to handle together. Elements have different positions? Just change the order in which the elements are in a stack. Or add a new stack. Or just throw it out.
  • Easy modifications. Since subnodes can handle their own layout, it couldn't be easier to make changes. Just take something out, remaining elements just fill the place. Not like removing an element from AutoLayout stack sometimes just annihilates everything where you either decide to revert changes and leave it as it is or start from scratch. Again.
  • Animating layouts. I believe that this will be a point, which I'm also going to be placing in the dislikes section, but I'll get to that. I guess I like the idea, where you can just say 'please redraw everything and use animation' and every change you have made from the previous layout will be animated. So that's kind of cool.
  1. Call the transition function to initiate animation;
  2. Have the layout get calculated to match your necessary animations;
  3. Implement the animated transition function and then animate the frames however you feel like;
// 1) - (void)transitionLayoutWithAnimation:(BOOL)animated shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(nullable void(^)())completion // 2) func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec // 3) override func animateLayoutTransition(_ context: ASContextTransitioning) { let aFrame = context.finalFrame(for: aNode) UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.4, options: .curveEaseInOut, animations: { [weak self] in guard let aNode = self?.aNode else { return } aNode.frame = aFrame }, completion: nil) }
  • TableNode and CollectionNode automatically sizing cells. This is a cool thing to have. Cell nodes just like any other nodes need to calculate their own layout for subnodes. That means that when creating a layout, you can also force specific elements to be displayed at certain sizes. And AsyncKit is smart enough to then take this calculated size and transfer it to a ASCollectionNode or ASTableNode. Yes, AutoLayout has a similar approach, but for some reason, we felt that AsyncKit was handling that in a more developer friendly way. But that's a subjective opinion.
  • Reusability. Once developed nodes can be just passed on to other nodes. This eases development a lot when you have a couple of repeating elements. The node can be just added and sized with no significant effort.
  • The development speed. When working with AsyncKit we felt that deliveries were happening faster than when working with nibs or storyboards. This point is related to me previously mentioning that AsyncKit has good syntax and modifications are easy but in any case, I believe it's worth mentioning, that we encountered a speed boost when laying out everything in code.
The dislikes
  • Sometimes even the tiniest layouts require a lot of code to be set up. I believe this can encourage subclassing nodes and creating a whole bunch of separated layout code, but for us, it was a case-by-case basis.
  • Attributed strings. Attributed strings everywhere. It's a dislike as much it could be a like, but we felt that it got us more agitated then satisfied. Buttons, labels, everything consisting of attributed strings. Of course, it gives you a lot more customisability, but even the tiniest things need to go through the custom setup. Sometimes great to have, but definitely not always. For us a library called BonMot helped out a lot when handling attributed strings.
  • Some elements are not supported. I think this was a transition problem for us from UIKit, but a time to time it caused us to scratch our brain a bit to figure out what exactly should be used where and why.
  • Animating layouts. I'm adding this here as well because to some extent it was confusing to figure out how this is supposed to work. Let's just say that I have a supernode and two subnodes. I need the subnodes to animate their frames while contained in supernode and then the subnodes each to animate their own nodes in one smooth motion. Initially, we got stuck on having one node animate and the other jump to its new layout. Then the other way around. Then the same happened to subnodes. It takes a while to figure out what goes where. At this point, we managed to get the animated layouts to work as expected, but it wasn't the most straight forward process to understand when layout should be calculated asynchronously or not. It was a lot of — try that, doesn't work, now try that.
  • Scroll views. Initially, I was ready to flip everything when my custom scroll node decided to take whatever shape it felt like, when I specifically had it styled with preferred size shape. And I'm ashamed to say that it took a little longer than expected to figure out that a scroll node, which is expected to scroll horizontally while contained in a vertical stack will only layout properly if you first lock it in a horizontal stack. Now I know.
  1. You lay out your scroll node subnodes and then add them in a horizontal stack.
  2. Then when using this scroll node in a larger layout, you must first add it in a horizontal stack as a single child (this is the case, when the horizontal scroll node is sized edge-to-edge)
  3. And then you can use the stack from 2nd step to use in a vertical layout.
1) let hStack = ASStackLayoutSpec(direction: .horizontal, spacing: 0, justifyContent: .spaceAround, alignItems: .center, children: items) return ASInsetLayoutSpec(insets: UIEdgeInsets(top: verticalPadding, left: 8, bottom: verticalPadding, right: 8), child: hStack) 2) let superHStack = ASStackLayoutSpec(direction: .horizontal, spacing: 0, justifyContent: .center, alignItems: .center, children: [scrollNode]) 3) let finalStack = ASStackLayoutSpec(direction: .vertical, spacing: 5, justifyContent: .spaceBetween, alignItems: .start, children: [elem0, superHStack, elem1])

Of course, other layout elements can and probably should be used as well to achieve a better layout, but the idea remains. If we're looking at a vertically scrolling node, then the steps probably remain the same, but only stack directions change. Haven't tried though.

Conclusion

We worked on and delivered not the smallest of applications using AsyncDisplayKit. There were times when UIKit seemed more pleasing, but doing everything the AsyncKit way was a really good experience. Even when sometimes the necessary feature required more code than it would in UIKit, it turned out to be easier to make, sustain and scale in AsyncKit.

Definitely recommending this for a development approach. AsyncKit has practically all necessary features and it was a fun and enjoyable experience.

I'd like to thank the Texture (or AsyncKit) guys for giving us such an amazing tool to work with.

Some samples

Just to share something AsyncKit-ish. If you feel like it you can check out the Github repo. Nothing special or magical, just to give an overall idea and maybe help someone out.

Thanks for reading! :)

https://miro.medium.com/max/300/1*hB1OofoTpqI6wm43oKjH8A.png

Receive a new Mobile development related stories first. — Hit that follow button

Twitter: @ChiliLabs

www.chililabs.io

Share with friends

Let’s build products together!

Contact us