24/7 twenty-four seven

iOS/OS X application programing topics.

CATiledLayerとUIScrollViewを使って、超巨大な画像を表示するサンプル

(参考) 無為空間 |タイルビューの挙動確認用サンプル
CATiledLayerとUIScrollViewを組み合わせて、巨大な画像をスムーズにスクロールして表示するサンプルです。


表示する画像はこちらを使用しました。Garden | photo page - everystockphoto
画像の大きさは2448x3264です。


大きな画像を一度に読み込むとメモリが足りなくなるので、あらかじめ小さな単位に分割しておきます。
画像の分割はGraphicConverterなどを使用すると簡単です。
分割した画像を、画面に表示される部分だけ読み込むので、ファイル名を規則的に付けておきます。
GraphicConverterなら、自動的にimage-01-01.jpg, image-01-02.jpg, ...のような名前に自動的に付けてくれます。


今回は、タテ、ヨコそれぞれ10分割にしました。
分割後の画像サイズは245x327です。


まず、表示レイヤーにCATiledViewを使用するビュー(TiledView)を作成します。

+ (Class)layerClass {  
    return [CATiledLayer class];  
}


ビュー全体のサイズに、もとの画像サイズを指定します。
タイル1つぶんのサイズに分割後の画像サイズを指定します。

TiledView *tiledView = [[TiledView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 2448.0f, 3264.0f)];
tiledView.tiledLayer.tileSize = CGSizeMake(245.0f, 327.0f);


TiledViewをスクロールビューのサブビューとして設定します。

scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];  
scrollView.contentSize = tiledView.bounds.size;
scrollView.delegate = tiledView;
[scrollView addSubview:tiledView];


TiledViewでは、次に表示されるタイルごとにdrawRect:が呼ばれます。
どの位置のタイルなのかを判定して、適切な画像を読み込み、描画します。

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
	
    int xmin = CGRectGetMinX(rect);
    int ymin = CGRectGetMinY(rect); 
    int x = xmin / CGRectGetWidth(rect);  
    int y = ymin / CGRectGetHeight(rect);
	
    NSString *fileName = [NSString stringWithFormat:@"image-%02d-%02d.jpg", x + 1, y + 1];
    UIImage *image = [UIImage imageNamed:fileName];
    [image drawInRect:rect];
}


そのまま動作するサンプルプロジェクトをGitHubに置きました。
kishikawakatsumi's TiledLayerView at master - GitHub


だいたいのコードは下記になります。

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface TiledView : UIView <UIScrollViewDelegate> {
    CATiledLayer *tiledLayer;
}
@property (nonatomic, readonly) CATiledLayer *tiledLayer;
@end

@implementation TiledView

+ (Class)layerClass {  
    return [CATiledLayer class];  
}

- (CATiledLayer *)tiledLayer {  
    return (CATiledLayer *)self.layer;
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();  
    CGAffineTransform t = CGContextGetCTM(context);  
	
    int xmin = CGRectGetMinX(rect);  
    int ymin = CGRectGetMinY(rect);  
    int xmax = CGRectGetMaxX(rect);  
    int ymax = CGRectGetMaxY(rect);  
    int x = xmin / CGRectGetWidth(rect);  
    int y = ymin / CGRectGetHeight(rect);
	
    NSString *fileName = [NSString stringWithFormat:@"image-%02d-%02d.jpg", x + 1, y + 1];
    UIImage *image = [UIImage imageNamed:fileName];
    [image drawInRect:rect];
}

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
    [self setNeedsDisplay];  
    return NO;  
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {  
    return self;  
}

- (void)dealloc {
    [tiledLayer release];
    [super dealloc];
}

@end
#import <UIKit/UIKit.h>
#import "TiledView.h"

@interface TiledLayerViewController : UIViewController {
	UIScrollView *scrollView;
	TiledView *tiledView;
}
@end

@implementation TiledLayerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
	
    tiledView = [[TiledView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 2448.0f, 3264.0f)];
    tiledView.tiledLayer.tileSize = CGSizeMake(245.0f, 327.0f);
    tiledView.tiledLayer.levelsOfDetail = 1;
    tiledView.tiledLayer.levelsOfDetailBias = 0;

    scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];  
    scrollView.contentSize = tiledView.bounds.size;  
    scrollView.maximumZoomScale = 4.0f;  
    scrollView.minimumZoomScale = 0.5f;  
    scrollView.delegate = tiledView;
    [scrollView addSubview:tiledView];
	
    [self.view addSubview:scrollView];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)dealloc {
    [scrollView release];
    [tiledView release];
    [super dealloc];
}

@end