1

I have looked around on SO and the web and have not found a specific answer.

Quite simply, I have a table loading images info from Flickr. I want to display a thumbnail of the image on the left side of each cell.

In order to do this without blocking the main (UI) thread, I am using blocks:

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

  //getting the selected row image 
  NSDictionary* currentImageDictionary=[self.topfifty objectAtIndex:indexPath.row];//topFifty is an array of image dictionaries


  //creating the download queue 
  dispatch_queue_t downloadQueue=dispatch_queue_create("thumbnailImage", NULL);

  dispatch_async(downloadQueue, ^{

    UIImage *downloadedThumbImage=[self getImage:currentImageDictionary] ;

    //Need to go back to the main thread since this is UI related
    dispatch_async(dispatch_get_main_queue(), ^{

        cell.imageView.image = downloadedThumbImage ;

    });

  });

  dispatch_release(downloadQueue);

  return cell;

}

Now this is not going to work. Because returning the cell will likely happens before executing the block. But at the same time, I cannot return the cell within the main queue block because the block does not accept a return argument.

I want to avoid creating a UITableViewCell subclass.

Any simple answer using blocks?

Thanks KMB

Khaled Barazi
  • 8,681
  • 6
  • 42
  • 62

1 Answers1

5

The problem with doing cell.imageView.image = downloadedThumbImage; is that this cell may now be reused and used for another row.

Instead you need to update the current cell in the specific indexPath (the indexPath will be the same)

UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = image;  

Or sometimes what I do is to update the model and then reload the cell at the specific indexPath:

myModel.image = downloadedThumbImage;
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                                      withRowAnimation:UITableViewRowAnimationNone];  


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

  //getting the selected row image 
  NSDictionary* currentImageDictionary=[self.topfifty objectAtIndex:indexPath.row];//topFifty is an array of image dictionaries

  UIImage *currentImage = [currentImageDictionary objectForKey:@"image"];

  if (currentImage) { 
      // we already fetched the image, so we just set it to the cell's image view
      cell.imageView.image = currentImage;
  }
  else {
      // we don't have the image, so we need to fetch it from the server  

      // In the meantime, we can set some place holder image
      UIImage *palceholderImage = [UIImage imageNamed:@"placeholder.png"];
      cell.imageView.image = palceholderImage;

      // set the placeholder as the current image to your model, so you won't 
      // download the image multiple times (can happen if you reload this cell while 
      // download is in progress)
      [currentImageDictionary setObject:palceholderImage forKey:@"image"];

      // then download the image
      // creating the download queue 
      dispatch_queue_t downloadQueue=dispatch_queue_create("thumbnailImage", NULL);

      dispatch_async(downloadQueue, ^{
         UIImage *downloadedThumbImage=[self getImage:currentImageDictionary] ;

         //Need to go back to the main thread since this is UI related
         dispatch_async(dispatch_get_main_queue(), ^{
                // store the downloaded image in your model
                [currentImageDictionary setObject:image forKey:@"image"];

                // update UI
                UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
                cell.imageView.image = image;    
         });
      });
      dispatch_release(downloadQueue);
  }

  return cell;
}
esh
  • 2,842
  • 5
  • 23
  • 39
Eyal
  • 10,777
  • 18
  • 78
  • 130
  • Thanks Eyal.I am still learning blocks and I thought that if the cell was returned and later the block was executed for that cell, the cell image would not be updated (as in, we might have needed a table reload). But I gather that setting the cell image even after the cell is returned will update the image (as long as it is the right IndexPath) and that is why you use cellForRowAtIndexPath.Now the problem that still remains is that when I scroll down, it shows the next "20 rows" with the image of the first 20 rows and then updates them for the right images. Thoughts? Thanks – Khaled Barazi Aug 09 '12 at 13:51
  • It happens because those cells are reused - so they already have an image in the imageView property. You need to store the image you already downloaded somewhere, for example I added the downloaded image to the image dictionary with the key @"image". This way you can check for every cell if you got the image already - set it to the cell imageView, else download it and set some other placeholder image or nil for no image. Take a look I updated my answer. – Eyal Aug 09 '12 at 14:44
  • I forgot the code to update your model (the image dictionary) with the downloaded image, check out the answer – Eyal Aug 09 '12 at 15:43