Core Animation のはまりどころ

Nov 21, 2010

Core Animation でつまずいた点。

その1:暗黙的なトランザクション

Core Animation には暗黙的なトランザクションと呼ばれる特徴があり、特に何も指定しなくてもアニメーションが実行されてとても便利です。

theLayer.opacity = 0; // これだけで勝手にフェードアウトのアニメーションになる

便利は便利なんですが、アニメーションさせたくない場合もあって、そういった時は CATransaction を用いて暗黙的なアニメーションを無効にします。

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];
// このブロックの中でプロパティを書き換えるとアニメーションは起こらない
[CATransaction commit];

基本的にはこれでいいのですが、以下のような例ではパフォーマンスに問題があります。

// CALayer継承クラス
@interface ParticleLayer : CALayer {
...
-(void) move {
  [CATransaction begin];
  [CATransaction setValue:(id)kCFBooleanTrue
                   forKey:kCATransactionDisableActions];
  self.position = CGPointMake(x, y);
  [CATransaction commit];
}

このクラスは CALayer を継承していて、move メソッドを呼ぶと動く、という作りになっています。そしてこのクラスのインスタンスは、パーティクルとして大量生産されます。
このインスタンスたちをいっぺんに動かすとかなり重いです。
これは、1回の CATransaction ごとに描画プロセスが走るから(たぶん)です。CATransaction を使う場合は、一括で行うべきです。

// パーティクルレイヤーを内包するビュークラス
@interface ParticleContainView : UIView {
...
-(void) move {
  [CATransaction begin];
  [CATransaction setValue:(id)kCFBooleanTrue
                   forKey:kCATransactionDisableActions];
  for (int i=0; i<[particles count]; i++) {
    ParticleLayer *layer = (ParticleLayer *)[particles objectAtIndex:i];
    [layer move];
  }
  [CATransaction commit];
}

この例では、パーティクルを内包するビュークラスで一括にトランザクションを行っています。

ちなみに Apple のドキュメントではこう説明されています。

レイヤへの変更はすべて、トランザクションの一部として行われます。CATransactionは、複数のレイヤツリーへの変更を描画ツリーへのアトミックな更新として一括処理する役割を果たすCore Animationクラスです。

ぼくの読解力の問題かもしれませんが、一見何のこっちゃわかりません...
が、ようはそういう事なのでしょう。

その2:アニメーションが終了すると元に戻ってしまう

CALayer はプロパティの変更だけでお手軽にアニメーションを実現しますが、複雑なアニメーションには CAAnimation およびその継承クラスを使う事になります。ところが CAAnimation を使って、例えば透明度を0にするアニメーションを設定して実行し、アニメーションが終わると元の透明度に戻ってしまいます。

CABasicAnimation *opacityAnime = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnime.duration = 1;
opacityAnime.fromValue = [NSNumber numberWithFloat:1];
opacityAnime.toValue = [NSNumber numberWithFloat:0];
[aLayer addAnimation:opacityAnime forKey:@"animeFadeOut"];

ググったりドキュメントを見ても、なかなかこの現象への言及が見つからなかったのですが、結論としては、removedOnCompletionfillModeを設定すれば意図した通りに動きます。

CABasicAnimation *opacityAnime = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnime.duration = 1;
opacityAnime.fromValue = [NSNumber numberWithFloat:1];
opacityAnime.toValue = [NSNumber numberWithFloat:0];
opacityAnime.removedOnCompletion = NO;
opacityAnime.fillMode = kCAFillModeForwards;
[aLayer addAnimation:opacityAnime forKey:@"animeFadeOut"];

Comments