OSXのLeopardからCoreAnimationというフレームワークが追加された。それによってCoverFlowなんかを実現している。それまでは空間的な管理しか行わなかったGUIシステムにおいて、時間の管理もOSが面倒を見てくれるようになったというのが、CoreAnimationのキモなのではないかと思う。
そして、OSX系の流れをくむiPhoneOSにもCoreAnimationは含まれている。iPhoneOSはOSXの紆余曲折のまだ後の方に出たということもあり、構造的にはモバイルOSにしてはかなりモダンな、OSXの成功も失敗も踏まえた上で設計されていると感じることがある。
さて、iPhoneのCoreAnimationだけど、かなりお手軽に使う方法が用意されている。ダイアログをビヨヨンとだしたり、スライドしてカットインさせたり、はたまた二つのViewをクロスディゾルブさせたり。UIViewControllerのトランジション効果なんかにもプリセットのアニメーションパターンが用意されているのだけど、簡単なお約束でアニメーションさせることが出来るので使わない手はない。
今回デモ用にEscapeButtonというジョークソフトを軽く組んでみた(ほんとに数行しか書いてない)。名前からして出落ちな感もあるけど、実装を見つつご紹介しようと思う。
UIViewをアニメーションさせる
EscapeButtonのソースコードはここ(EscapeButton.zip)にある。
CoreAnimationではCALayerクラスおよび、その派生クラスを扱うのだけど、UIViewにはCoreAnimationを意識せずに使う方法が用意されている。基本のお約束の型はこんな感じ。
[UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.25]; // アニメーション時間 // ここにアニメーションさせたいUIViewのゴール時の状態を設定する [UIView commitAnimations];
とりあえず、これだけで指定のUIViewがアニメーションする。たとえばalphaのプロパティ値を0にすればフェードアウトするといった具合に。
ただし、どのプロパティでもアニメーションできるかというと残念ながらそれはできない。ジオメトリ的な事(位置や回転拡大など)、カラー的な事に限られてると考えておいた方が良いだろう(詳しい話はCoreAnimationを見る必要がある)。
さて、EscapeButtonはボタンが押されたらそのボタンが逃げる。いきなりワープするんじゃなくて、するするとアニメーションしつつ上と下を行ったり来たりする。その部分のソースはこんな感じになっている。
- (IBAction)buttonTouchedDown:(id)sender { [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.25]; CGRect frame = _button.frame; if (!_side) frame.origin.y = 360; else frame.origin.y = 120; _side = !_side; _button.frame = frame; [UIView commitAnimations]; }
ボタンと、このアクションはInterfaceBuilderにおいてUIControlEventTouchDownイベントで接続している。
やっていることは先ほどの基本の型そのままだ。そして、今いたところとは反対側に移動させる。そのためにボタンのframeを取得してY座標を書き換えてセットしている。これだけなんだけど、ボタンを押すとするすると逃げるという操作が実装できてしまう。
チェーンアニメーション
もう少し複雑なアニメーションの実装をしてみよう。今度はUIViewControllerのviewが表示されるときに回転しながら拡大していき、丁度の位置より行き過ぎて戻るというアニメーションだ。
まずは仕込みだ。初期の段階で縮めておき、さらに90度回転させておく。
- (void)viewWillAppear:(BOOL)animated { CGAffineTransform t = CGAffineTransformMakeScale(0.1, 0.1); self.view.transform = CGAffineTransformRotate(t, M_PI_2); }
UIViewにはtransformというプロパティがあり、そこに2次元変換行列を設定することが出来る。CGAffineTransform〜という関数が用意されていて移動(Translate)、拡大(Scale)、回転(Rotate)が設定できる。回転の角度の指定は度ではなくラジアンだ。まずMakeがついているものでCGAffineTransformを作成し、必要なだけ行列を掛けていく。行列の掛ける順番は意味があるので注意が必要だ(移動してから回転するのと、回転してから移動したのでは結果が異なる)。
そしてViewが表示されたのと同時にアニメーションを開始する。
- (void)viewDidAppear:(BOOL)animated { [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:1]; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(_didEndAnimation)]; CGAffineTransform t = CGAffineTransformMakeScale(1, 1); self.view.transform = CGAffineTransformRotate(t, -M_PI_2); [UIView commitAnimations]; }
大体は基本の型どおりで、アニメーション終了後に拡大率等倍(=1)、回転がさらに90度過ぎた位置になるようになっている。
基本と違うのはsetAnimationDelegate:とsetAnimationDidStopSelector:が追加されていることで、これは想像の通り、アニメーションが終了したときに指定したターゲットのメソッドが呼ばれるように設定している。
このように終了時にメソッドを呼び出すことによって、例えば禁止していた処理を許可したり、次のアニメーションの再生を開始させたりすることが出来る。今回はさらに0度の位置に戻すようにアニメーションをさせる。
- (void)_didEndAnimation { [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.25]; self.view.transform = CGAffineTransformMakeScale(1, 1); [UIView commitAnimations]; }
これは基本通り。単位行列にしている(変形無し)。
これで回転しながら拡大し、ちょっと行き過ぎて戻るというアニメーションが完成した。このView Controllerが表示されるときはこのアニメーションが表示される。プリセットのトランジションにはない効果が簡単に実装できる(今回は重ね合わせに関しては実装をサボっている)。
CoreAnimationを直接使うともっと複雑なことが出来るのだけど、UIViewのラップされたインターフェイスでも、結構動きのあるインターフェイスを実現することが出来る。なかなかお手軽なわりには効果は大きいんじゃないだろうか。