Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper method/accepted standard for loading data asynchronously into table view cells?

Many apps such as Tweetbot, Twitterrific, Alien Blue, etc. that display images from an API seem to load them in an asynchronous manner. It seems the initial viewable images are loaded synchronously – that is the initial viewable cells aren't displayed until their images are ready – but when scrolling further down past the initial viewable cells, it seems the images for those new cells are loaded independently of the initial synchronous load.

For example, when the user loads tweets in a Twitter app, the initial six or seven tweets that will be visible aren't shown until the images for the users' avatars have loaded, but for the 40+ tweets after that (that aren't visible until the user scrolls), we don't have to wait for those images to have loaded in order to display the initial cells, as they're not even visible yet. It seems most apps allow those images to load independently allowing the initial tweets to show up quicker.

I'm confused how to best accomplish this, for example if there's a standard way that most developers go about doing this that I'm not aware of.

I'm accustomed to using Core Data by supplying objects to Core Data from the response, then NSFetchedResultsController displays them within the table view. However this has the negative affect of requiring everything to be fully loaded and stored in objects before the cells can be displayed. This means that if the API returns an image link to me and I want to display that image within the cell, I have to load the link into a UIImage and add it to Core Data.

So my question basically boils down to: what do popular apps such as Tweetbot, Twitterrific, Alien Blue, etc. do to handle table views with a lot of images and data needing to be loaded, but still permit very quick loading, without having to load unnecessary data, and still maintaining high scrolling performance?

like image 685
Doug Smith Avatar asked Feb 01 '14 22:02

Doug Smith


3 Answers

There are many solutions to this and you can find a lot of them here on stack overflow.

The basic idea is you want to load the image either in a block or an NSOperation. I prefer an NSOperation because you can cancel them which is useful when a cell scrolls off screen.

I recommend a controller to manage your image queue. Your table cell requests the image from the controller and if the controller has the image it returns it immediately. If it doesn't then it returns nil and fetches the image.

From here there are options. You could use a block to call back when the image is retrieved. I am not a fan of that but it is fairly popular. I prefer to use a NSNotification when the image is received and the cell listens for the NSNotification to populate.

I have a fairly complicated sample up on github that handles this as well as much much more. It is in my shared repository at http://github.com/ZarraStudios/ZDS_Shared. The main class is calledZSAssetManager.

Note: This sample code is a few years old so it is not ARC ready and will take some tweaking. It is also probably more complicated than you are looking for as it handles bandwidth detection and reaction as well. However it shows you what a production/high quality application does to handle asynchronous loading of images.

Enjoy.

like image 199
Marcus S. Zarra Avatar answered Oct 16 '22 16:10

Marcus S. Zarra


The Apple documentation has some sample code demonstrating ways to lazily load images into table view cells. It takes care of cancelling downloads for images that are scrolled out of sight and not starting image downloads at all until the user stops scrolling.

like image 2
Robert Atkins Avatar answered Oct 16 '22 15:10

Robert Atkins


There's no perfect answer. It's an art form to improve and tweak the various pieces involved to provide the best user experience for your app. Boiled down though, what you've described involves a proficient combination of the following:

Lazy loading

For lazy loading my absolute favorite goto is SDWebImage. To accomplish the effect you describe where the first 6 or 7 tweets don't load until the images are there you could hide the tweets initially and use SDWebImage's completion blocks to unhide them.

Pagination

Pagination refers to the practice of retrieving a set number of objects initially and pulling down the next X number of objects asynchronously before the user reaches them (usually initiated by a scroll). You could also extend this to to only load the images once the user's scroll slows or stops, so you don't waste time loading images the user scrolled right past. There are some great examples of this on GitHub, like NMPaginator, or you could roll your own using the UIScrollViewDelegate methods.

Setup your scrollview delegate and implement:

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
  if (!decelerate) {
    [self loadImagesForVisibleRows];
  }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  [self loadImagesForVisibleRows];
}

Then add a loadImagesForVisibleRows method where you use [self.tableView indexPathsForVisibleRows]; to access the visible rows and lazy load the images for those rows.

Efficient payloads

Lastly, if you keep your payloads clean and light it'll save you some loading time, especially for users with weak network connections. I'd recommend Vinay's Sahni's blogpost, Best Practices for Designing a Pragmatic RESTful API, for a lot of great information on RESTful API design and pagination.

Welcoming any suggestions or additions to the above.

like image 2
Kyle Clegg Avatar answered Oct 16 '22 15:10

Kyle Clegg