[iOS] スクロールビュー上に置いた拡大・縮小できるイメージビュー

2016.06.23

1 はじめに

別にコレと言って珍しい訳ではないのですが・・・ 今回、写真の選択と拡大表示できるとサンプルを書いてみたので紹介させて下さい。

動作しているイメージは、次のとおりです。

001 002 003 004 005

下の列に写真のサムネイルが並んでおり、選択すると上のビューに表示されます。 そして、上のビューでは、ピンチイン・ピンチアウトで、拡大縮小ができます。 それだけ・・・

コードは、下記におきました。ライセンスの関係上、写真(画像)については、Guthub上には置いていません。 動作を確認する場合は、別途「001.jpg〜030.jpg」をプロジェクトに追加して下さい。


github [GitHub] UIScrollView上の拡大できるイメージビュー

2 サムネイル表示

下段のサムネイルを表示しているコードは、次のとおりです。

- (void)viewDidAppear:(BOOL)animated{

    // 写真を配置する 元写真の大きさからサイズを決定する
    const NSInteger baseSize = 100; // 基準サイズ
    const NSInteger space = 10; // 余白
    NSInteger offSet = 10;
    for (NSInteger i=0; i<30; i++) { // 30枚の写真を順次並べます

        // 写真ごとにUIImageViewを生成する
        NSString *photoName = [NSString stringWithFormat:@"%3.3d.jpg",i+1];
        UIImage *image = [UIImage imageNamed:photoName];
        UIImageView *imageView = [[UIImageView alloc] initWithImage:image];

        // サイズを縮小(ランドスケープをポートレートを判断)
        CGSize size = image.size;
        if ( size.width < size.height){ // portrait
            NSInteger width = baseSize * (size.width / size.height);
            // 配置は、オフセット情報を頼りに前の写真の右に
            imageView.frame = CGRectMake(offSet, space, width, baseSize);
            // オフセットをづらす
            offSet += width + space;
        } else { // landscape
            NSInteger height = baseSize * (size.height / size.width);
            // 配置は、オフセット情報を頼りに前の写真の右に
            imageView.frame = CGRectMake(offSet ,space + (baseSize - height)/2, baseSize, height);
            // オフセットをづらす
            offSet += baseSize + space;
        }

        imageView.userInteractionEnabled = YES;//タッチイベントを受け取る
        imageView.tag = i+1;

        // スクロールビューに追加する
        [self.selectView addSubview:imageView];

    }
    self.selectScrollView.contentSize = CGRectMake(0, 0, offSet, 100 + space * 2).size;
}

基準となるサイズを元に、ランドスケープか、ポートレートか判断して、右にどんどん並べます。 そして、最終的に配置に必要としたサイズを、UIScrollViewcontentSizeプロパティにセットしています。

3 サムネイルのタッチイベント

スクロールビューでは、その中に配置されたコントロールへのタッチイベントを処理するためには、そのイベントを次のレスポンダーに渡すことを明示的に記述する必要があります。

UIScrollView+TouchEvent.h

#import <UIKit/UIKit.h>

@interface UIScrollView(TouchEvent)
@end

UIScrollView+TouchEvent.m

<br />#import "UIScrollView+TouchEvent.h"

@implementation UIScrollView(TouchEvent)
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [[self nextResponder] touchesBegan:touches withEvent:event];
}
@end

後は、イベントを受け取りたいビューのuserInteractionEnabledYESをセットすることでイベントを受け取れるようになります

imageView.userInteractionEnabled = YES;//タッチイベントを受け取る
imageView.tag = i+1;

なお、どのビューがタップされたのか分かるように、tagプロパティに値を設定しました。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    NSLog( @"%ld",touch.view.tag ); //タップされたビューのtagプロパティを表示する
}

4 拡大できるビュー

上段の写真を拡大できるビューは、スクロールビューのサブクラスとなっています。

中に配置するUIImageViewは、プロパティで宣言しています。 そして、ストーリボードから生成される際に、今回の動作に必要なスクロールビューとして設定を一通り行っています。

ImageScrollView.m

@interface ImageScrollView () <UIScrollViewDelegate>
// スクロールビューの中に配置するイメージビュー
@property UIImageView *imageView;
@end

@implementation ImageScrollView
- (void)awakeFromNib
{
    self.showsVerticalScrollIndicator = NO;
    self.showsHorizontalScrollIndicator = NO;
    self.bouncesZoom = YES; // スクロール外までスクロールしてバウンドして戻る動作
    self.decelerationRate = UIScrollViewDecelerationRateFast; // スクロールのスピード
    self.delegate = self;
}

・・・省略・・・

写真を指定するメソッドを次の通りです。

- (void)setImage:(UIImage *)image
{
    // 新しい画像でビューを再作成する
    [self.imageView removeFromSuperview];
    self.imageView = nil;
    self.imageView = [[UIImageView alloc] initWithImage:image];
    [self addSubview:self.imageView];

    //画像のサイズを倍率にすればスクロールビュー(画面)いっぱいになるかを計算する
    float scale = 0;
    if ( image.size.width < image.size.height){
        // ポートレートの場合は、縦のサイズを基準に縮尺を得る
        scale = self.bounds.size.height / image.size.height;
    } else {
        // ランドスケープの場合は、横のサイズを基準に縮尺を得る
        scale= self.bounds.size.width / image.size.width;
    }
    self.minimumZoomScale = scale; // 現在のサイズが最小とする
    self.maximumZoomScale = scale * 7; // 拡大は7倍まで可能
    self.zoomScale = scale; // 当初画面いっぱいで表示する

    // 画像ビューを中央に配置する
    // 初期値 X=0,Y=0とする
    CGRect newFrame = CGRectMake(0,0,self.imageView.frame.size.width,self.imageView.frame.size.height);
    // 水平方向余白がある場合はX座標をずらす
    if (self.imageView.frame.size.width < self.bounds.size.width)
        newFrame.origin.x = (self.bounds.size.width - self.imageView.frame.size.width) / 2;
    // 垂直方向余白がある場合はY座標をずらす
    if (self.imageView.frame.size.height < self.bounds.size.height)
        newFrame.origin.y = (self.bounds.size.height - self.imageView.frame.size.height) / 2;
    self.imageView.frame = newFrame;
}

ここでの作業は下記のとおりです。

  • UIImageViewの生成
  • 画像を何倍で表示すると画面にちょうど一杯になるかの比率を計算する
  • 上記をズーム最小値とする
  • ズーム最小値の7倍をスーム最大値とする
  • 現在のズームをズーム最小値にする
  • イメージビューのオフセットをずらして、写真を中央に配置する

やってることは、大した事ないのですが、UIScrollViewのズームの概念と、中に配置するビューのオフセット概念が理解できていないと、ちょっとややこしいかも知れません。

考え方のコツとしては、中のビュー(今回で言えば、UIImageView)のframeでスクロールビュー内での位置決めをするというあたりでは無いでしょうか。

5 最後に

今回は、スクロールビューを使用した写真の選択と、同じくスクロールビュー上の拡大できるイメージビューを書いてみました。

特に目新しいものではありませんが、スクロールビューで混乱した時の参考にでもなれば幸いです。

6 参考リンク


Appleサンプル - ScrollViewSuite
Appleサンプル - PhotoScroller
UIScrollViewにおけるAutoLayoutについてまとめる
UIScrollViewでズーム可能なUIImageのスライドビューワー
UIScrollViewの中央にUIImageViewを配置しつつズーム可能にする