Simmons, Brent Simmons
Unless you do everything you possibly could to stay away from anything related to iOS development, then you’ve at a bare minimum stumpled upon at least one of Brent Simmons articles on the continued development of Vesper. I try to blog somewhat regularly, but the frequency tends decrease the more involved I am with a project. However, with Mr. Simmons, it seems to be the opposite and I’m inspired to do more of the same. Being a devout reader of his chronicals it has been comforting to know that a developer, such as himself, goes through many of the conumdrums of development decisions that myself and I’m sure many others do. In addition, I think that the fact a) Brent is willing to disclose so much information regarding the development b) The great responses from other developers on his approaches are exemplary of how great the iOS/Mac dev commnuity is. The fact is that iOS development is hard and no matter how good you are the answers aren’t always straightforward and there is always something new and interesting to learn.
Asynchronous Image Loading - Hasn’t This Already Been Solved
How many guitarist does it take to play Stairway to Heaven? 100…1 to actually change it and the other 99 to say, Not bad, but I could do it better
I’ve seen many different ways to handle effective asynchronous image dowloading, specifically in the context of UITableView’s. Most of the time this involves some variation of utilizing Grand Central Dispatch, which in this context isn’t very effecient for few reasons.
- Not a good way to manage the active queues
- You end putting in way too many references to the cell instance in
UIScrollView
delegate methods - Your controller code becomes too large for it’s own good.
My Version
I’m not including any caching logic because that is a subject for another blog (debate)
Benefits
- Utilizes
NSOperation
andNSOperationQueues
- You can have as many operations as you want in your cell subclass
- Reference to the cell instance via block param
- Using
UITableViewDelegate
methods to cancel any download operation for cells that aren’t onscreen
PhotoViewerCell.h
@class PhotoViewerCell;
@class Photo;
extern NSString * const PhotoViewerCellIdentifier;
typedef PhotoViewerCell* (^PhotoViewerCellBlock)(void);
@interface PhotoViewerCell : UITableViewCell
@property (nonatomic, strong) Photo *photo;
- (void)loadPhotoDataForCell:(PhotoViewerCellBlock)currentCell
withPhotoInstance:(Photo *)aPhoto
queue:(NSOperationQueue *)queue;
- (void)cancelDownload;
PhotoViewerCell.m
#import "PhotoViewerCell.h"
#import "Photo.h"
NSString * const PhotoViewerCellIdentifier = @"PhotoViewerCellIdentifier";
@interface PhotoViewerCell()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, weak) NSOperation *fullImageRequestOperation;
@end
@implementation PhotoViewerCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
_imageView = [[UIImageView alloc] initWithFrame:frame];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView.clipsToBounds = YES;
[self.contentView addSubview:_imageView];
}
return self;
}
#pragma mark - Public Methods
- (void)loadPhotoDataForCell:(PhotoViewerCellBlock)currentCell
withPhotoInstance:(Photo *)aPhoto
queue:(NSOperationQueue *)queue {
NSOperation *fullImageBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSString *fullsizeImageUrl = [NSString stringWithFormat:@"%@%@/iPhoneFullsizeImage",
[FFManager sharedManager].ff.baseUrl, [aPhoto ms_ffUrl]];
NSURL *downloadURL = [NSURL URLWithString:fullsizeImageUrl];
NSData *imageData = [NSData dataWithContentsOfURL:downloadURL];
UIImage *scaledImage = [UIImage imageWithData:imageData scale:[UIScreen mainScreen].scale];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
PhotoViewerCell *originalCell = (id) currentCell();
originalCell.imageView.image = scaledImage;
}];
}];
self.fullImageRequestOperation = fullImageBlockOperation;
[queue addOperation:self.fullImageRequestOperation];
self.imageView.image = [UIImage imageNamed:@"placeholder"];
}
- (void)cancelDownload {
if (self.fullImageRequestOperation) {
MSLog(@"cancelled");
[self.fullImageRequestOperation cancel];
}
}
@end
ViewController Snippet
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.imageDownloadQueue cancelAllOperations];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PhotoViewerCell *cell = [tableView dequeueReusableCellWithIdentifier:PhotoViewerCellIdentifier
forIndexPath:indexPath];
PhotoViewerCell * (^currentCellForBlock)(void) = ^PhotoViewerCell*{
return (id) [tableView cellForRowAtIndexPath:indexPath];
};
Photo *currentPhoto = (Photo *)self.photos[indexPath.row];
[cell loadPhotoDataForCell:currentCellForBlock
withPhoto:currentPhoto
queue:self.imageDownloadQueue];
return cell;
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
PhotoViewerCell *currentCell = (PhotoViewerCell *)[tableView cellForRowAtIndexPath:indexPath];
[currentCell cancelDownloads];
}