Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView with infite scrolling and lazy loading

I got an UITableView 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 UITableView 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 scrollViewDidScroll method which is called by the UITableView.

- (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;

        _currentPage++;

        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 UITableView 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 NSDictionary, 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 NSDictionary 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 NSDictionary 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 NSDictionary, because of memory problems (too many UIImage objects in the NSDictionary 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 numberOfItemsInSection method.
  4. A reloadData is called to make sure the UITableView 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 reloadData on the UITableView some images are loading from disk again and some hickups occur while scrolling.

I am looking for a solution for this. I tried using insertItemsAtIndexPaths with the amount of new rows, to add them to the UITableView, 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.

like image 727
Wim Haanstra Avatar asked Jun 30 '12 11:06

Wim Haanstra


People also ask

What is UITableView in Swift?

A view that presents data using rows in a single column. iOS 2.0+ iPadOS 2.0+ Mac Catalyst 13.1+ tvOS 9.0+

What is iOS pagination?

Pagination in Swift is the ability to scroll to the bottom of the page and it automatically fetches more data. Every app nowadays uses this feature to load more information.


2 Answers

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.

like image 93
Jeffery Thomas Avatar answered Oct 13 '22 04:10

Jeffery Thomas


to avoid the flickering try calling beginUpdateson the table, then insertrowAtIndexPaths:withRowAnimations: and finish with endUpdates.

Good luck.

like image 36
Bill Noto Avatar answered Oct 13 '22 06:10

Bill Noto