Asynchronous downloading of images for UITableView with GCD

The problem here is that your image-fetching blocks are holding references to the tableview cells. When the download completes, it sets the imageView.image property, even if you have recycled the cell to display a different row.

You’ll need your download completion block to test whether the image is still relevant to the cell before setting the image.

It’s also worth noting that you’re not storing the images anywhere other than in the cell, so you’ll be downloading them again each time you scroll a row onscreen. You probably want to cache them somewhere and look for locally cached images before starting a download.

Edit: here’s a simple way to test, using the cell’s tag property:

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

    cell.tag = indexPath.row;
    NSDictionary *parsedData = self.loader.parsedData[indexPath.row];
    if (parsedData)
    {
        cell.imageView.image = nil;
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
        dispatch_async(queue, ^(void) {

            NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:parsedData[@"imageLR"]];

            UIImage* image = [[UIImage alloc] initWithData:imageData];
            if (image) {
                 dispatch_async(dispatch_get_main_queue(), ^{
                     if (cell.tag == indexPath.row) {
                         cell.imageView.image = image;
                         [cell setNeedsLayout];
                     }
                 });
             }
        });

        cell.textLabel.text = parsedData[@"id"];
    }
    return cell;
}

Leave a Comment