Invitation to CoreAnimation

Animating UIViewのエントリーでUIViewにラッパーされたCoreAnimationを使ってみたのだけど、今回はもう一歩CoreAnimationの世界に踏み込んでみる。とはいっても、アニメーションさせようっていうわけでなく、CoreAnimationで用意されている機能を使ってちょっとおいしいところをつまんでみようかということなのだ。

CoreAnimationはOSXではLeopardで追加された機能の一つなのだけど、iPhoneOS(おっとiOS)では最初から使える機能だ。ただ、CocoaとCocoa touchの類似性を保つためなのか、そのViewとの関係性は同じように実装されている(何となく後付け的な感じがうかがえる)。

UIViewに対応してそのCoreAnimationレイヤーであるのがCALayerクラスだ。layerプロパティがUIViewに用意されている。ちょっとわかりにくいのが、UIViewとしてもヒエラルキーを組めるのだけど、それとは別にCALayer側でも親子構造が組めるということだ。

今回扱うのはこのCALayerクラスだ。いくつかプロパティを持っているのだけど、注目するのは形状に関するプロパティだ。

iOSやOSXのUIには角丸の四角形がよく出てくるのだけど、CALayerにはcornerRadiusというプロパティがある。そのものずばり、角の丸みの半径を指定するパラメータだ。これを使うと簡単に角丸の四角形を作ることができる。

UIViewをインターフェイスビルダーで作成しておき、それをUIViewControllerのプロパティ(この場合layerTestView)と接続しておく。CoreAnimationを使用するにはQuartzCoreフレームワークもプロジェクトに足しておく必要がある。

- (void)viewDidLoad {
[super viewDidLoad];
CALayer *layer = _layerTestView.layer;
CGFloat bg_rgba[] = { 1.0, 0.0, 0.0, 0.7 };
CGFloat border_rgba[] = { 0.0, 0.0, 0.0, 1.0 };
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef bgColor = CGColorCreate(colorSpace, bg_rgba);
layer.backgroundColor = bgColor;
CGColorRelease(bgColor);
CGColorRef borderColor = CGColorCreate(colorSpace, border_rgba);
layer.borderColor = borderColor;
layer.borderWidth = 2;
CGColorRelease(borderColor);
CGColorSpaceRelease(colorSpace);
layer.cornerRadius = 10;
}

やっていることは、まずUIViewよりlayerを取得している。そして、そのlayerに対してbackgroundColorとborderColorに色をセットしている。コードが長いのはCoreGraphicsのAPIを使用しているためだけど、ここはUIColorのCGColorメソッド使っても良いだろう。そしてborderWidthでボーダーの幅をセットし、最後にcornerRadiusをセットしている。このコードではこんな風に描画される。

さらに、コードを追加してみる。

layer.cornerRadius = 10;
layer.shadowOpacity = 0.8;
layer.shadowOffset = CGSizeMake(5, 5);
}

プロパティ名からも分かるように、影を描画する機能がCALayerには用意されているのだ。影を描画するために最低設定しないといけないプロパティはshadowOpacityだ。そして、こんな風に描画される。

これだけでも結構使える機能なんじゃないかと思う。しかし、まだまだこんなものではない。backgroundColorをセットしていたコードをこんな風に変更してみる。

/*	CGColorRef bgColor = CGColorCreate(colorSpace, bg_rgba);
layer.backgroundColor = bgColor;
CGColorRelease(bgColor); */
UIImage *image = [UIImage imageNamed:@"test.png"];
layer.contents = (id)image.CGImage;

CALayerにはcontentsというプロパティが用意されていて、そこにはCGImageRefをセットすることが出来るのだ(もともとのUIViewがUIImageViewではないことを思い出して欲しい)。そして、こんな風に描画される。

惜しいのだけど、角丸の部分がクリップされてない。もし角丸である必要がない場合はこんなコードでイメージを影付で表示できる。さて、せっかくなのでちゃんと表示できるようにしたい。

CALayerにはmasksToBoundsというプロパティがある。これは文字通り境界で描画内容をマスクするというプロパティだ。これを設定してみよう。

layer.masksToBounds = YES;

そして、描画結果はこのようになる。

これもまた惜しいのだけど、影が消えてしまっている。影の描画範囲も境界(角丸の四角部分)の外なのでクリップされてしまっているのだ。影無しで良いならこのコードで良いのだけど、もう一工夫する必要がある。

一つのlayerだけではこれ以上は無理そうだ。二つレイヤーを使えば、影付の土台に角丸領域でマスクされたイメージを重ねれば期待する描画結果が得られるんじゃないだろうか。CALayerはUIViewとは別に階層構造を持つことが出来る。今扱っているlayerにイメージ用のサブレイヤーを足してみよう。

CALayer *subLayer = [CALayer layer];
subLayer.cornerRadius = 10;
subLayer.frame = CGRectMake(0, 0, 120, 120);
UIImage *image = [UIImage imageNamed:@"test.png"];
subLayer.contents = (id)image.CGImage;
subLayer.masksToBounds = YES;
[layer addSublayer:subLayer];

新しくlayerを作成しそれに今までやってきたようにプロパティを設定する。さて、うまくいくだろうか。描画結果はこのようになる。

角丸の四角領域でマスクしたイメージを影付で描画するのがたったこれだけのプロパティをセットするだけ(+α)で出来るというのはなかなか使える機能じゃないだろうか。ボーダーなしだと丁度ホーム画面のアイコンの描画のようになる。そういう目で見ていくとあちこちで活用されていることが分かる。

さて、今回のコードをまとめるとこんな感じだ。

- (void)viewDidLoad {
[super viewDidLoad];
CALayer *layer = _layerTestView.layer;
CGFloat border_rgba[] = { 0.0, 0.0, 0.0, 1.0 };
CGColorRef borderColor = CGColorCreate(colorSpace, border_rgba);
layer.borderColor = borderColor;
layer.borderWidth = 2;
CGColorRelease(borderColor);
CGColorSpaceRelease(colorSpace);
layer.cornerRadius = 10;
layer.shadowOpacity = 0.8;
layer.shadowOffset = CGSizeMake(5, 5);
CALayer *subLayer = [CALayer layer];
subLayer.cornerRadius = 10;
subLayer.frame = CGRectMake(0, 0, 120, 120);
UIImage *image = [UIImage imageNamed:@"test.png"];
subLayer.contents = (id)image.CGImage;
subLayer.masksToBounds = YES;
[layer addSublayer:subLayer];
}

プロジェクトはここからダウンロードできる。