1

What are the best practices to download several images and load each of them in a UIImageView which is inside a UITableViewCell? Particularly, should I resize/replace the UIImageview after downloading or should I resize the image to fit into the UIImageView. Note that resizing/replacing UIImageView also resize/replace UITableViewCell. Will it cause any issue?

fatih
  • 1,171
  • 2
  • 14
  • 26

2 Answers2

3

A couple of thoughts:

What are the best practices to download several images and load each of them in a UIImageView which is inside a UITableViewCell?

The best practices regarding the downloading of images include:

  1. Definitely avail yourself of lazy loading (load images as you need them and not before).

  2. Download images asynchronously.

  3. Make sure your download technique will cancel requests for tableview cells that are no longer visible. For example, if you're on a slow network and scroll down quickly on your table view, you don't want to tie up your device downloading images that aren't visible anymore.

  4. Make sure you don't make too many concurrent requests of your server. In iOS, when you exceed 5 or 6 concurrent requests, subsequent requests will freeze until the prior ones complete. In worst case scenarios, the subsequent requests will actually start failing as they timeout.

  5. Cache your results. At the very least, cache them in memory. You might also want to cache them to persistent storage (a.k.a. "disk"), too.

If you were going to write your own code for the asynchronous operations, caching, etc. you might want to use NSOperationQueue instead of GCD so that I could constrain number of background requests and make the requests cancelable. You would use NSCache to cache the images. And you'd probably use a UITableViewCell subclass (or a category) so that you can save weak reference to "previous" operation, so that you can cancel any incomplete, prior requests.

As you can see, this is non-trivial, and I'd suggest you using an existing UIImageView category, such as those available as part of SDWebImage or AFNetworking. IMHO, the former is a little richer (e.g. offers disk caching), but if you're doing a lot of networking and want to stick with a single framework, AFNetworking does a great job, too.

Later you ask:

Particularly, should I resize/replace the UIImageview after downloading or should I resize the image to fit into the UIImageView. Note that resizing/replacing UIImageView also resize/replace UITableViewCell. Will it cause any issue?

  1. If your images are larger than what your cell's thumbnail view requires, you have two approaches. First, you can use a contentMode of UIViewContentModeScaleAspectFit or UIViewContentModeScaleAspectFill (and if you use AspectFill, make sure you also set clipsToBounds to YES). Even better, you can actually resize the image after you download it.

  2. It's personal opinion, but I think it's a better UX to have a UIImageView of fixed size on the cell and then when the asynchronous image download is done, just set the image property of the UIImageView. You want the images to gracefully appear in your UI as they're downloaded, but you generally don't want a jarring re-layout of the view while the user is already in the process of reading what's there. If your design absolutely necessitates the re-layout of the cell, then you can just call reloadRowsAtIndexPaths.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • @Yanchi While I appreciate your vote of support, I must confess that I have subsequently learned of significant limitations in that simplistic GCD implementation I posted nearly a year ago. At the very least, I'd suggest `NSOperationQueue` and `NSCache`. Even better, use an existing `UIImageView` category (SDWebImage's or AFNetworking's) that solves many of the issues I enumerate in my revised answer. – Rob Jun 10 '13 at 13:44
  • I tried to use SDWebImage, but it won't compile because of some header file missing, but thanks for new answer! – Yanchi Jun 10 '13 at 14:36
  • @Yanchi I just pulled the most recent `SDWebImage` and there's now a unhelpful dependency on a third party library, [`libwebp`](https://developers.google.com/speed/webp/) and if you don't have that, you'll get a cryptic `"webp/decode.h not found"` error. I've [posted an issue](https://github.com/rs/SDWebImage/issues/415), so hopefully they fix that soon. If not, I posted a comment on how to go back to a version before they introduced this unnecessary dependency. – Rob Jun 10 '13 at 18:17
  • @Yanchi In response to the [issue I posted](https://github.com/rs/SDWebImage/issues/415), they've remedied this issue. If you pull the latest `SDWebImage`, it should now work as advertised, without any strange error about a missing header. – Rob Jun 11 '13 at 14:37
  • 1
    Good job Rob :) I was afraid to write issue as I wasn't sure if the problem is not on my side :) As we had to submit app before it was fixed, I used synchronous request in dispatch_async block.. Downloaded images are saved in dictionary with indexPath.row key so they are not duplicated and you can get image from this cache instead of downloading them again.. I wonder if apple will accept it, because it seems that sometimes app creates ALOT of threads (like 50, if u scroll down really fast) – Yanchi Jun 14 '13 at 12:29
0

A common practice for lazy-loading images in a UITableViewCell is to use a notification callback to let the UITableViewCell know when the image has been received.

In essence, you'll want to create a subclass of UIImageView that has an imageURL field that, when changed, fires off a request for the image, and use that instead of a standard UIImageView:

Interface for the UIImageView subclass:

@property (nonatomic, copy) NSString *imageURL;

Implementation for the UIImageView subclass:

//synthesize property
@synthesize imageURL = _imageURL;

- (void)setImageURL:(NSString *)imageURL {
    if(_imageURL)
        [[NSNotificationCenter defaultCenter] removeObserver:self name:_imageURL object:nil];
    _imageURL = [imageURL copy];
    //if imageURL is valid...
    if(_imageURL.length) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveImage:) name:_imageURL object:nil];
        //fire off some asynchronous image fetch
        //when the image fetch completes, sent off a notification using the imageURL as the notification name
        //It's up to you to create the implementation for this yourself
        ... [MyImageManager fetchImage:_imageURL notificationName:_imageURL]; 
    }
}

