Ark provides an option to adopt high performance of both Texture and IGListKit, without any boilerplate code stand in the way.
- an instance of
A
which implements SectionInflator protocol. - applying a new snapshot triggers diff processing.
- instance of
ASCollectionNode
from Texture. - call
performBatchUpdates
on collectionNode with diff results. - instance of SectionInflator provides an array of AnyNodable elements.
- instance of AnyNodable will map to an instance of
ASCellNode
in collectionNode. - collectionNode's delegate methods create a new event passed into rx.nodeEventChannel.
- showing an example what a custom cell node looks like.
- custom node maps user action to an event in rx.nodeEventChannel.
- differentiate user actions by enum
NodeEvent.Action
. - subscribe to rx.nodeEventChannel on instance of DiffableDataSource for future events (more details later).
- Texture, especially LayoutSpecs.
- RxSwift
-
Set
ASCollectionNode
as ViewController's root node.import AsyncDisplayKit class ViewController: ASDKViewController<ASCollectionNode> { required init?(coder: NSCoder) { // storyboard // create layout instance let node = ASCollectionNode(collectionViewLayout: layout) super.init(node) } }
-
Create an instance of
DiffableDataSource
.import Ark class ViewController: ASDKViewController<ASCollectionNode> { private lazy var dataSource = DiffableDataSource<HomeFeed>(collectionNode: node) // ... }
-
Implements
SectionInflator
for your HomeFeed type.import Ark enum HomeFeed: SectionInflator { case banner(Banner) case subjects(SubjectFeed) var diffIdentifier: AnyHashbale { switch self { case .banner(let banner): return banner.diffIdentifier case .subjects(let feed): return feed.date } } var items: [AnyNodable] { switch self { case .banner(let banner): return [AnyNodable(banner)] case .subjects(let feed): return feed.subjects.map(AnyNodable.init) } } }
-
Implements
Nodable
for your Banner type.struct Banner: Nodable { let imageName: String var diffIdentifier: AnyHashable { imageName } func nodeBlock(with channel: NodeEventChannel, indexPath: IndexPath) -> ASCellNodeBlock { let image = UIImage(named: imageName)! // main thread operation return { Node(image: image) } } final class Node: ASCellNode { var imageNode: ASImageNode init(image: UIImage) { self.imageNode = ASImageNode() super.init() automaticallyManagesSubnodes = true backgroundColor = .systemBackground imageNode.image = image imageNode.cornerRadius = 4 imageNode.contentMode = .scaleAspectFit } override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { ASWrapperLayoutSpec(layoutElement: imageNode) } } }
-
Subscribe on
rx.nodeEventChannel
ofdataSource
to handle collection node's delegate methods callback and other target actions, for example, to move the selected subject to the bottom of the section:dataSource .rx.nodeEventChannel() .subscribe(onNext: { [unowned self] (event: GenericNodeEvent<SubjectFeed.Subject>) in let targetSection = event.indexPath.section guard case .subjects(let feed) = self.sections[targetSection], let index = feed.subjects.firstIndex(of: event.model) else { return } if case .selected = event.action { var subjects = feed.subjects subjects.remove(at: index) subjects.append(event.model) let newFeed: HomeFeed = .subjects(.init(date: feed.date, subjects: subjects)) self.sections[targetSection] = newFeed } }) .disposed(by: disposeBag)
Note that in onNext
closure you should specify the Nodable
type, and it will be called when events related to or from that type's node arrives.
-
Call
apply(_:animatingDifferences:completion:)
ondataSource
, Isn't this what you have been waiting for?private var sections: [HomeFeed] = [] { didSet { updateUI(animated: true) } } private fun updateUI(animated: Bool = false) { dataSource.apply(.init(sections: sections), animatingDifferences: true, completion: nil) }
- Support Installation via CocoaPods