Moving Layers

Animating UIViewのエントリーではUIViewにラップされたインターフェイスからCore Animationの機能、アニメーションをさせてみた。簡単なお約束ごとでアニメーションを実行できるのだけど、Core Animationのクラス、CALayerだとどうだろうか。

いきなり結論から言ってしまうと、CALayerを使うともっと簡単にアニメーションができてしまう。実はCALayerクラスのアニメーション可能となっているプロパティに値を設定すると、それは即時実行ではなく現在値からのアニメーションとして設定される。

つまり何も考えずにプロパティ値を設定するだけでアニメーションを実行できてしまうわけだ。これをCoreAnimationではImplicit Animationと呼んでいる。

たとえば、

theLayer.alpha = 0.0f;

とするだけで、レイヤーはalpha=0へと消えていく。

今回はImplicit Animationを使ってみようと思う。

Implicit Animation

アニメーションの長さはどうなっているのだろうか?Implicit Animationの場合デフォルトでは0.25秒になっている。この値は変更が可能だ。

[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:10.0f]
forKey:kCATransactionAnimationDuration];     // 1
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];

CATransactionのクラスメソッドのbeginとcommit挟まれた部分でsetValue:forKey:でキーkCATransactionAnimationDurationに対して10を設定している(1)。これでcommitされるまでに設定されるアニメーションの長さは10秒に設定される。

beginで始まりcommitで終わるこのフォームはUIViewのラッパーの類似性を思い出して欲しい。

key-value coding

ここでちょっと寄り道をしてみる。さっきのアニメーションの再生長を指定した時に使ったメソッド、setValue:forKey:はNSObjectで定義されているメソッドだ。つまり、どのクラスでも使えるということになる。

これはkey-value codingという機能の一環で、Objective-Cの動的言語な側面が垣間見える。

次のようなクラスを書いてみる。

@interface kvtest : NSObject {
int _number;
NSPoint _point;
NSString *_text;
}
- (void)setPoint:(NSPoint )point;
- (void)setText:(NSString *)text;
@end
@implementation kvtest
- (void)setPoint:(NSPoint )point
{
NSLog(@"setPoint");
_point = point;
}
- (void)setText:(NSString *)text
{
[_text release];
_text = [text copy];
}
@end
void test(void)
{
kvtest *obj = [[kvtest alloc] init];
[obj setValue:[NSValue valueWithPoint:NSMakePoint(100,200)]
forKey:@"point"];      // 2
[obj setValue:@"test" forKey:@"text"];  // 3
[obj setValue:[NSNumber numberWithInt:100]
forKey:@"number"];     // 4
}

2、3はなんとなくset+key名のメソッドがあるので、そのメソッドを検索してるのだろうなということはわかるのだけど、面白いのが4ではないだろうか。何もメソッドが用意されていないインスタンス変数に対してちゃんと値が設定されるのだ。

もちろん、どんなインスタンス変数でも探し出して値をセットしてくれるわけではない。どうやって、何を探すかというのには決まりがあるのだ。その決まり事はKey-Value Coding Programming GuideのAccessor Search Implementation Detailsの章に詳しく書いてある。

探索の順番は次の通りだ。

  1. set<Key>名のメソッド。
  2. もし1が存在しなく、accessInstanceVariablesDirectlyの戻り値がYESだった場合。
    _<key>、_is<Key>、<key>、is<Key>の順に。

  3. それも存在しない場合は、setValue:forUndefinedKey:が呼ばれる。

さらにすごいのは、setValue:のところでNSValueクラスのオブジェクトを渡しているのだけど、値をセットするときには元の型に自動的に戻してセットしてくれることだ。このあたりはObjective-Cのruntimeと深く関係してくるのだけど、そこまで行ってしまうと脱線から帰ってこれなくなるので今回は触れない。

Key-Value CodingはCocoaフレームワークの重要な機能の一つで、いろんなことがこの機能によって実現されている。Core Animationにも、また、深く関わっているのだ。

