[Storyboardを使わないiOSアプリ開発] Vol.05 – アニメーションを使ってUIViewを重ねよう(CAKeyframeAnimation + AHEasing編)

今回はUIViewAnimationとはまた異なるアニメーション体系 CAKeyframeAnimationについてご説明します。
そもそも “CA” とは “Core Animation” の略です。
iOSにはCore Audio, Core Videoといった 様々なメディアを司る Core が存在します。
今回扱う Core Animation は読んで字のごとく、iOSにおいてアニメーションを司っている親分になります。

CoreAnimationにおけるアニメーションさせる対象は Layer という概念です。
Layerは人間の目に映る部分、まさしく 表層 を指します。
UIButtonで言えば、ボタンとして見える表層部分です。

UIViewは基本的に1つのLayerを持っています。
特定のUIViewのLayerを指定したい場合は UIView.layer という構文になります。
UIButtonといったUI要素は基本的にUIViewを継承していますので、UIButtonのlayerを差す場合は UIButton.layer といった風に書くことができます。

さて、お話をCore Animationに戻します。
UIViewAnimationとCore Animationの違いは何か。

誤解を恐れずに言えば、「アニメーションに関して何でもできるようになる!」ということです。

UIViewって、実は結構大雑把なアニメーションしか指定できないのです。
最初はゆっくり、徐々に速くする といったアニメーション(イージングといいます)には対応できていません。
いわば、等速直線番長。

対して、Core Animationは キーフレーム といった概念を持っています。
キーフレームとは、アニメーション開始後 0.1秒後にはこの値を持っていて、0.2秒後にはこの値を持っている、というような時間軸と値の概念です。
ですので、0.1秒後にX = 20、0.2秒後にX=100 というキーフレームを持たせて上げれば 「最初ゆっくり、次の瞬間ドーンと進む」といったアニメーションを作れるわけです。

イメージはこんな感じ。

UIViewAnimationとCAKeyframeAnimationAnimation

UIViewAnimationとCAKeyframeAnimationAnimation

速度変化のあるアニメーションの滑らかさは、どれだけ多くのキーフレームが設定されているかにかかっています。
数学の授業で、「グラフの線は点の集合」という話を聞いた経験がある方も多いかと思います。
つまり、滑らかなアニメーション = ゴールまでの道のりが細かく設定されたアニメーション と言えるのです。

通常、CoreAnimationではキーフレームを自分でポチポチ設定して、Layerにキーフレームアニメーションを適用します。
つまり、オール手作業。
一般的には1秒間に60個のキーフレームを設定すると、ヌルヌル動くとても滑らかなアニメーションになります。
ですが、1秒間のアニメーションを作るために60個の数値を入力するのは かなりの苦行です。
そこで、便利なフレームワークを導入して手軽にヌルヌル動くCoreAnimationを作成したいと思います。

