AsyncDisplayKit 2.0 Tutorial: Getting Started

In this AsyncDisplayKit 2.0 tutorial, learn how to make your user interfaces scroll as smooth as butter through the power of asynchronous rendering. By Luke Parham.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Converting the TableView

The first thing you’ll do is to convert the current table view into a table node. Doing this is relatively straightforward.

Replacing tableView with tableNode

First, navigate to AnimalTableController.m. Add the following line below the other imports in this class:

#import <AsyncDisplayKit/AsyncDisplayKit.h>

This imports ASDK in order to use the framework.

Then, go ahead and replace the following property declaration of tableView:

@property (strong, nonatomic) UITableView *tableView;

with the following tableNode:

@property (strong, nonatomic) ASTableNode *tableNode;

This will cause a lot of code in this class to break, but do not panic!

butBut

Seriously, don’t worry. These errors and warnings will serve as your guide in the task of converting what you currently have into what you really want.

The errors in -viewDidLoad are, of course, to do with the fact that the tableView doesn’t exist anymore. I’m not going to make you go through and change all the instances of tableView to tableNode (I mean, find and replace isn’t that hard so feel free to) but if you did you’d see that:

  1. You should be assigning an ASTableNode to the property.
  2. A table node doesn’t have a method called -registerClass:forCellReuseIdentifier:.
  3. You can’t add a node as a subview.

At this point you should just replace -viewDidLoad with the following:

- (void)viewDidLoad {
  [super viewDidLoad];

  [self.view addSubnode:self.tableNode];
  [self applyStyle];
}

The interesting thing to note here is that you’re calling -addSubnode: on a UIView. This method has been added to all UIViews via a category, and is exactly equivalent to:

[self.view addSubview:self.tableNode.view];

Next, fix -viewWillLayoutSubviews by replacing that method definition with the following:

- (void)viewWillLayoutSubviews {
  [super viewWillLayoutSubviews];
  
  self.tableNode.frame = self.view.bounds;
}

All this does is replace self.tableView with self.tableNode to set the table’s frame.

Next, find the -applyStyle method and replace the implementation with the following:

- (void)applyStyle {
  self.view.backgroundColor = [UIColor blackColor];
  self.tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone;
}

The line that sets the table’s separatorStyle is the only line that changed. Notice how the table node’s view property is accessed in order to set the table’s separatorStyle. ASTableNode does not expose all the properties of UITableView, so you have to access the table node’s underlying UITableView instance in order to change UITableView specific properties.

Then, add the following line at the very beginning of -initWithAnimals:

_tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];

and add the following at the end, before the initializer’s return statement:

[self wireDelegation];

This initializes AnimalTableController with a table node and calls -wireDelegation to wire up the table node’s delegates.

Setting the Table Node’s Data Source & Delegate

Just like UITableView, ASTableNode uses a data source and delegate to get information about itself. Table node’s ASTableDataSource and ASTableDelegate protocols are very similar to UITableViewDataSource and UITableViewDelegate. As a matter of fact, they define some of the exact same methods such as -tableNode:numberOfRowsInSection:. The two sets of protocols don’t match up perfectly because ASTableNode behaves a bit differently than UITableView.

Find -wireDelegation and replace tableView with tableNode in the implementation:

- (void)wireDelegation {
  self.tableNode.dataSource = self;
  self.tableNode.delegate = self;
}

Now, you’ll be told that AnimalTableController doesn’t actually conform to the correct protocol. Currently, AnimalTableController conforms to to UITableViewDataSource and UITableViewDelegate. In the following sections you will conform to and implement each of these protocols so that the view controller’s table node can function.

Conforming to ASTableDataSource

Towards the top of AnimalTableController.m, find the following DataSource category interface declaration:

@interface AnimalTableController (DataSource)<UITableViewDataSource>
@end

and replace UITableViewDataSource with ASTableDataSource:

@interface AnimalTableController (DataSource)<ASTableDataSource>
@end

Now that AnimalTableController declares conformance to ASTableDataSource, it’s time to make it so.

Navigate toward the bottom of AnimalTableController.m and find the implementation of the DataSource category.

First, change the UITableViewDataSource method -tableView:numberOfRowsInSection: to the ASTableDataSource version by replacing it with the following.

- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section {
  return self.animals.count;
}

Next, ASTableNodes expect their cells to be returned in a different way than a UITableView would. To accommodate the new paradigm replace -tableView:cellForRowAtIndexPath: with the following method:

//1
- (ASCellNodeBlock)tableNode:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath {
  //2
  RainforestCardInfo *animal = self.animals[indexPath.row];

  //3
  return ^{
    //4
    CardNode *cardNode = [[CardNode alloc] initWithAnimal:animal];

    //You'll add something extra here later...
    return cardNode;
  };
}

Let’s review this section by section:

  1. An ASCellNode is the ASDK equivalent to a UITableViewCell or a UICollectionViewCell. The more important thing to notice is that this method returns an ASCellNodeBlock. This is because an ASTableNode maintains all of its cells internally and by giving it a block for each index path, it can concurrently initialize all of its cells when it’s ready.
  2. The first thing you do is grab a reference to the data model needed to populate this cell. This is a very important pattern to take note of. You grab the data and then capture it inside the following block. The indexPath shouldn’t be used inside the block, in case the data changes before the block is run.
  3. You then return a block whose return value must be an ASCellNode.
  4. There is no need to worry about cell reuse so just chill and initialize a cell the easy way. You may notice that you’re returning a CardNode now instead of a CardCell.

This brings me to an important point. As you may have gathered, there is no cell reuse when using ASDK. Alright, maybe I already basically said that twice, but it’s a good thing to keep in mind. Feel free to go to the top of the class and delete

static NSString *kCellReuseIdentifier = @"CellReuseIdentifier";

You won’t be needing it anymore.

Maybe take a second to mull that over. You never have to worry about -prepareForReuse again…

Conforming to ASTableDelegate

Towards the top of AnimalTableController.m, find the following Delegate category interface declaration:

@interface AnimalTableController (Delegate)<UITableViewDelegate>
@end

and replace UITableViewDelegate with ASTableDelegate:

@interface AnimalTableController (Delegate)<ASTableDelegate>
@end

Now that AnimalTableController declares conformance to ASTableDelegate, it’s time to handle the implementation. Navigate towards the bottom of AnimalTableController.m and find the implementation of this Delegate category.

As I’m sure you’re aware, with a UITableView you usually need to, at least, provide an implementation of -tableView:heightForRowAtIndexPath:. This is because, with UIKit, the height of each cell is calculated and returned by the table’s delegate.

ASTableDelegate lacks -tableView:heightForRowAtIndexPath:. In ASDK, all ASCellNodes are responsible for determining their own size. Instead of being providing a static height, you can optionally define a minimum and maximum size for your cells. In this case, you want each cell to at least be as tall as 2/3rds of the screen.

Don’t worry about this too much right now; it’s covered in detail in part two of this series.

For now, just replace -tableView:heightForRowAtIndexPath: with:

- (ASSizeRange)tableView:(ASTableView *)tableNode 
  constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath {
  CGFloat width = [UIScreen mainScreen].bounds.size.width;
  CGSize min = CGSizeMake(width, ([UIScreen mainScreen].bounds.size.height/3) * 2);
  CGSize max = CGSizeMake(width, INFINITY);
  return ASSizeRangeMake(min, max);
}

After all your hard work, go ahead and build and run to see what you have.

AfterTableNodeBeforePager

That is one smooth table! Once you’ve composed yourself a little, get ready to make it even better.