Non Animating Setup

Core Animationではプロパティに値を設定するだけでアニメーションが実行できるお手軽さなのだけど、さて、最初の位置に動かすなど、アニメーションさせないで即時反映させたい場合はどうすればいいのだろうか。

この方法はおきまりの「型」がある。

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];  // 5
theLayer.alpha = 1.0f;
[CATransaction commit];

5のように、beginのあとにCATransactionのkCATransactionDisableActionsにkCFBooleanTrueをセットしてやると、そこからcommitするまでの間のプロパティへの値のセットはアニメーション実行でなく、即時実行になる。

このプロパティ値の設定はcommitまで有効で、セットしたらその後ずっと有効というわけではない。また、その値をセットするだけのメソッドも用意されている。

[CATransaction setDisableActions:YES];  // 6

6のコードの効果は5と同じとなる。

Chained Animation

一つのアニメーションの再生が終わったときに次のアニメーションを続けて再生したい場合等の、終了時に処理を行える機能ももちろん用意されている。

それには、Objective-C 2.0の新機能の一つBlocksが使われている。以前はセレクタなどで実装されていたような機能も、新しく追加されるメソッドは積極的にBlocksを使うようになっているようだ。

Blocksとはなにかというと、Objective-C版のクロージャと言える無名関数を使うための機能だ。こんな感じで使うことが出来る。

void (^block)(void) = ^{ printf("hello,world!"); }
block();

ぱっと見た感じ、^というキーワードが目新しいぐらいで関数ポインタと変わらないように見えるのだけど、一つ大きな違いがある。実はBlockはオブジェクトなのだ。試しに次のようなコードを実行させてみると、__NSGlobalBlock__というクラス名ということがわかる。

NSLog(@"%@",[(^{ }) className]);

今は内部クラスであるBlockも、そのうちクラスとして使えるようになるのかもしれない。

Blocksはメッセージ式の引数として使われ、CATransactionでもアニメーション終了時に実行するBlockを登録することによって、終了時に処理を行うことが可能になる。

- (void)execAnimation
{
[CATransaction begin];
....
[CATransaction setValue:^{ [self execAnimation]; }
forKey:kCATransactionCompletionBlock];  // 7

7はアニメーションが完了したときに実行するBlockを設定する。Blockはオブジェクトなのでこんな感じにkey-value codingの引数として渡すことが出来る。この例では終了時に再度アニメーションの実行メソッド(execAnimation)が呼ばれるので、ずっと繰り返すアニメーションになる。

このkCATransactionCompletionBlockのキー値を設定するメソッドも用意されている。

[CATransaction setCompletionBlock:^{ [self execAnimation]; }];  // 8

8は7と同じ結果となる。

Summary and Sample Code

ざっと見てきたCALayer、およびCATransactionクラスの機能を使うと簡単なアニメーションを作成することが出来るだろう。

  • 継続処理が必要無いものはプロパティ値に設定してやるだけでとりあえず動いてしまう。
  • 即時実行や指定時間でアニメーションさせたいときはCATransactionのbeginとcommitの間で指定すれば良い。
  • 終了時処理を行いたい場合はCATransactionクラスにBlocksの機能を使って処理を登録する。

さて、実はCATransactionのアニメーションは入れ子構造にすることが出来る。例えば指定場所に移して(即時実行)から、その位置から別の位置に移動させるといったようなときに必要になるだろう。

実装としては入れ子ではないものと変わらないので、説明は省略しようと思うが、サンプルプログラムで簡単にその機能を使っている。

サンプルプログラムはフェードインフェードアウトしながら場所を4角に移動していくアニメーションだ。即時実行で位置を移動してopacity(透明度)をアニメーションさせている。詳しくはソースの方を参照して欲しい。

CABasicSampleのソースコードはここ(CABasicSample.zip)にある。