3

I am experimenting in tableView:cellForRowAtIndexPath to set an image by calling

[self downloadImage:urlString andSetIntoCellImageView:cell]

and in downloadImage, it will call NSURLConnection:sendAsynchronousRequest (iOS 5 and up only), and in the completion block, set the image by using

cell.imageView.image = [UIImage imageWithData:data];   // data is downloaded data

and it works if in tableView:cellForRowAtIndexPath, the imageView is populated with a dummy placeholder image -- and I wonder how the new image is refreshed, is it by setNeedsDisplay to do a repaint? But if I don't set the placeholder image, then the new image won't show at all. I wonder what mechanism can be used to make it show the image?

If I use

[cell.imageView setNeedsDisplay]

or

[cell setNeedsDisplay];

in the completion block, it won't work, and if I use

[self.table reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
                            withRowAnimation:UITableViewRowAnimationAutomatic];

in the completion block by making downloadImage accept the indexPath, it will call tableView:cellForRowAtIndexPath again, and cause an infinite loop. So it seems like I need to use some hash table to remember if the image is already in hash table: if not, call downloadImage, and if in hash table, simply use it, so there will be no infinite loop.

But is there an easy way to cause the image to show up? Setting a placeholder image works but what if we don't -- by what mechanism does placeholder cause the refresh of image?

nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • So `cell.imageView.image = [UIImage imageWithData:data]` works as long as there's been a placeholder image set in the cell within the `tableView:cellForRowAtIndexPath:` method, but if you don't provide a placeholder, this same technique doesn't work? Would you mind posting how you're setting up the placeholder image? – Carl Veazey Aug 31 '12 at 05:34
  • @CarlVeazey that's correct. Sure, I set the placeholder using `cell.imageView.image = [UIImage imageNamed:@"pic0.png"];` – nonopolarity Aug 31 '12 at 05:44

2 Answers2

14

When a UITableViewCell's -layoutSubviews method is called, if its imageView's image property is nil, imageView is given a frame of (0,0,0,0). Also, -layoutSubviews only is to be called in some situations: when the cell is about to become visible and when it is selected. Not during normal scrolling. So what you've seen is that setting the placeholder inside tableView:cellForRowAtIndexPath: sizes cell.imageView to a non-zero size and subsequent changes of the image will be visible.

I fixed the issue by calling [cell setNeedsLayout] in the completion handler, like so:

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:MY_IMAGE_URL]];
[NSURLConnection sendAsynchronousRequest:request
                                   queue:self.operationQueue
                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                                            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                                                UIImage *image = [UIImage imageWithData:data];
                                                cell.imageView.image = image;
                                                [cell setNeedsLayout];
                                            }];

I found the completion block happens in the background so that necessitates performing my UI work on the main thread. Of course this solution won't account for cell reuse and so forth, but at least solves why the cell's image wouldn't appear :)

Hope this helps!

Carl Veazey
  • 18,392
  • 8
  • 66
  • 81
  • but the, why does `setNeedsDisplay` not work? The other thing is, if we reuse a cell in `tableView:cellForRowAtIndexPath`, we actually need to do `cell.imageView.image = nil` or else the cell will show the "old" image for some time before the correct image is loaded and replace that – nonopolarity Aug 31 '12 at 07:19
  • `setNeedsDisplay` only calls `drawRect` to be called, I think, which will draw the elements that are there, but will not resize or reposition anything. And yes, there will have to be additional work to handling the reusing of cells, caching the images, and so on. – Carl Veazey Aug 31 '12 at 07:24
  • another observation is, if we actually add a `UIActivityIndicatorView` to where the image will appear, we might want to use an empty, perhaps transparent image, so that the `UIActivityIndicatorView` can be added to the imageView. Otherwise, it won't show. (the indicator is removed in the completion block using `[[imageView viewWithTag:100] removeFromSuperview];` If we add to `cell` or `cell.contentView`, then the text won't get pushed to the right... – nonopolarity Aug 31 '12 at 07:31
  • but if I set `imageView.frame = CGRectMake(0,0, 300,300);` first and then call `[cell setNeedsDisplay];` and also `[cell.imageView setNeedsDisplay];`, the image still won't show – nonopolarity Aug 31 '12 at 07:38
  • That's probably because layoutSubviews sets it back – Carl Veazey Aug 31 '12 at 09:09
0

This doesn't answer your question directly, but a simple workround to your problem would be to use SDWebImage in your cellForRowAtIndexPath: instead. The example on their README page does exactly what you are trying to do.

jonkroll
  • 15,682
  • 4
  • 50
  • 43
  • yes, I collected these all 4: SDWebImage, AFNetworking, TTImageView (from Three20), and ASIHTTPRequest (recommended by Rob Napier)... so I will try them one by one... but for now, I am just experimenting with the iOS 5 built-in method – nonopolarity Aug 31 '12 at 05:42