Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous UITableViewCell Image Loading Using GCD

I'm currently trying to load a UITableView list of Flickr Photo (cs193p iOS Stanford, assignment 5). To avoid UI blocking event, I've deferred the thumbnail download of each cell into a different queue (but do update the UI back in the main queue). This code doesn't asynchronously load the images, though does add a thumbnail once I click on of the UITableViewCell row. (see screenshots below). Any idea what i'm doing wrong?

PS: I've looked already in a few other stackoverflow questions & Apple's LazyTableImages example, but I remain convinced this is the cleanest way to achieve the desired result.

Thanks!

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Photo List Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Configure the cell
    NSDictionary *photo = [self.photoList objectAtIndex:indexPath.row];


    if (photo != nil) {
        if ([[photo objectForKey:@"title"] length] > 0) {
            cell.textLabel.text = [photo objectForKey:@"title"];
        } else if ([[[photo objectForKey:@"description"] objectForKey:@"_content"] length] > 0) {
            cell.textLabel.text = [[photo objectForKey:@"description"] objectForKey:@"_content"];
        } else {
            cell.textLabel.text = @"Unknown";
        }
    }
    cell.imageView.image = [[UIImage alloc] initWithCIImage:nil];

    // Fetch using GCD
    dispatch_queue_t downloadThumbnailQueue = dispatch_queue_create("Get Photo Thumbnail", NULL);
    dispatch_async(downloadThumbnailQueue, ^{
        UIImage *image = [self getTopPlacePhotoThumbnail:photo];
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([self.tableView.visibleCells containsObject:cell]) {
                [cell.imageView setImage:image];
            }
        });
    });
    dispatch_release(downloadThumbnailQueue);

    return cell;
}

Before clicking a row Before clicking a row

After selecting the row After selecting the row

UPDATE: For those interested, this is the final code I used:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *CellIdentifier = @"Photo List Cell";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

  // Configure the cell
  NSDictionary *photo = [self.photoList objectAtIndex:indexPath.row];

  if (photo != nil) {
    if ([[photo objectForKey:@"title"] length] > 0) {
        cell.textLabel.text = [photo objectForKey:@"title"];
    } else if ([[[photo objectForKey:@"description"] objectForKey:@"_content"] length] > 0) {
        cell.textLabel.text = [[photo objectForKey:@"description"] objectForKey:@"_content"];
    } else {
        cell.textLabel.text = @"Unknown";
    }
  }
  cell.imageView.image = [[UIImage alloc] initWithCIImage:nil];

  // Fetch using GCD
  dispatch_queue_t downloadThumbnailQueue = dispatch_queue_create("Get Photo Thumbnail", NULL);
  dispatch_async(downloadThumbnailQueue, ^{
    UIImage *image = [self getTopPlacePhotoThumbnail:photo];
    dispatch_async(dispatch_get_main_queue(), ^{
        UITableViewCell *cellToUpdate = [self.tableView cellForRowAtIndexPath:indexPath]; // create a copy of the cell to avoid keeping a strong pointer to "cell" since that one may have been reused by the time the block is ready to update it. 
        if (cellToUpdate != nil) {
            [cellToUpdate.imageView setImage:image];
            [cellToUpdate setNeedsLayout];
        }
    });
  });
  dispatch_release(downloadThumbnailQueue);

  return cell;
}
like image 282
sybohy Avatar asked Sep 07 '12 04:09

sybohy


1 Answers

You should call setNeedsLayout on the cell after setting the thumbnail.

From the Apple Doc:

"Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance."

like image 89
Carl Veazey Avatar answered Nov 20 '22 21:11

Carl Veazey