aug2uag
aug2uag

Reputation: 3435

UITableView asynchronous UIImage setting

My goal is to set the text, then make an asynchronous call for image, and set the image on arrival.

I currently make an asynchronous call for image to set in UITableViewCell, and noticed my previous technique resulted in a loop I recently identified:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell* cell = [_tableView dequeueReusableCellWithIdentifier:@"id"];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"id"];
    }

    [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:userAvatar]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        if (data) {
            UIImage* avatarImage = [UIImage imageWithData:data];
            if (avatarImage) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [cell.imageView setImage:avatarImage];
                });


                /*
                 .--.      [STOP]
          .----'   '--.    |
          '-()-----()-'    |

          If I include the following, 
          the program goes in a loop, 
          and have not readily identified workaround:

                dispatch_async(dispatch_get_main_queue(), ^{
                    [_tableView reloadData];
                });

                           [GO]           .--.  
                           |         .----'   '--. 
                           |         '-()-----()-' 

                */
            }
        }
    }];

    cell.textLabel.text = @"foo";
    cell.detailTextLabel.text = @"bar";
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;


    return cell;
}

Is there a methodology to produce the outcome I am looking for without a custom setter in custom class?

Upvotes: 3

Views: 789

Answers (1)

Wil Shipley
Wil Shipley

Reputation: 9553

You definitely don’t want to call -reloadData every time you load an image, it’s way too heavyweight, and also causes all the cells to be reloaded, which calls this method, as you’ve discovered.

I’m not clear why you want to call -reloadData at all, because this should already work as-is—setting the image on the imageView of the cell will cause the display to refresh...EXCEPT:

There’s a big logic error in this code because cells are re-used all the time, but you’re asynchronously loading an image and then setting it to a particular cell. There’s no guarantee that the cell will still be assigned to the same row of the table by the time the image loads—you’re just setting an image on a random cell at that point.

In general, tableViews only create just enough cells as can be visible on screen at the same time, not one per row of the table. As you scroll down through a table the cells from the top are removed and re-used down below, to save memory and creation time.

Instead of setting the image on the cell directly, look up the cell again in the tableView by its row (if it’s still visible) and set it that way.

Also, you’re not caching the images you fetch, which will result in a new fetch every time the user scrolls down and then up again. While the built-in networking code will do some level of caching, you never know how much it is and it’s certainly not speedy to change HTTP requests into full images, so you want to cache those images yourself, associated with the original URLs or the rows or whatever keys uniquely identify them.

Upvotes: 3

Related Questions