jarinosuke blog

about software engineering, mostly about iOS

UIView の Animation Block の中で行われている事

Animation Block

iOS 4 から UIView のアニメーションを簡単にするために、以下のアニメーションに関するクラスメソッドが UIView に追加されました。

iOS 開発に携わってる人ならみんな知ってると思います。

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations;

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;

上記以外にも iOS 7 からは以下のようなキーフレームや UI Dynamics を用いたアニメーションのためのメソッドも追加されています。

+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations;

+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

これらのメソッドのおかげで、とても簡潔に UIView をアニメーションさせることができるようになりました。

しかし、実際にあの Block の中ではどのような処理が走ってアニメーションが実行されているかが上手に隠蔽され、

あれだけで済んでしまったことによる Core Animation に対する認識不足が自分の中で目立ってきたので今回は調べてみました。

CAAnimation による明示的アニメーション

上記の UIView の簡易 API ができる以前はどのようにアニメーションを実装していたのか、

というそもそもの所に戻ってみるのが近道っぽいです。

以下のコードは Core Animation Programming Guide - Animating Layer Content の章にあるサンプルです。

CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];

fadeAnim.fromValue = [NSNumber numberWithFloat:1.0];

fadeAnim.toValue = [NSNumber numberWithFloat:0.0];

fadeAnim.duration = 1.0;

[theLayer addAnimation:fadeAnim forKey:@"opacity"];

 
// Change the actual data value in the layer to the final value.

theLayer.opacity = 0.0;

CAAnimation を作成し、それを CALayeraddAnimation:forKey: することでアニメーションを実行しています。

Animatable Property

少し脇道にそれますが、CALayer には animatable property というのがあります。以下をみるとわかりますが、ほとんどがそれです。

Core Animation Programming Guide - Animatable Properties

では animatable property とは何なのでしょうか?

animatable property とはその名の通りアニメーション可能な値のことで、

その値を変更すると actionForKey: が発火して、然るべき CAAction プロトコルに準拠したオブジェクトを返し、暗黙的アニメーションを実行するというプロパティです。

actionForKey: が CAAction プロトコルに準拠したオブジェクトを返す流れは reference を見るのがわかりやすいです。

特別な定義が無ければ、animatable property と一緒に定義された CAAnimation が返ります。

ということで、CAAnimation による明示的アニメーションと animatable property が行う暗黙的アニメーション

について分かったところで UIView の話に戻ります。

結論

上記でも書きましたが、スタンドアローンCALayer の animatable property が変更された場合は、CAAnimation が発行されて暗黙的アニメーションが実行されます。

しかし UIView が保持している CALayer に同じことをしても何も起こりません。ただ新しいフレームにアニメーション無しで移るだけです。

なぜかというと UIView が自身が保持している layer のアニメーション機能をオフにしてるからなんですね。

これは Core Animation Programming Guide - How to Animate Layer-Backed Views にも書かれています。

The UIView class disables layer animations by default but reenables them inside animation blocks.

理由としては Core Text などと同様、 iPhone OS リリース当時のパフォーマンスが芳しくなかったからとかだと想像します。

どうやってオフにしてるかというと、CALayerCALayerDelegate 経由で UIViewCAAnimation を問い合わせる時に行っています。

で、それを簡単に有効にするために登場したのが、冒頭に書いた UIView Animation Block というわけですね。

上記の事柄は、以下のコードをみると一目瞭然だと思います。

NSLog(@“Animation Block 外: %@",
      [view actionForLayer:view.layer forKey:@"position"]);

[UIView animateWithDuration:1.0 animations:^{
    NSLog(@“Animation Block 内: %@",
          [view actionForLayer:view.layer forKey:@"position"]);
}];

— 実行結果
Animation Block 外: <null>
Animation Block 内: <CABasicAnimation: 0x8c2ff10>

まとめると、

UIView Animation Block の中で animatable property を変更した場合のみ、CAAnimation が発行され暗黙的アニメーションが実行されている

ということでした。長々と失礼しました。

参考リンク

Multiple Animations

View Layer Synergy