ご紹介するのは AHEasing というフレームワークです。(紹介ページ
AHEasingはプリセットのアニメーション名を指定するだけで、途中のキーフレームを自動生成してくれるという とても素敵なフレームワークです。

上記リンク先ページ右側の「Download ZIP」より、AHEasingフレームワークをダウンロードします。
スクリーンショット-2013-11-22-23.57.35

次に、ZIPファイルを解凍して、下記のフォルダを選択
スクリーンショット 2013-11-22 23.58.28

XCodeのプロジェクトにこのような配置になるようにドラッグ&ドロップで追加します
スクリーンショット 2013-11-22 23.56.57

その際に下のようなポップアップが立ち上がるかと思いますが、下記のような設定で Finish ボタンをクリックしてください。スクリーンショット 2013-11-22 23.58.44

これでAHEasingフレームワークの導入は完了です。
次はAHEasingを使って、ヌルヌル動くアニメーションを作成します。

下記、いつも通り コードを紹介します。
今回の変更は SecondaryView.m ファイルのみです。

SecondaryView.m

#import "SecondaryView.h"
////////////////前回からの変更点////////////////
#import "CAKeyframeAnimation+AHEasing.h"

@implementation SecondaryView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

        ////////////////前回からの変更点////////////////
        //自分(SecondaryView)を画面中央に配置する
        self.frame = self.bounds;

        ////////////////前回からの変更点////////////////
        //透明度を1.0(不透明)に設定する(下記alphaプロパティの設定を削除するでも◯)
        self.alpha = 1.0;

        self.backgroundColor = [UIColor colorWithRed:181/255.0f green:255/255.0f blue:235/255.0f alpha:0.2f];
        _hogeLabel= [[UILabel alloc] initWithFrame:CGRectMake(0, 100, self.frame.size.width, 40)];
        _hogeLabel.font = [UIFont systemFontOfSize:16.0];
        _hogeLabel.text = @"SecondaryView";
        _hogeLabel.textAlignment = NSTextAlignmentCenter;
        _hogeLabel.textColor = [UIColor colorWithRed:75/255.0f green:112/255.0f blue:10/255.0f alpha:1.0f];
        [self addSubview:_hogeLabel];

        _viewCloseButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _viewCloseButton.frame = CGRectMake(self.frame.size.width/2 - 50,150,100,30);
        [_viewCloseButton setTitle:@"close" forState:UIControlStateNormal];
        [_viewCloseButton addTarget:self action:@selector(viewClose:) forControlEvents:UIControlEventTouchDown];
        [self addSubview:_viewCloseButton];

        [self viewOver];

    }
    return self;
}

//重なるときのアニメーション
-(void) viewOver{
    NSLog(@"%@ called", NSStringFromSelector(_cmd));

    ////////////////前回からの変更点////////////////
    //出現時の移動アニメーション
    CAKeyframeAnimation *slideInAnimation;
    {
        CGSize offSize = self.bounds.size;
        //画面出現時の横移動の始点と終点を定める
        CGPoint fromPoint = CGPointMake(offSize.width, offSize.height/2);
        CGPoint toFrame = CGPointMake(offSize.width / 2.0, offSize.height/2);

        // "position" に対して "ExponentialEaseOut" で 始点から終点まで 秒間180コマのアニメーションを作成する
        slideInAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position" function:ExponentialEaseOut fromPoint:fromPoint toPoint:toFrame keyframeCount:180];

        //アニメーションは1秒間
        slideInAnimation.duration = 1.0;

        //アニメーションが完了しても、アニメーションの結果はそのままにする(この2つの設定がないとアニメーション完了時に巻き戻されてしまう)
        slideInAnimation.removedOnCompletion = NO;
        slideInAnimation.fillMode = kCAFillModeForwards;

        //アニメーションについてのイベント呼び出し先を自分に設定する
        slideInAnimation.delegate = self;

    }
    [self.layer addAnimation:slideInAnimation forKey:@"SlideInAnimation"];

    ////////////////前回からの変更点////////////////
    //出現時の透明度アニメーション
    CAKeyframeAnimation *opacityAnimation;
    {
        // "opacity" に対してアニメーションを作成する
        opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
        //アニメーションは0.5秒
        opacityAnimation.duration = 0.5f;

        //アニメーションが完了しても、アニメーションの結果はそのままにする(この2つの設定がないとアニメーション完了時に巻き戻されてしまう)
        opacityAnimation.removedOnCompletion = NO;
        opacityAnimation.fillMode = kCAFillModeForwards;

        //アニメーションによって、opacityの値を0.0から1.0に変更する
        opacityAnimation.values = [[NSArray alloc] initWithObjects:
                                 [NSNumber numberWithFloat:0.0f],
                                 [NSNumber numberWithFloat:1.0f],
                                 nil];
    }
    [self.layer addAnimation:opacityAnimation forKey:@"OpacityAnimation"];
}

