Wim Haanstra Wim Haanstra - 1 year ago 194
iOS Question

UITableView with infite scrolling and lazy loading

I got an

which is filled with an unknown amount of rows. Each row contains (for example 3) images and the application gathers this information from a webservice.

For the
I implemented infinite scrolling. Whenever the table almost reaches the end of the current amount of rows, I initiate a call to the webservice, to fetch the next 50 rows of data. I do this in the
method which is called by the

- (void) scrollViewDidScroll:(UIScrollView *)scrollView
CGFloat actualPosition = scrollView.contentOffset.y;

float bottomOffset = 450;
if([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
bottomOffset = 1000;

CGFloat contentHeight = scrollView.contentSize.height - bottomOffset;
if (actualPosition >= contentHeight && !_loading && !_cantLoadMore)
self.loading = YES;


NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject:[NSNumber numberWithInt:_currentPage] forKey:@"page"];
[dict setObject:[_category objectForKey:@"ID"] forKey:@"category"];

[self performSelectorInBackground:@selector(getWallpapersInBackground:) withObject:dict];

The images in the rows of the
are lazy loaded. Whenever a row is visible, the images are loaded in a separate thread and the rows are updated once an image is fully downloaded. The principle I use for lazy loading, is the same as Apple suggests in their documentation. The image is also added to a local
, so I can fetch it when the row scrolls out of the screen and back in (and the row is recreated).

Because the amount of pictures in a single view can go up to 2000 - 3000, I also cache the images to disk and clear out the images from the
when they are further than X rows away. When the user scrolls down and up again the following happens:

  1. New rows are displayed

  2. Lazy loading method is called, which checks if the image is present on disk or that it should download it.

  3. When the image is downloaded or fetched from disk, it performs a codeblock, which displays the image in the row. Images downloaded from the internet, are also cached to disk.

  4. UIImage
    is added to
    for faster caching of images that need to be within reach.

  5. Images which are in rows, 15 rows or further from the visible rows are removed from the
    , because of memory problems (too many
    objects in the
    cause out-of-memory errors).

When the UITableView almost reaches the end, the following occurs:

  1. UITableView almost reaches end of currently loaded rows

  2. Call to webservice, with loads of new rows (50)

  3. The new rows are added to an array, which is used for the

  4. A
    is called to make sure the
    populates with the extra new rows.

  5. New rows that contain images, performs the steps mentioned above to lazy load the images.

So, the problem I have is when I am adding new records from the webservice. When I call a
on the
some images are loading from disk again and some hickups occur while scrolling.

I am looking for a solution for this. I tried using
with the amount of new rows, to add them to the
, but this makes my lazy load method already download the images (because somehow all the cells are created at that time, even when they are not visible, checking if cell is visible during creation delivers unexpected results, images not loading, cells look weird, etc).

So, what I essentially am looking for is a solution for an infinite scrolling UITableView, which lazy loads images from the web/disk and is as smooth as the photo application. Since images are all loaded in separate threads, I don't understand why scrolling isn't as smooth as a baby's skin.

Answer Source

I'm not sure if I totally grok the fullness of your question, but here are some things that I did when confronted with a similar problem.

  • I used -tableView:cellForRowAtIndexPath: as my cue to load more data from the web. I picked a number (in my case 10), when indexPath.row + 10 >= self.data.count load more data.

    • I needed to call -tableView:cellForRowAtIndexPath: anyway.
    • I didn't force a callback into the tighter loop of -scrollViewDidScroll:.
  • I subclassed UIImageView with a class that would async load images which I called URLImageView.

    • In -tableView:cellForRowAtIndexPath:, I assigned a URL to the URLImageView instance.
    • That assignment caused a background operation that would either load from disk or load from the web.
    • The operation was cancelable, so I didn't keep working on an image that no longer needed loading.

I had a table view with hundreds (but not thousands) of images. I only had 2 or 3 table cells on the screen at once.

  • I used -refreshData: it worked like a champ.
  • I even got URLImageView to fade in the image. It was a very nice effect.

I hope that helps.