pajevic
pajevic

Reputation: 4657

Problems when reloading rows to update cell height in UITableView

I have a custom UITableViewCell where I set the image in a UIImageView from an URL using AFNetworking. When the image is loaded I want to resize the image view and the cell in order to fit the image.

In my tableView:cellForRowAtIndexPath: I have the following code:

FeedItem *feedItem = self.dataArray[indexPath.row];
FeedItemCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"FeedItemCell" forIndexPath:indexPath];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:feedItem.thumbnailUrl];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[cell.thumbnailImageView setImageWithURLRequest:request placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
    cell.thumbnailImageView.image = image;
    // first I set the height of the image view
    cell.thumbnailImageHeightConstraint.constant = image.size.height;
    // then I save the height in a dictionary to return it in tableView:heightForRowAtIndexPath:
    [thumbnailHeights setObject:[NSNumber numberWithFloat:thumbnailHeight] forKey:[NSNumber numberWithInteger:indexPath.row]];

    // now I reload the cell in order to have the new height applied
    [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
    NSLog(@"failed loading: %@", error);
}];

In my tableView:heightForRowAtIndexPath: I have this:

NSNumber *thumbnailHeightNumber = [thumbnailHeights objectForKey:[NSNumber numberWithInteger:indexPath.row]];
if (thumbnailHeightNumber != nil) { // image size has been calculated
    return thumbnailHeightNumber.floatValue;
}
return 0;

This sort of works. The height is set correctly on both the image view and the cell. However, there is some unpredictable behaviour:

  1. When I scroll, some cells flash sometimes, as If being reloaded, which is annoying.
  2. Sometimes wrong cells are shown in rows. Actually, the content of the cell returned by tableView:cellForRowAtIndexPath: is correct, but it displays content from one of the previous cells.
  3. Sometimes a cell is completely blank. Again, inspecting its content it seems correct.

I have noticed something that might be related to the issue. My tableView:cellForRowAtIndexPath: method is always called twice, which I believe is actually causing my problems. If I remove the line [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; it is only called once (for each row) and the problems stop, but of course then the height of the cell is not updated.

Also, I do not believe that the issue is related to the actual change in the cell height. If I set a fixed height for the image and the cell height, I still get the same side-effects when calling reloadRowsAtIndexPaths:withRowAnimation:.

So I am hoping that there is either a fix for this issue, or some other way of accomplishing my goal of dynamic cell height.

Upvotes: 1

Views: 567

Answers (1)

user2067021
user2067021

Reputation: 4529

The call to setImageWithURLRequest:request:placeholderImage:success:failure: is asynchronous, so cell.thumbnailImageView is going to be unpopulated or (if the cell is being re-used) have previous image data in it until setImageWithURLRequest succeeds, probably some time after tableView:cellForRowAtIndexPath: has already returned the cell. If setImageWithURLRequest fails, then your cell is left with an unchanged/unconfigured thumbnail image view.

You can provide an image in the placeholder: argument which will be displayed until the success block is called, then at least you also have a fallback for the failure case.

The table is likely to flash as the various cells' success blocks get called asynchronously some time later, each of them forcing a reload of different rows.

Unless you know the thumbnail images are all a fixed size in advance (and could then easily use a standard placeholder image), I'd suggest pre-loading the relevant thumbnail images before your table view's numberOfRowsInSection: changes its return value and thus triggers the calls to tableView:cellForRowAtIndexPath:. That way you'll be able to return fully configured images in the cells.

Upvotes: 1

Related Questions