西海岸より

つらつらざつざつと

非同期で画像をロードするUIImageView

多くのWeb画像をUIImageViewで表示する時に、読み込み中はインジケータアニメーションを表示し、画像ダウンロード完了後に画像を表示するというものがほしかったので作成。(一般には既出かと)

以下のような、UIImageのImageWithURLでは、同期で画像をWebから取得しようとするため、その間画面はフリーズしたように見えてしまう。

    UIImageView *imageView = [[UIImageView alloc]
                              initWithImage:
                              [UIImage imageWithData:
                               [NSData dataWithContentsOfURL:url]]];

ソース

使い方としては、URLを指定して初期化し、読み込みをキックしてやると自動で非同期に画像を取得し、表示してくれるUIImageViewで、コードは以下の通り。

  • LazyImageView.h
#import <UIKit/UIKit.h>

@interface LazyImageView : UIImageView

// Use this initializer.
- (id)initWithFrame:(CGRect)frame withUrl:(NSURL *)url;

// Start load the image of the specified url.
// Doesn't load the image if an image has been already set.
- (void)startLoadImage;

// Reload load the image of the specified url.
// Load the image even if a connection was already created.
- (void)reloadImage;

// Cancel loading if requesting.
- (void)cancelLoading;

// Set the url of the image to reqeust.
@property (nonatomic, retain) NSURL *imageUrl;

@end
  • LazyImageView.m
#import "LazyImageView.h"

typedef enum LazyImageViewTag_ {
  LazyImageViewTagIndicatorView = 1,
} LazyImageViewTag;

@interface LazyImageView ()

- (void)setLoadingImage;
- (void)setLoadErrorImage;

@property (nonatomic, retain) NSURLConnection *connection;
@property (nonatomic, retain) NSMutableData *data;

@end

@implementation LazyImageView

- (id)initWithFrame:(CGRect)frame withUrl:(NSURL *)url {
  self = [super initWithFrame:frame];
  
  if (self) {
    [self setImageUrl:url];
  }
  
  return self;
}

- (void)startLoadImage {
  
  if (self.image) return;
  
  if (self.connection) {
    [self.connection cancel];
  }
 
  [self setData:[NSMutableData data]];
  
  NSURLRequest *req = [NSURLRequest requestWithURL:self.imageUrl];  
  NSURLConnection *con = [NSURLConnection connectionWithRequest:req delegate:self];
  [self setConnection:con];
  
  [self setLoadingImage];
}


- (void)reloadImage {
  [self setImage:nil];
  [self startLoadImage];
}

- (void)cancelLoading {
  [self.connection cancel];
  [self setConnection:nil];
  
  if (self.image == nil) {
    [self setLoadErrorImage];
  }
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  [self.data appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  [self setLoadErrorImage];
  [connection autorelease];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {  
  UIActivityIndicatorView *iv = (UIActivityIndicatorView *)
  [self viewWithTag:LazyImageViewTagIndicatorView];
  if (iv) [iv removeFromSuperview];
  
  [self setImage:[UIImage imageWithData:self.data]];
  
  [connection autorelease];
}

#pragma mark -

- (void)setLoadingImage {
  UIActivityIndicatorView *iv = (UIActivityIndicatorView *)
                                [self viewWithTag:LazyImageViewTagIndicatorView];
  if (iv == nil) {
    iv = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray] autorelease];
    [iv setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
    [iv setTag:LazyImageViewTagIndicatorView];
    [self addSubview:iv];
  }
  [iv startAnimating];
  [iv setHidden:NO];
  
  //[self setBackgroundColor:[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.3]];
  [self setBackgroundColor:[UIColor whiteColor]];
}

- (void)setLoadErrorImage {
  UIActivityIndicatorView *iv = (UIActivityIndicatorView *)
  [self viewWithTag:LazyImageViewTagIndicatorView];
  [iv removeFromSuperview];
  
  [self setImage:nil];
  //[self setBackgroundColor:[UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.4]];
  [self setBackgroundColor:[UIColor redColor]];
}

#pragma mark -

- (void)dealloc {
  [self setImageUrl:nil];
  [self.connection cancel];
  [self setConnection:nil];
  [self setData:nil];
  [super dealloc];
}

@synthesize imageUrl;
@synthesize connection;
@synthesize data;

@end

使い方

    // 初期化
    LazyImageView *imageView = [[LazyImageView alloc] initWithFrame:rect withUrl:url];
    // 画像のロードを開始
    [imageView startLoadImage];

    // Viewを追加
    [self.view addSubview:imageView];