Texture by example

Formerly known as AsyncDisplayKit – Texture is a UI framework for iOS. I'm starting to use Texture in development and so I will learn by walking through it.

The code is over at https://github.com/TextureGroup/Texture


Starting with the example called CustomCollectionView-Swift I'll go line by line, skipping lines that already make sense for an intermediate app developer.

The AppDelegate.swift file doesn't have anything unique to it.

Class definition

ViewController.swift starts out pretty normal

import UIKit
import AsyncDisplayKit

class ViewController: ASViewController<ASCollectionNode>, MosaicCollectionViewLayoutDelegate, ASCollectionDataSource, ASCollectionDelegate {

First importing the library and conforming to four (4) different protocols. We'll take each:


Meaning: we are a ViewController which represents an "AS" collection.


Meaning: we can answer to the Mosaic layout. MosaicCollectionViewLayoutDelegate is custom to this example project.

ASCollectionDataSource, ASCollectionDelegate

These should look familiar, they're much like the UICollectionView* variations with similar names.

Instance variables

var _sections = [[UIImage]]()
let _collectionNode: ASCollectionNode
let _layoutInspector = MosaicCollectionViewLayoutInspector()
let kNumberOfImages: UInt = 14

Things to note:

  2. The model is just an array of arrays
  3. There is no actual Layout instance, only this thing called an "inspector"
  4. kNumberOfImages probably abstracts away something that would normally be dynamic. We'll look for it later.


init() {
    let layout = MosaicCollectionViewLayout()
    layout.numberOfColumns = 3;
    layout.headerHeight = 44;
    _collectionNode = ASCollectionNode(frame: CGRect.zero, collectionViewLayout: layout)
    super.init(node: _collectionNode)
    layout.delegate = self

All pretty normal, except note that the layout gets a delegate directly (instead of just assuming the delegate of the CollectionView like we'd normally do).

_collectionNode.layoutInspector = _layoutInspector
_collectionNode.registerSupplementaryNode(ofKind: UICollectionElementKindSectionHeader)

Bunches of normal stuff until these two lines. We set the layout inspector on the collectionNode and use this helper called registerSupplementaryNode to register for a special header. Note we never registered any kinds of traditional "cells" in this file.

Setting up the view

override func viewDidLoad() {
  _collectionNode.view.isScrollEnabled = true

Here we have to take the Node and set its view to be scrollable. Prettyyyyyy weird but ok, that makes sense. Nothing but former convention says a collection should be scrollable by deafult after all.

func collectionNode(_ collectionNode: ASCollectionNode, nodeForItemAt indexPath: IndexPath) -> ASCellNode {
  let image = _sections[indexPath.section][indexPath.item]
  return ImageCellNode(with: image)

Here we see one of the only custom classes in this example, an ImageCellNode which is created with just a UIImage.

func collectionNode(_ collectionNode: ASCollectionNode, nodeForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> ASCellNode {
// ...

The method for setting up the Headers is actually much longer than setting up a simple node/cell.

Data source & layout

The numberOfSections and numberOfItemsInSection both make a lot of sense, no surprises there.

Take note: The way size is represented to the layout engine is basically with a ratio. The method is originalItemSizeAtIndexPath meaning essentially, "What shape is the node?"

Custom CollectionViewLayout and Inspector (implementing ASCollectionViewLayoutInspecting)

The top of the file is mostly just a custom grid layout extending UICollectionViewFlowLayout – Which is nothing we've not seen before. The bottom of the file is most interesting: implementing ASCollectionViewLayoutInspecting

First we must provide a ASSizeRange for the node at a arbitrary indexPath.

func collectionView(_ collectionView: ASCollectionView, constrainedSizeForNodeAt indexPath: IndexPath) -> ASSizeRange {

(And same for the header as well in constrainedSizeForSupplementaryNodeOfKind)

Then we provide the number of sections, and the number of supplementary nodes in each section.

Interesting still we provide scroll directions here:

func scrollableDirections() -> ASScrollDirection {

Custom cell view

Our hero, the ImageCellNode which earlier we saw was essentially our custom ViewCell of sorts, is actually of the type ASCellNode. It has a subnode, an ASImageNode which holds its image. And that's about it.

This object is only responsible for returning layoutSpecThatFits or an object view model representing the compound result of ASInsetLayoutSpec, with a ASStackLayoutSpec that contains a ASRatioLayoutSpec wrapping the ImageNode