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 3 of 4 of this article. Click here to view the first page.

Intelligent Preloading

Have you ever worked on an app where you decided to load content in advance in some kind of scroll view or page view controller? Maybe you were working on a full-screen image gallery and you decided you always wanted the next few images to be loaded and waiting so your users rarely saw a placeholder.

iThinkIveGotThis

When you do work on a system like this, you soon realize there’s a lot to think about.

  • How much memory are you taking up?
  • How far in advance should you be loading content?
  • When do you decide to dump what you have in response to user interaction?

And this gets quite a lot more complex when you factor in multiple dimensions of content. Do you have a page view controller with a collection view inside of each of the view controllers? Now you need to think of how you’re going to dynamically load content in both directions… Also, go ahead and tune that for each device you’re supporting. K, thanks.

officespaceboss

Remember how I told you to to keep that ASRangeController thing on the back burner of your mind? Well move it to the front burner!

Within each of the container classes there is a concept of the interface state for each of the contained nodes. At any given time, a node can be in any combination of:

preloadingRanges-small

  • Preload Range: Usually the furthest range out from being visible. This is when content for each subnode in a cell, such as an ASNetworkImageNode, should be loaded from some external source; an API or a local cache for example. This is in contrast to batch fetching which should be used to fetch model objects representing cells themselves.
  • Display Range: Here, display tasks such as text drawing and image decoding take place.
  • Visible Range: At this point, the node is onscreen by at least one pixel.

These ranges also work on the metric of “screenfuls” and can be easily tuned using the ASRangeTuningParameters property.

For example, you’re using an ASNetworkImageNode to display the image in each page of the gallery. Each one will request data from the network when it enters the Preload Range and decode the image it has retrieved when it enters the Display Range.

In general, you don’t have to think too hard about these ranges if you don’t want to. The built in components, such as ASNetworkImageNode and ASTextNode, take full advantage of them which means you will see huge benefits by default.

Note: One thing that may not be obvious is that these ranges don’t stack. Instead they overlap and converge on the visible range. If you set the display and prefetch both to one screen, they will happen at exactly the same time. The data usually needs to be present for display to be possible, so usually the prefetch range should be a little larger so nodes will be ready to start the display process when they make it to that range.

In general, the leading side of the range is larger than the trailing side. When the user changes their scroll direction, the sizes of the ranges reverse as well in order to favor the content the user is actually moving toward.

Node Interface State Callbacks

You’re probably wondering how exactly these ranges work right? I’m glad you asked.

Every node in the system has an interfaceState property which is a “bitfield” (NS_OPTION) type ASInterfaceState. As the ASCellNode moves through a scroll view managed by an ASRangeController, each subnode has its interfaceState property updated accordingly. This means that even the deepest nodes in the tree can respond to interfaceState changes.

Luckily, it’s rarely necessary to fiddle with the bits of a node’s interfaceState directly. More often, you’ll just want to react to a node changing to or from a certain state. That’s where the interface state callbacks come in.

Naming Nodes

In order to see a node move through the various states, it is useful to give it a name. This way, you’ll be able to watch as each node loads its data, displays its content, comes on-screen and then does the whole thing in reverse as it leaves.

Go back to -tableNode:nodeBlockForRowAtIndexPath:, and find the comment that says:

//You'll add something extra here later...

Right below it, add the following line to give each cell a debugName.

cardNode.debugName = [NSString stringWithFormat:@"cell %zd", indexPath.row];

Now you’ll be able to track the cells’ progression through the ranges.

Observing the Cells

Navigate to CardNode_InterfaceCallbacks.m. Here you’ll find six methods you can use to track a node’s progress through the various ranges. Uncomment them, and then build and run. Make sure your console in Xcode is visible and then scroll slowly. As you do, watch as the various cells react to their changing states.

console

Note: In most cases, the only ASInterfaceState change method you’ll care about is -didEnterVisibleState or -didExitVisibleState. That said, a lot of work is going on under the hood for you. To check out what you can do by integrating with the Preload and Display states, take a look at the code in ASNetworkImageNode. All network image nodes will automatically fetch and decode their content, as well as free up memory, without you needing to lift a finger.

(Intelligent Preloading)2

In the 2.0 release, the concept of intelligently preloading content in multiple directions was introduced. Say you have a vertically scrolling table view, and at some point a cell comes onscreen that contains a horizontal collection view.

proaldGif^2

Though this collection is now technically in the visible region, you wouldn’t want to load the entire collection up front. Instead, both scroll views have their own ASRangeController complete with separately configurable range tuning parameters.

Entering the Second Dimension

Now that you have completed AnimalTableController, you’re able to use it as a page in an ASPagerNode.

The view controller you’ll use to contain this pager is already in the project so the first thing you need to do is navigate to AppDelegate.m.

Find -installRootViewController and replace:

AnimalTableController *vc = [[AnimalTableController alloc] 
                              initWithAnimals:[RainforestCardInfo allAnimals]];

with:

AnimalPagerController *vc = [[AnimalPagerController alloc] init];

Then, go into AnimalPagerController.m and add the following lines to the initializer right before the return statement. All you need to do is create a new pager and set its dataSource to be this view controller.

_pagerNode = [[ASPagerNode alloc] init];
_pagerNode.dataSource = self;

The pager node is actually a subclass of an ASCollectionNode preconfigured to be used in the same way you’d use a UIPageViewController. The nice thing about this is that the API is actually quite a bit simpler to think about than UIPageViewController‘s.

The next thing you have to do is to implement the pager’s data source methods. Navigate to the ASPagerDataSource category implementation at the bottom of this file.

First, tell the pager that its number of pages is equal to the number of animal arrays, in this case, three by replacing the existing -numberOfPagesInPagerNode:.

- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode {
  return self.animals.count;
}

Then, you need to implement -pagerNode:nodeAtIndex:, similar to the node block data source method you implemented for the ASTableNode earlier.

- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index {
  //1
  CGSize pagerNodeSize = pagerNode.bounds.size;
  NSArray *animals = self.animals[index];
    
  //2
  ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^{
    return [[AnimalTableController alloc] initWithAnimals:animals];
  } didLoadBlock:nil];
   
  return node;
}

Let’s review this section by section:

  1. Although this version isn’t block-based, it’s good practice to grab your data model first.
  2. This time, you’re using the powerful -initWithViewControllerBlock: initializer. All you need to do is return a block that returns the table node controller you fixed up earlier and the managed view will automatically be used as the view for each page. Pretty cool if you ask me. ;]

Once you’ve added this method you’ll have a fully functioning pager whose cells are generated from the tableNodeController you created earlier. This comes fully stocked with two dimensional preloading based on the vertical and horizontal scrolling performed by the user!

AfterASDKGif