7

I have a UITableView with a list of items, each having it's own image. I thought Apple's LazyTableImages sample project would be perfect to learn from, and use to implement the same kind of process of downloading images asynchronously, after the original list data is retrieved.

For the most part, it works quite well, except I did notice a subtle difference in behavior, between this sample app, and how the actual app store downloads images.

If you launch the LazyTableImages sample, then do a quick flick-scroll down, you'll see that the images do not get displayed until after the scrolling comes to a complete stop.

Now, if you do the same test with a list of items in the actual app store, you'll see that the images start displaying as soon as the new items come into view, even if scrolling hasn't stopped yet.

I'm trying to achieve these same results, but so far I'm not making any progress. Does anyone have any ideas on how to do this?

Thanks!

bpatrick100
  • 1,261
  • 1
  • 15
  • 23

2 Answers2

10

I'm baffled that nobody could answer this...

So, I eventually figured out how to acheive the exact same effect that is used in the actual app store, in regards to how the icons are downloaded/displayed.

Take the LazyTableImages sample project and make a few simpled modifications.

  1. Go into the root view controller and remove all checks regarding is table scrolling and/or decelerating in cellForRowAtIndexPath

  2. Remove all calls to loadImagesForOnScreenRows, and thus remove that method as well.

  3. Go into IconDownload.m and change the startDownload method to not do an async image downlaod, but instead do a sync download on a background thread. Remove all the code in startDownload, and add the following, so it looks like this:


- (void)startDownload
{
    NSOperationQueue *queue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImage) object:nil];

    [queue addOperation:operation];

    [operation release];
    [queue release];
}

Then, add a loadImage, like this:


- (void)loadImage
{
    NSData *imageData = [[NSData alloc] initWithContents OfURL:[NSURL URLWithString:appRecord.imageURLString]];
    self.apprecord.appIcon = [UIImage imageWithData:imageData];
    [imageData release];

    [self performSelectorOnMainThread:@selector(notifyMainThread) withObject:nil waitUntilDone:NO];
}

Then, add notifyMainThread like this:


- (void)notifyMainThread
{
    [delegate appImageDidLoad:self.indexPathInTableView];
}

Done! Run it, and you will see the exact app store behavior, no more waiting to request image downloads until scrolling stops, and no more waiting for images to display until scrolling stops, or until user has removed their finger from the screen.

Images are downloaded as soon as the cell is ready to be displayed, and the image is displayed as soon as it is downloaded, period.

Sorry for any typos, I didn't paste this from my app, I typed it in, since I'm away from my mac right now...

Anyway, I hope this helps you all...

bpatrick100
  • 1,261
  • 1
  • 15
  • 23
  • Brilliant! Thanks for posting this! – Nic Hubbard Jun 01 '11 at 18:25
  • I appreciate your effort on improving Apple's example. Your solution works partly. Scroll delegates should **NOT** be removed, or your view controller does not know when to start download the icons. The `loadImagesForOnScreenRows` should also be called in `viewDidLoad` or `viewDidAppear`, depends on usage. – Raptor Mar 08 '12 at 09:35
  • Nice, but it changes the entire sample code. It doesn't use NSURLConnection anymore. – yosh Feb 25 '14 at 10:40
0

Check out UIScrollViewDelegate. I've implemented something like this by listening for scrollViewDidScroll:, calculating the scroll speed (by checking the contentOffset against the last recorded contentOffset, divided by the difference in time), and starting to load images once the speed drops below a certain threshold. (You could achieve something similar with UIScrollViewDelegate's scrollViewDidEndDragging:willDecelerate: as well).

Of course, you don't have to check the speed; you could just load images on UITableViewDelegate's tableView:willDisplayCell:forRowAtIndexPath: whenever you see a new cell, but I've found that if the user is flipping through tons of cells, you don't need to bother until you see that they're going to slow down to browse.

kevboh
  • 5,207
  • 5
  • 38
  • 54
  • I have tried this. I have verified that the calls to download the newly displayed item images are happening immediately. However, connectionDidFinishLoading: never gets called until scrolling comes to a complete stop. I'm really confused by this behavior... – bpatrick100 Feb 11 '11 at 01:40
  • Another way to describe the sample app behavior, is if you swipe the list up, but don't release your finger (keep it on the screen), you'll see that the images do not display. Then, as soon as you release your finger, the images will display. It's seems like single-threaded behavior. I wonder if that is the whole problem here. I wonder if I need to explicitly put each of the image download calls on to their own thread... – bpatrick100 Feb 11 '11 at 01:46
  • It should suffice to just allocate one thread for all of your download calls. As for connections in general, I'd recommend ASIHTTPRequest (http://allseeing-i.com/ASIHTTPRequest/) – kevboh Feb 11 '11 at 02:22
  • Can you describe how I can use a seperate thread to handle all images downloads? I tried using performSelectorInBackground:, but I stopped getting NSURLConnectionDelegate messages, as well as numerous other errors. I don't have any experience with multi-threading on iOS, so if you could point me in the right direction to accomplish this task, I would be appreciative. – bpatrick100 Feb 11 '11 at 04:24