Swiftgram/submodules/AsyncDisplayKit/docs/_docs/containers-asnodecontroller.md
Peter 9bc996374f Add 'submodules/AsyncDisplayKit/' from commit '02bedc12816e251ad71777f9d2578329b6d2bef6'
git-subtree-dir: submodules/AsyncDisplayKit
git-subtree-mainline: d06f423e0ed3df1fed9bd10d79ee312a9179b632
git-subtree-split: 02bedc12816e251ad71777f9d2578329b6d2bef6
2019-06-11 18:42:43 +01:00

239 lines
8.1 KiB
Markdown
Executable File

---
title: "ASNodeController <b><i>(Beta)</i></b>"
layout: docs
permalink: /docs/containers-asnodecontroller.html
prevPage: containers-asviewcontroller.html
nextPage: containers-astablenode.html
---
<div class = "note">
To use this feature, you will need to import "ASNodeController+Beta.h"
</div>
The Texture team has many exciting ideas for expanding `ASNodeController`. Follow along [here](https://github.com/facebook/AsyncDisplayKit/issues/2964) if you'd like to participate in shaping the future of node controllers.
For now, `ASNodeController` remains a simple, but powerful class.
### Example
The [example project](https://github.com/texturegroup/texture/pull/2945) attached in the initial PR modifies the normal [ASDKgram](https://github.com/texturegroup/texture/tree/master/examples/ASDKgram) project to use an `ASNodeController`.
This `PhotoCellNodeController` is used to manage the fetching of the comments data for a photo in a photo feed, once the photo enters the preload range. This node controller allows us to separate the preloading logic from where it previously existed in the `PhotoCellNode` "view" class.
To convert ASDKgram to use an `ASNodeController`, we first create a `PhotoCellNodeController` class.
This node controller overrides `ASNodeController`'s' `-loadNode` method to create a `PhotoCellNode` once required. It is not neccessary to call super in this method.
This node controller also observes its node's interface state in order to intelligently preload the photo's comment feed model data when the `PhotoCellNode` enters the preload state (which indicates that the photo cell is likely to scroll onscreen soon).
All of this logic can be removed from where it previously existed in the "view" (our `PhotoCellNode` class), leading to a more concise and MVC-friendly view class.
<div class = "highlight-group">
<span class="language-toggle">
<a data-lang="swift" class="swiftButton">Swift</a>
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
</span>
<div class = "code">
<pre lang="objc" class="objcCode">
@implementation PhotoCellNodeController
- (void)loadNode
{
self.node = [[PhotoCellNode alloc] initWithPhotoObject:self.photoModel];
}
- (void)didEnterPreloadState
{
[super didEnterPreloadState];
CommentFeedModel *commentFeedModel = _photoModel.commentFeed;
[commentFeedModel refreshFeedWithCompletionBlock:^(NSArray *newComments) {
// load comments for photo
if (commentFeedModel.numberOfItemsInFeed > 0) {
[self.node.photoCommentsNode updateWithCommentFeedModel:commentFeedModel];
[self.node setNeedsLayout];
}
}];
}
@end
</pre>
<pre lang="swift" class = "swiftCode hidden">
final class PhotoCellNodeController: ASNodeController&lt;PhotoCellNode&gt; {
override func loadNode() {
self.node = PhotoCellNode(photoObject: photoModel)
}
override func didEnterPreloadState() {
super.didEnterPreloadState()
let commentFeedModel = photoModel.commentFeed
commentFeedModel.refreshFeedWithCompletionBlock { [weak self] newComments in
// load comments for photo
if commentFeedModel.numberOfItemsInFeed > 0 {
self?.node.photoCommentsNode.updateWithCommentFeedModel(commentFeedModel)
self?.node.setNeedsLayout()
}
}
}
}
</pre>
</div>
</div>
Next, we add a mutable array to the `PhotoFeedNodeController` to store our node controllers and instantiate it in the init method.
<div class = "highlight-group">
<span class="language-toggle">
<a data-lang="swift" class="swiftButton">Swift</a>
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
</span>
<div class = "code">
<pre lang="objc" class="objcCode">
@implementation PhotoFeedNodeController
{
PhotoFeedModel *_photoFeed;
ASTableNode *_tableNode;
<b>NSMutableArray<PhotoCellNodeController *> *_photoCellNodeControllers;</b>
}
- (instancetype)init
{
_tableNode = [[ASTableNode alloc] init];
self = [super initWithNode:_tableNode];
if (self) {
self.navigationItem.title = @"Texture";
[self.navigationController setNavigationBarHidden:YES];
_tableNode.dataSource = self;
_tableNode.delegate = self;
<b>_photoCellNodeControllers = [NSMutableArray array];</b>
}
return self;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
final class PhotoFeedNodeController: PhotoFeedBaseController {
let photoFeed: PhotoFeedModel
let tableNode: ASTableNode = ASTableNode()
<b>var photoCellNodeControllers: [PhotoCellNodeController] = []</b>
init() {
super.init(node: tableNode)
navigationItem.title = "Texture"
navigationController.isNavigationBarHidden = true
tableNode.dataSource = self
tableNode.delegate = self
}
}
</pre>
</div>
</div>
To use this node controller, we modify our table row insertion logic to create a `PhotoCellNodeController` rather than a `PhotoCellNode` directly and add it to our node controller array.
<div class = "highlight-group">
<span class="language-toggle">
<a data-lang="swift" class="swiftButton">Swift</a>
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
</span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (void)insertNewRowsInTableNode:(NSArray *)newPhotos
{
NSInteger section = 0;
NSMutableArray *indexPaths = [NSMutableArray array];
NSUInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed];
for (NSUInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) {
<b>// create photoCellNodeControllers for the new photos
PhotoCellNodeController *cellController = [[PhotoCellNodeController alloc] init];
cellController.photoModel = [_photoFeed objectAtIndex:row];
[_photoCellNodeControllers addObject:cellController];</b>
// include this index path in the insert rows call for the table
NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section];
[indexPaths addObject:path];
}
[_tableNode insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
func insertNewRowsInTableNode(newPhotos: [PhotoFeedModel]) {
let section = 0
var indexPaths: [NSIndexPath] = []
let newTotalNumberOfPhotos = photoFeed.numberOfItemsInFeed
let firstRow = newTotalNumberOfPhotos - newPhotos.count
(firstRow..<newTotalNumberOfPhotos).forEach { row in
<b>// create photoCellNodeControllers for the new photos
let cellController = PhotoCellNodeController()
cellController.photoModel = photoFeed[row]
photoCellNodeControllers.append(cellController)</b>
// include this index path in the insert rows call for the table
let path = IndexPath(row: row, section: section)
indexPaths.append(path)
}
tableNode.insertRows(at: indexPaths, with: .none)
}
</pre>
</div>
</div>
Don't forget to modify the table data source method to return the node controller rather than the cell node.
<div class = "highlight-group">
<span class="language-toggle">
<a data-lang="swift" class="swiftButton">Swift</a>
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
</span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
{
<b>PhotoCellNodeController *cellController = [_photoCellNodeControllers objectAtIndex:indexPath.row];</b>
// this will be executed on a background thread - important to make sure it's thread safe
ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() {
PhotoCellNode *cellNode = [cellController node];
return cellNode;
};
return ASCellNodeBlock;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
<b>let cellController = photoCellNodeControllers[indexPath.row]</b>
// this will be executed on a background thread - important to make sure it's thread safe
let cellNodeBlock = { () -> ASCellNode in
let cellNode = cellController.node
return cellNode
}
return cellNodeBlock
}
</pre>
</div>
</div>