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
Walkthrough
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:
ASViewController<ASCollectionNode>
Meaning: we are a ViewController which represents an "AS" collection.
MosaicCollectionViewLayoutDelegate
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:
- THERE IS NO COLLECTION VIEW!
- The model is just an array of arrays
- There is no actual Layout instance, only this thing called an "inspector"
- kNumberOfImages probably abstracts away something that would normally be dynamic. We'll look for it later.
init()
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() {
super.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