- (void)didReceiveImage:(NSNotification*)notification
{
    //handle your received image here
    if([notification.object isKindOfClass:[UIImage class]])
    {
        self.myCustomImageView.image = notification.object;
    }
}

And then in the UITableViewCell class when you override prepareForReuse:

- (void)prepareForReuse {
    [super prepareForReuse];
    self.myCustomImageView.imageURL = nil;
    self.myCustomImageView.image = nil;
    //do the rest of your prepareForReuse here:
    ...
}

Now, as far as the whole re-sizing of the images vs resizing of the imageview, what you ought to do is leave the imageView's size alone, and use the contentMode property to handle the varying sizes for the resulting images. Your possible values are:

UIViewContentModeScaleToFill,
UIViewContentModeScaleAspectFit,
UIViewContentModeScaleAspectFill,
UIViewContentModeRedraw,
UIViewContentModeCenter,
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,

Each of which has their own respective result - you will probably want to use UIViewContentModeScaleAspectFit as this will re-size the image to fit the imageView, without distorting it - this may leave empty margins to the size or top/bottom of the imageView. Aspect fill will do a similar thing, only it will resize the image to be large enough to fill the entire imageView, and may cut off the sides or top/bottom of the image. Scale to fill will just stretch the image to fill the imageView.

CrimsonDiego
  • 3,616
  • 1
  • 23
  • 26
  • so are you saying resizing each cell whenever I got the image is OK or not? – fatih Jul 20 '12 at 16:39
  • When you actually set the image, you do not need to resize the UIImageView. What you can do is set the imageView's `contentMode`. I'll edit my answer to reflect this. – CrimsonDiego Jul 20 '12 at 16:43
  • as you point out in the last paragraph, it may left out some margin if I don't resize the imageview and this is not desirable. – fatih Jul 20 '12 at 17:07
  • If you are not willing to scale the image, then why did you ask about that? You have two choices - resize the imageView, or resize the image. If you refuse to scale the image, then you have to resize the imageView (and probably the cell along with it). [You do not need to reload the entire table](http://stackoverflow.com/questions/3069339/how-to-dynamically-resize-uitableviewcell-height) - you can just reload the cell with a new height. – CrimsonDiego Jul 20 '12 at 17:21