今回から、いよいよ「ペン」の話に入ります。Quartz2D(CoreGraphics)フレームワークを利用してビューに画像や図形を描画する方法について解説していきます。
前回までは、UIImageViewに用意されている機能を使いデバイスのスクリーンに画像を表示していました。今回はUIImageViewを使わず、一般的なUIViewに画像を表示してみます。この仕組みをマスターすれば、Quartz2DフレームワークのAPIを使い、ビュー上に自由に落書きすることが可能になります。そのために、まず最初にUIViewを継承した独自のビュークラスを用意します。
Xcodeのファイルメニューの「新規ファイル...」を選択し、表示されたダイアログからテンプレートとして「Cocoa Touch Class」と「Objective-C Class」を選び、真ん中近辺にある「Subclass of」ポップアップメニューで「UIView」を選択します。

「次へ」ボタンをクリックすると、今度はファイル名を入力するダイアログが表示されます。ここで入力するObjective-Cのソースファイル(.m)の名称が、UIViewを継承したクラスの名称となります。今回のクラス名は「SymmetryView」とします。ダイアログに表示されている他の設定については、特別変更する必要はありません。

最後に「完了」ボタンを押せば、自動的にクラス定義ファイル(SymmetryView.h)とObjective-Cのメソッド実装ファイル(SymmetryView.m)が作成されて、現在のプロジェクトウィンドウのファイル一覧に追加されます。

続いてnibファイル側の修正です。対象となるのは「ImageViewController.xib」です。前回までは、ビューコントローラで扱うビューいっぱいにUIImageViewを配置していました。これを取り外し(削除し)代わりにUIViewを配置します。背景は黒にしておきます。そして、インスペクターウィンドウ「View Identity」の一番上に表示されているClassを「UIView」から「SymmetryView」へと変更します(メニューで選択し直す)。

この時、先んじてSymmetryView.hファイルがプロジェクトに登録されていないと、クラス選択メニューには「SymmetryView」の表記は現れませんので注意してください。このビューをImageViewControllerで新規に用意した IBOutlet(次回詳細を解説)にリンクします。これでnibファイル側の準備は完了です。続いてSymmetryViewクラスのソースコードの編集を行います。まずはクラス定義(SymmetryView.h)ですが、プロパティへ表示対象となる画像(UIImage)を保存できるようにしておきます。
@interface SymmetryView : UIView
{
UIImage *sy_image; // 対称処理を実行する画像
}
@property(nonatomic,retain)UIImage *sy_image;
@end
Xcodeのテンプレートから作成されたメソッド実装用ファイル( SymmetryView.m)には、既に幾つかのメソッドが用意されています。この中で、 initWithFrame:メソッドはビューの初期化時に呼ばれますが、今回は利用しませんので削除してもかまいません。最後のdeallocメソッドではプロパティとして確保した画像(UIImage)を解放します。ここで重要なのは、もう一つのdrawRect:メソッドです。
#import "SymmetryView.h"
@implementation SymmetryView
@synthesize sy_image;
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[sy_image drawInRect:rect]; // 画像の描画
}
- (void)dealloc
{
self.sy_image=nil;
[super dealloc];
}
drawRect:メソッドは、このUIViewが表示されて何らかのアップデート(描き直し)が必要とされた時に呼ばれます。描き直しの対象となる矩形領域は引数として渡されるrect(CGRect)です。当然、一番最初にスクリーン上にビューが配置された場合には、必ずこのメソッドが呼ばれることになります。 つまり、このメソッドをオーバライドすることで、ビュー内に好みの描画が実現できるわけです。まずは簡単な例としてプロパティに保存されている画像を描画してみます。描画結果は、前回解説したUIViewContentModeのUIViewContentModeScaleToFillモードと同等です。

この仕組み、昔からMacでアプリケーションを開発している人ならば、ToolBox(古い話)の「Update Event」と同じだと考えれば理解しやすいと思います。ちなみに強制的に再描画(Update Eventを発生)を行いたい場合には、UIViewクラスに用意されている以下のメソッドを使います。両メソッドの違いは、アップデートの対象領域をビュー全体にするか、引数指定の矩形内にするかです。
- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)rect;
昔は、表示速度を上げるためにアップデート対象の矩形領域を詳細に設定していたのですが、今では高速なアニメーション処理でもしないかぎり、setNeedsDisplayの方を使えば大丈夫です。ちなみに、本当に高速なアニメーション処理を望むなら、今回のオーバライドの仕組みではなくOpenGL ESを使うことになるでしょう。
さて、上記ではUIImageのメソッドを使い画像を描画したのですが、UIImageクラスの描画機能は限定的で複雑な処理には向きません。そこで、Quartz2D APIの出番です。この2D描画専用フレームワークを使えば、2D描画処理に関する要求をほとんど満たすことができます。とりあえず前回の処理をQuartz2D APIを使い書き換えると以下の様になります。 Quartz2Dを利用した描画については、Apple社が提供している「QuartzDemo」というサンプルソースコードを参照されることをお勧めします。
- (void)drawRect:(CGRect)rect
{
CGContextRef context;
CGImageRef cgimage;
context=UIGraphicsGetCurrentContext();
cgimage=sy_image.CGImage; // CGImageRefを得る
CGContextDrawImage( context,rect,cgimage ); // 画像の描画
}
Quartz2D APIの引数には、現在の描画環境のリファレンスとしてCGContextRefを必ず渡す必要があります。drawRect:内では、これをUIGraphicsGetCurrentContext()ルーチンで得ます。このルーチンはUIGraphics.hに定義されています。また、Quartz2Dの画像リファレンスであるCGImageRefは、UIImageのプロパティ(CGImage)として参照することが可能です。さて、こちらの処理を試してみると、何故か画像が上下にひっくり返って描画されることが分かります。

これは、Quartz2Dの描画座標系が左下原点のためです(iPhone OSのビュー等は左上原点に変更されている)。このため、まず最初にQuartz2Dの座標系変換マトリックス設定用のAPIを使い、描座標系を上下反転させてやる必要がります。これでようやくUIImageクラスのdrawInRect:メソッドと同等の描画が行えるようになりました。
- (void)drawRect:(CGRect)rect
{
CGContextRef context;
CGImageRef cgimage;
context=UIGraphicsGetCurrentContext();
CGContextTranslateCTM( context,0.0,rect.size.height ); // 座標系上下反転
CGContextScaleCTM( context,1.0,-1.0 );
cgimage=sy_image.CGImage; // CGImageRefを得る
CGContextDrawImage( context,rect,cgimage ); // 画像の描画
}
次回は、今回追加したビューへの描画処理に伴う他のクラスの修正個所を解説します。また、画像を扱う場合のメモリ管理についての重要ポイントについても言及する予定です。