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.

Infinite Scrolling with Batch Fetching

In most apps, the server has more data points available than the number of cells you’d want to show in your average table. This means that darn near every app you work on will have some mechanism set up to load another batch of objects from the server as the user approaches the end of the current data set.

Many times, this is handled by manually observing the content offset in the scroll view delegate method -scrollViewDidScroll:. With ASDK, there is a more declarative way of doing things. Instead, you can describe how many pages in advance you’d like to load new content.

The first thing you’ll do, is uncomment the helper methods that have been included. Go to the end of AnimalTableController.m and uncomment the two methods in the Helpers category. You can think of -retrieveNextPageWithCompletion: as your networking call, while -insertNewRowsInTableNode: is a pretty standard method for adding new elements to a table.

Next, add the following line to -viewDidLoad:.

self.tableNode.view.leadingScreensForBatching = 1.0;  // overriding default of 2.0

Setting leadingScreensForBatching to 1.0 means that you want new batches to be fetched whenever the user has scrolled to the point where only 1 screenful of content is left in the table before they would reach the end.

Next, add the following method to the Delegate category implementation:

- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode {
  return YES;
}

This method is used to tell the table whether or not it should keep making requests for new batches after this one. If you know you’ve reached the end of your API’s data, return NO and no more requests will be made.

Since you really do want this table to scroll forever, just return YES to ensure new batches will always be requested.

Next, also add:

- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context {
  //1
  [self retrieveNextPageWithCompletion:^(NSArray *animals) {
    //2
    [self insertNewRowsInTableNode:animals];
    
    //3
    [context completeBatchFetching:YES];
  }];
}

This method is called when the user has neared the end of the table and the table has received a YES from -shouldBatchFetchForTableNode:.

Let’s review this section by section:

  1. First, you make a request for the next batch of animals to show. Usually this is an array of objects coming back from an API.
  2. On completion, update the table with the newly downloaded data.
  3. Finally, make sure to call -completeBatchFetching: with YES when you’re done. New batch fetching requests won’t be made until this one has been completed.

Build, run, and just start swiping. Don’t stop until you don’t care to see another bird. They are infinite.

InfiniteScrollingGif

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.