前回は、画像ビューワでフリックによる移動を実現させた。今回は、ピンチイン/アウトによる拡大縮小を実装しよう。様々な比率の画像に対応しようとすると、それなりの手間がかかる。そのことについて説明しよう。

UIScrollViewによる拡大縮小の問題点

ピンチイン/アウトによる拡大縮小は、単純に対応させるだけであれば、簡単である。UIScrollViewのデリゲートメソッドであるviewForZoomingInScrollView:を実装して、拡大を行うためのビューを返してやればいい。今回の場合であれば、_imageView1ということになる。

List 1.

- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView
{
    // サブスクロールビューの場合
    if (scrollView == _subScrollView) {
        // 中央のimage viewを使う
        return _imageView1;
    }

    return nil;
}

試しにこのように実装すると、一見うまく動く。ただしそれは、表示する画像とスクロールビューの大きさの比率が同じであるときに限られる。つまり、iPhoneを縦向きに持ち、同じく縦画面サイズの画像を表示するだけであれば問題ない。だが、横画面サイズの画像を表示しようとすると、問題が発生する。

UIScrollViewは、拡大縮小するサブビューを左上を基準に考えようとする。したがって、横画面サイズの画像全体を表示できるように縮小させると、画像が画面の上の方に寄ってしまうという事態が発生する。

画像が画面上方に寄ってしまっている

これは、はっきりいって格好悪い。やはり画面中央に表示されるのが望ましいだろう。

では、どうすればいいのか。基本的な考え方は、ズーム中およびズーム後、にサブビューを移動させてスクロールビューの真ん中に移動するようにすればいい。この対応を行ってみよう。

イメージビューとサブスクロールビューの設定

まずは、_renewImagesメソッドを変更しよう。いままでは同比率の画像を表示することしか考えていなかったので、単純に画像をイメージビューに設定するだけだった。だが異なる比率の画像を表示するとなると、サブスクロールビューの設定を適切に行う必要が出てくる。具体的には、コンテントサイズと、スケールの設定を行う。コンテントサイズは、画像の実サイズを指定する。スケールは、画像全体が表示されている状態が最小スケールになるようにしてやる。

ソースコードを紹介しよう。以前の_renewImagesの実装のうち、中央のイメージビューおよびサブスクロールビューの更新部分を編集することになる。

まず行うのは、サブスクロールビューおよびイメージビューのスケールをリセットすることだ。というのは、この_renewImagesメソッドが呼び出されるとき、拡大縮小の対象となっている_imageView1は、様々なスケールで表示されていることが予想される。UIScrollViewによる拡大縮小は、対象となるビューのtransformを変更することによって実現される。これからイメージビューのframeなどの設定を行うのだが、このときtransformがバラバラだと都合が悪い。そこで、一旦リセットしてやるのだ。

プログラム中からリセットを行うには、UIScrollViewのzoomScaleを1.0にし、同時にサブビューのtransformをCGAffineTransformIdentityにしてやる。

List 2.

    ...

    // サブスクロールビューのスケールをリセットする
    _subScrollView.zoomScale = 1.0f;
    _imageView1.transform = CGAffineTransformIdentity;

    ...

現在のiOSだと、zoomScaleを1.0にしただけではサブビューのtransformは変わらないようだ。そこで明示的に設定するようにした。

この状態で中央のイメージビューの設定を行ってやる。frameとimageの設定を行おう。

List 3.

    ...

    // 中央のimage viewの設定
    rect.origin = CGPointZero;
    rect.size = image.size;
    _imageView1.frame = rect;
    _imageView1.image = image;
    [image release];

    ...

ポイントは、frameの大きさを画像の大きさにしているところだ。これにより、スクロールビューのスケールを1.0にしたときに、画像が等倍で表示されることになる。

次にサブスクロールビューの設定を行う。コンテントサイズは画像サイズに。スケールは、最小スケールを画像が画面内部に収まるように計算する。最大スケールは1.0だ。

List 4.

    ...

    // サブスクロールビューのコンテントサイズを設定する
    _subScrollView.contentSize = image.size;

    // サブスクロールビューのスケールを設定する
    float   hScale, vScale, minScale;
    hScale = CGRectGetWidth(_subScrollView.bounds) / image.size.width;
    vScale = CGRectGetHeight(_subScrollView.bounds) / image.size.height;
    minScale = MIN(hScale, vScale);
    _subScrollView.minimumZoomScale = minScale;
    _subScrollView.maximumZoomScale = 1.0f;
    _subScrollView.zoomScale = minScale;

    ...

最小スケールは、サブスクロールビューの大きさと、画像サイズから計算している。

これで、画像の表示とサブスクロールビューの設定はできた。次は、画面中央への移動だ。

画面中央への移動

画面中央への移動は、新しいメソッドを作ってその中で行おう。_updateImageCenterという名前にする。

中央への移動は、UIViewのcenterプロパティを設定することで行う。次のような手順になるだろう。まず、画像が現在画面上にどのくらいの大きさで表示されているかを取得する。これは、画像の実サイズと、スクロールビューのスケールから計算することができる。この値とサブスクロールビューの大きさを比べることで、中央の座標を割り出してやるのだ。

List 5.

- (void)_updateImageCenter
{
    // 画像の表示されている大きさを取得
    UIImage*    image;
    CGSize      imageSize;
    image = _imageView1.image;
    imageSize = image.size;
    imageSize.width *= _subScrollView.zoomScale;
    imageSize.height *= _subScrollView.zoomScale;

    // サブスクロールビューの大きさを取得
    CGRect  bounds;
    bounds = _subScrollView.bounds;

    // イメージビューの中央の座標を計算
    CGPoint point;
    point.x = imageSize.width * 0.5f;
    if (imageSize.width < CGRectGetWidth(bounds)) {
        point.x += (CGRectGetWidth(bounds) - imageSize.width) * 0.5f;
    }
    point.y = imageSize.height * 0.5f;
    if (imageSize.height < CGRectGetHeight(bounds)) {
        point.y += (CGRectGetHeight(bounds) - imageSize.height) * 0.5f;
    }
    _imageView1.center = point;
}

centerの値は、基本的には画像サイズの中央になる。例外として、画像サイズがスクロールビューの大きさより小さい場合は、画面中央になるように計算してやることになる。

このメソッドは、ズーム中およびズーム後に呼び出す。UIScrollViewのデリゲートメソッドであるscrollViewDidZoom:とscrollViewDidEndZooming:withView:atScale:を実装すればいい。

List 6.

- (void)scrollViewDidZoom:(UIScrollView*)scrollView
{
    // Update appearance
    [self _updateImageCenter];
}

- (void)scrollViewDidEndZooming:(UIScrollView*)scrollView
        withView:(UIView*)view atScale:(float)scale
{
    [self _updateImageCenter];
}

これで横向きの画像であっても、期待通りに拡大縮小できるはずだ。

ここまでのソースコード: Image-2.zip