UISrollViewで複数のサブビューを拡大・縮小する

2012.10.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

UIScrollViewに複数のサブビューを配置した状態で拡大・縮小に対応したいというよくありそうな場面に直面し、「まっ余裕でしょ」とたかをくくっていたらちょっとはまってしまいましたのでメモします。

UIScrollViewというのはもともとiOSアプリの中の表示や操作の対象となるコンテンツが画面に入りきらない場合に使われます。 UIScrollViewではその対象となるコンテンツに対して、

  1. ユーザが表示したいコンテンツの領域をドラッグして表示する
  2. ユーザがピンチジェスチャ(つまんだりひろげたり)を使ってコンテンツの表示を拡大縮小する

などの操作が行えるようにしてくれます。今回のはまりどころは上記2番目のピンチジェスチャについてです。

サブビューが1つの場合

ピンチジェスチャを有効にするには、

  • 拡大率、縮小率を指定
  • UIScrollViewDelegateの- viewForZoomingInScrollView:メソッドを実装

してやる必要があります。

よくある使い方

@interface ViewController ()
・・・・
@property (strong, nonatomic) UIScrollView *aScrollView;
・・・・
@end

- (void)viewDidLoad
{
    [super viewDidLoad];

    ・・・・

    // UIScrollViewインスタンスを生成
    self.aScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.aScrollView.delegate = self;
    self.aScrollView.contentSize = CGSizeMake(1600, 1200);

     // 縮小・拡大率を設定
    self.aScrollView.minimumZoomScale = 0.5f;
    self.aScrollView.maximumZoomScale = 5.0f;

    // ターゲットとなるコンテンツをaScrollViewのサブビューに追加
    [self.aScrollView addSubview:aTargetView];

    [self.view addSubview:self.aScrollView];

    ・・・・
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    // 対象となるコンテンツ(View)を教える
    // ここではaTargetViewが返る
    return [[self.aScrollView subviews] objectAtIndex:0];
}

これで「- viewForZoomingInScrollView:」メソッドの戻り値として指定したコンテンツを、指定した拡大・縮小率の範囲内でピンチイン・ピンチアウトすることができます。

サブビューが複数ある場合はどうすんの?

ここで問題なのが、- viewForZoomingInScrollView:メソッドが戻り値として対象となるコンテンツを1つしか返せないということです。 これだとUIScrollViewに複数のコンテンツを配置した場合、「いったいどいつを返したらいいの??」となってしまいます。

ターゲットとなるコンテンツが複数の場合

- (void)viewDidLoad
{
    [super viewDidLoad];

    ・・・・
    // ターゲットとなるコンテンツを複数追加
    [self.aScrollView addSubview:aTargetView1];
    [self.aScrollView addSubview:aTargetView2];
    ・・・・
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    // なにを返せばいいの??
    return ????;
}

UIViewを間にかます!

この問題は、UIScrollViewに直接ターゲットとなるコンテンツをaddSubviewせず、間にUIViewをかますことで解決できます。具体的には、

  1. UIScrollViewのcontentSizeと同じ大きさのUIViewを用意
  2. 1のUIViewにターゲットとなるコンテンツをaddSubview
  3. 1のUIViewをUIScrollViewにaddSubview!!

で解決できます。

いっちょサンプルコード!

XCodeを開き、Single View Applicationを選択してプロジェクトを作成し、以下のソースに書き換えて試してみてください。

なお、以下のサンプルは下記の環境で動作確認しました。

  • Xcode 4.5.1
  • iOS SDK 6.0
  • Storyboard, ARC使用

サンプルコード

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIScrollViewDelegate>

@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) UIScrollView *aScrollView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // UIScrollViewインスタンスを生成
    self.aScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.aScrollView.delegate = self;
    self.aScrollView.contentSize = CGSizeMake(1600.0f, 1200.0f);
    
    // 縮小・拡大率を設定
    self.aScrollView.minimumZoomScale = 0.5f;
    self.aScrollView.maximumZoomScale = 5.0f;
    
    // 間にかますUIViewを生成
    UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(0.0f,
                                                             0.0f,
                                                             self.aScrollView.contentSize.width,
                                                             self.aScrollView.contentSize.height)];
    
    // ターゲットとなるコンテンツ1 緑の矩形
    UIView *aTargetView1 = [[UIView alloc] initWithFrame:CGRectMake(80.0f, 100.0f, 100.0f, 50.0f)];
    aTargetView1.backgroundColor = [UIColor greenColor];
    
    // ターゲットとなるコンテンツ2 赤の矩形
    UIView *aTargetView2 = [[UIView alloc] initWithFrame:CGRectMake(160.0f, 180.0f, 80.0f, 80.0f)];
    aTargetView2.backgroundColor = [UIColor redColor];
    
    // 間にかますUIViewのサブビューに追加
    [aView addSubview:aTargetView1];
    [aView addSubview:aTargetView2];
    
    // 間にかますUIViewをUIScrollViewのサブビューに追加
    [self.aScrollView addSubview:aView];
    
    [self.view addSubview:self.aScrollView];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    // 対象となるコンテンツ(View)を教える
    // ここではaViewが返る
    return [[self.aScrollView subviews] objectAtIndex:0];
}

@end

実行例

UISrollViewで複数のサブビューを拡大・縮小実行例

まとめ

わかってしまえば簡単というか当たり前ですが、それ故はまってしまうとダメージがでかいです。 もし同じことでお悩みの方は、この記事をみて私のように無駄な時間を過ごさぬよう気を付けてください。