//viewCloseButtonが押されて呼び出されるメソッド
-(void) viewClose:(UIButton*)button{

    //ログとしてメソッド名を書き出す
    NSLog(@"%@ called", NSStringFromSelector(_cmd));

    ////////////////前回からの変更点////////////////
    //削除時の移動アニメーション
    CAKeyframeAnimation *slideOutAnimation;
    {
        CGSize offSize = self.bounds.size;
        CGPoint fromPoint = CGPointMake(offSize.width / 2.0, offSize.height/2);
        CGPoint toFrame = CGPointMake(offSize.width*1.5, offSize.height/2);

        slideOutAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position" function:ExponentialEaseOut fromPoint:fromPoint toPoint:toFrame keyframeCount:180];
        slideOutAnimation.duration = 0.8;
        slideOutAnimation.removedOnCompletion = NO;
        slideOutAnimation.fillMode = kCAFillModeForwards;
        slideOutAnimation.delegate = self;

    }
    [self.layer addAnimation:slideOutAnimation forKey:@"SlideOutAnimation"];

    ////////////////前回からの変更点////////////////
    //削除時の透明度アニメーション
    CAKeyframeAnimation *opacityAnimation;
    {

        opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
        opacityAnimation.duration = 0.35f;
        opacityAnimation.removedOnCompletion = NO;
        opacityAnimation.fillMode = kCAFillModeForwards;
        opacityAnimation.values = [[NSArray alloc] initWithObjects:
                                 [NSNumber numberWithFloat:1.0f],
                                 [NSNumber numberWithFloat:0.0f],
                                 nil];
    }
    [self.layer addAnimation:opacityAnimation forKey:@"OpacityAnimation"];
}

////////////////前回からの変更点////////////////
//アニメーションが完了した時に呼び出される
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    //終了したアニメーションが "SlideOutAnimation" だったとき
    if (anim == [self.layer animationForKey:@"SlideOutAnimation"]) {
        //SecondaryViewを削除する
        [self removeFromSuperview];
    }
}

@end

さてさて、これでヌルヌル イージングのかかったアニメーションの完成です。
今回使ったアニメーションプリセットは “ExponentialEaseOut”。

擬音で言えば、「ギュンッ!! → スーッ…」。

ExponentialEaseOut

ExponentialEaseOut Animation

…分かりづらいので映像にしました。

どうでしょう?なんだかソレっぽくないですか?

AHEasingには他にも様々なイージングのプリセットが用意されています。
使い方は easing.h ファイルに定義されているイージング名を ExponentialEaseOut の代わりに指定してあげるだけです。
どのようなイージングかを大体でもいいので知りたい場合は このようなサイトを使ってみるのも手ですよ〜。

それでは今回はこの辺で!


[Storyboardを使わないiOSアプリ開発] Vol.04 – アニメーションを使ってUIViewを重ねよう(UIViewAnimation 編)

前回までで、ボタンをタップすることで異なるUIViewを表示することができました。今回からは、いよいよアニメーションを使用してUIの操作感をデザインしていきます。

アニメーションの方式は多々ありますので、方式ごとに説明してきたいと思います。
今回は基本中の基本、UIViewAnimationを使用したアニメーションを説明します。

作成するアニメーションは、ボタンを押した時に横からUIViewが透明度を変えながらスーッと出てくる、というものです。ありがちですね。
続きを読む


[Storyboardを使わないiOSアプリ開発] Vol.03 – ボタンで他のUIViewを表示してみよう

前回はRootViewControllerに登録したViewControllerにラベルとツールバーを表示しました。

今回のイメージはこんな感じ。

ツールバーにボタンを追加して、ボタンをタップすることで他のUIViewを表示・非表示する機能を持たせてみます。
重なりで考えれば、CatchCopyViewの上に新しいUIViewを重ねるイメージです。
また、呼び出される側のUIViewにも自身を画面から削除するためのボタンを用意してあげます。

続きを読む