NIPhotoScrollViewを使って実装する

画像を全画面表示してズーム機能もつけたいことはよくあると思います。一から実装するのは大変なので、nimbusのNIPhotoScrollViewを使うと良いでしょう。サンプルコードを用意したので、それを元に解説したいと思います。

https://github.com/stack3/NIPhotoScrollViewSample

このサンプルは、nimbusとAFNetworkingの中から必要なソースファイルが追加された状態になっています。

NIPhotoScrollViewは全画面表示とズーム機能がありますが、それだけではありません。最初はサムネイルを表示して、サーバーからオリジナル画像(高解像度画像)をダウンロードしたら、そちらに表示を切り替えることを想定した仕組みもあります。このサンプルもサーバーからオリジナル画像をダウンロードして表示するようにしています。

サンプルを起動しましょう。

縦画面起動時

01

横画面起動時

02

ピンチ操作やダブルタップでズーム

03

なんだか、おなか減ってきましたが・・・

NIPhotoScrollViewのメンバー変数

STViewController.mを開いてください。
メンバー変数の宣言です。

@implementation STViewController {
    __weak NIPhotoScrollView *_photoScrollView;
    __strong NSOperationQueue *_operationQueue;
    __strong UIImage *_thumbnailImage;
    __strong UIImage *_originalImage;
    __strong NSURL *_originalImageURL;
}
  • _photoScrollView NIPhotoScrollViewオブジェクト
  • _operationQueue ネットワークから画像を読み込むOperation用のキュー
  • _thumbnailImage サムネイル画像
  • _originalImage オリジナル画像
  • _originalImageURL オリジナル画像のURL

viewDidLoad

ViewController#viewにaddSubviewするのは、NIPhotoScrollViewだけです。よってInterface Builderを使ってレイアウトするまでもないので、プログラムで生成しています。

CGRect bounds = self.view.bounds;

NIPhotoScrollView *photoScrollView = [[NIPhotoScrollView alloc] initWithFrame:bounds];
_photoScrollView = photoScrollView;
_photoScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
_photoScrollView.photoDimensions = CGSizeMake(800, 600);
[self.view addSubview:_photoScrollView];
}

frameをview.boundsと同じに、autoresizingMaskを横縦伸縮指定して、viewのサイズが変わっても全画面表示を保つようにします。photoDimensionにはオリジナル画像のサイズを設定します。

ネットワーク画像のダウンロードはAFNetworkingを使うのですが、そのためにNSOperationQueueが必要なので初期化しておきます。

_operationQueue = [[NSOperationQueue alloc] init];

サムネイルの画像はリソースから読んでおきます。オリジナル画像のURLも初期化しています。

_thumbnailImage = [UIImage imageNamed:@"ramen200x150.jpg"];
_originalImageURL = [NSURL URLWithString:@"http://cdn-ak.f.st-hatena.com/images/fotolife/e/eimei23/20120413/20120413232454.jpg"];

実践ではViewControllerのinitメソッドで引数に受けて初期化することになるでしょう。

viewWillAppear

viewWillAppearでサムネイル画像を設定します。もし既にオリジナル画像が設定されていれば、それを設定します。

- (void)viewWillAppear:(BOOL)animated
{
    if (_originalImage) {
        [_photoScrollView setImage:_originalImage photoSize:NIPhotoScrollViewPhotoSizeOriginal];
    } else {
        [_photoScrollView setImage:_thumbnailImage photoSize:NIPhotoScrollViewPhotoSizeThumbnail];
    }
}

画像の設定はNIPhotoScrollView#setImage:photoSizeを使います。photoSizeに以下を指定してサムネイルかオリジナルかを区別します。

  • NIPhotoScrollViewPhotoSizeThumbnail サムネイル画像
  • NIPhotoScrollViewPhotoSizeOriginal オリジナル画像

本サンプルでは、この段階では常に_originalImageはnilですが、実践ではオリジナル画像のキャッシュがあれば、_originalImageはnilでなく、それを設定する処理になるでしょう。

この画像の設定処理は、viewDidLoadでやるとうまく表示されないことがありました。viewDidLoadの段階ではview.frame.sizeが実際に表示されるものと異なる場合があるからだと思います。よってview.frame.sizeが確定しているviewWillAppearでやることが望ましいと思います。

viewDidAppear

viewDidAppearでは、オリジナル画像がまだ読まれていないなら、AFImageRequestOperationを使って画像をダウンロードします。ダウンロードした画像は、NIPhotoScrollView#setImage:photoSizeを使ってオリジナル画像(NIPhotoScrollViewPhotoSizeOriginal)として設定します。

- (void)viewDidAppear:(BOOL)animated
{
    if (_originalImage == nil) {
        //
        // Start Loading original image.
        //
        NSURLRequest *request = [NSURLRequest requestWithURL:_originalImageURL];
        NSOperation *operation =
        [AFImageRequestOperation
           imageRequestOperationWithRequest:request
                                    success:^(UIImage *image) {
                                        _originalImage = image;
                                        [_photoScrollView setImage:_originalImage
                                                         photoSize:NIPhotoScrollViewPhotoSizeOriginal];
                                    }];
        [_operationQueue addOperation:operation];
    }
}

この処理はviewWillAppearに含めても問題ないと思います。なんとなくサムネイル画像が表示されてからオリジナル画像を読みに行くほうが良い気がしたので、そのようにしています。

willAnimateRotationToInterfaceOrientation

画面を回転させた時、NIPhotoScrollViewに画像を再設定しないと回転前の画像サイズのままで見栄えがよくありません。
たとえば縦画面から横画面に切り替えた時、このように縦画面のサイズのままになります。

04

ここは以下のように画面に合わせた表示に切り替わって欲しいですね。

02

このようにするために、willAnimateRotationToInterfaceOrientationを実装します。

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    if (_originalImage) {
        [_photoScrollView setImage:_originalImage photoSize:NIPhotoScrollViewPhotoSizeOriginal];
    } else {
        [_photoScrollView setImage:_thumbnailImage photoSize:NIPhotoScrollViewPhotoSizeThumbnail];
    }
}

以上のように若干の注意点はありますが少ない実装で実現できます。

NIPhotoScrollViewまとめ

最後にNIPhotoScrollViewについてまとめておきます。

  • NIPhotoScrollView#photoDimensionsにオリジナル画像のサイズを設定する
  • NIPhotoScrollView#setImage:photoSizeでサムネイルもしくはオリジナル画像を設定する
  • オリジナル画像ダウンロード前はサムネイルを設定し、ダウンロード後はオリジナル画像を設定する
  • UIViewController#viewWillAppearで画像を設定する
  • UIViewController#willAnimateRotationToInterfaceOrientationで画像を再設定する

nimbusの詳しいインストール方法は、nimbusのREADMEなどを参考にしてください。日々更新されているので、ここで現状のものを書いても情報が古くなると思いますので。