iOSのアニメーションあれこれ(Objective-C/Swift(※随時更新中))

iOSで使うアニメーションあれこれをまとめてみました。アニメーションはクオリティの高いアプリ作成をしたいときにとても重要なものになります。この記事では、小さめのサンプルとともに、基本的なアニメーションの実装方法について記述していきます。

# 0. アニメーションとは
Wikipediaによると...
*アニメーション（英語: animation）は、動画（どうが）とも呼ばれ、コマ撮りなどによって、複数の静止画像により動きを作る技術。**連続して変化する絵や物により発生する仮現運動**を利用した映像手法である。*

連続して変化する絵や物により発生する仮現運動を作る方法をさらっと見ていきましょう。

# 1. NSTimerを使ったアニメーション
時間ごとに状態を更新することによってアニメーションを行います。
移動・点滅・拡大縮小を行ってみましょう。

## ①移動

```objc:ViewController.m
@implementation ViewController {
    NSTimer *timer;
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    if (!timer) {
        timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(update:) userInfo:nil repeats:YES];
        [timer fire];
    }
    
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)update:(NSTimer *)timer {
    sampleView.center = CGPointMake(sampleView.center.x + 1, sampleView.center.y);
    if (sampleView.center.x > self.view.bounds.size.width) {
        sampleView.center = CGPointMake(0, sampleView.center.y);
    }
}


@end

```

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var timer: NSTimer!
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        if timer == nil {
            timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update:"), userInfo: nil, repeats: true)
            timer.fire()
        }
        
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    private func update(timer: NSTimer) {
        sampleView.center = CGPointMake(sampleView.center.x + 1, sampleView.center.y)
        if (sampleView.center.x > self.view.bounds.size.width) {
            sampleView.center = CGPointMake(0, sampleView.center.y)
        }
    }
}
```

## ②点滅

```objc:ViewController.m
@implementation ViewController {
    NSTimer *timer;
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    if (!timer) {
        timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(update:) userInfo:nil repeats:YES];
        [timer fire];
    }
    
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)update:(NSTimer *)timer {
    sampleView.alpha = sampleView.alpha - 0.01;
    if (sampleView.alpha < 0) {
        sampleView.alpha = 1.0;
    }
}

@end
```


```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var timer: NSTimer!
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        if timer == nil {
            timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update:"), userInfo: nil, repeats: true)
            timer.fire()
        }
        
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    private func update(timer: NSTimer) {
        sampleView.alpha = sampleView.alpha - 0.01
        if (sampleView.alpha < 0) {
            sampleView.alpha = 1.0
        }
    }
}
```


## ③拡大縮小

```objc:ViewController.m
@implementation ViewController {
    NSTimer *timer;
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    if (!timer) {
        timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(update:) userInfo:nil repeats:YES];
        [timer fire];
    }
    
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    [self.view addSubview:sampleView];
}

- (void)update:(NSTimer *)timer {
    sampleView.frame = CGRectMake(0, 0, sampleView.bounds.size.width + 1, sampleView.bounds.size.height + 1);
    if (sampleView.bounds.size.width > self.view.bounds.size.width) {
        sampleView.frame = CGRectMake(0, 0, 100, 100);
    }
}

@end

```

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var timer: NSTimer!
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        if timer == nil {
            timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update:"), userInfo: nil, repeats: true)
            timer.fire()
        }
        
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    private func update(timer: NSTimer) {
        sampleView.frame = CGRectMake(0, 0, sampleView.bounds.size.width + 1, sampleView.bounds.size.height + 1)
        if (sampleView.bounds.size.width > self.view.bounds.size.width) {
            sampleView.frame = CGRectMake(0, 0, 100, 100)
        }
    }
}

```

## ④NSTimerによるアニメーション総括
- 座標判定などはしやすいので「壁に当たったら跳ね返る」みたいな実装はやりやすい。
- カクカクで描画がしんどそう。

# 2. UIKitのUIViewクラスの基本アニメーション

UIViewクラスが持っているアニメーション機能を使ってアニメーションさせます。以下の書き方は、**アニメーションをかなり直感的に実装できる**書き方です。実は内部ではCoreAnimationが動いているといわれています。
今回は、移動・点滅・拡大縮小・回転・クロスフェード


## ①移動

```objc:ViewController.m
@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    [UIView animateWithDuration:1.0f
                     animations:^{
                         sampleView.center = CGPointMake(200, 200);
                     }];
}

@end
```

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewDidAppear(animated: Bool) {
        UIView.animateWithDuration(1.0) { () -> Void in
            self.sampleView.center = CGPointMake(200, 200)
        }
    }
    
}
```

## ②点滅
```objc:ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    [UIView animateWithDuration:1.0f
                     animations:^{
                         sampleView.alpha = 0.0;
                     }];
}

@end
```

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewDidAppear(animated: Bool) {
        UIView.animateWithDuration(1.0) { () -> Void in
            self.sampleView.alpha = 0.0
        }
    }
    
}
```


## ③拡大縮小
※アフィン変換(CGAffineTransform)を使って簡単に拡大の実装をしています。

```objc:ViewController.m

@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    [UIView animateWithDuration:1.0f
                     animations:^{
                         sampleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
                     }];
}

@end
```

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewDidAppear(animated: Bool) {
        UIView.animateWithDuration(1.0) { () -> Void in
            self.sampleView.transform = CGAffineTransformMakeScale(1.5, 1.5)
        }
    }
    
}
```

## ④回転
※アフィン変換(CGAffineTransform)を使って簡単に回転の実装をしています。

```objc:ViewController.m
@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    [UIView animateWithDuration:1.0f
                     animations:^{
                         sampleView.transform = CGAffineTransformMakeRotation(M_PI * 30.0 / 180.0);
                     }];
}

@end
```

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewDidAppear(animated: Bool) {
        UIView.animateWithDuration(1.0) { () -> Void in
            self.sampleView.transform = CGAffineTransformMakeScale(1.5, 1.5)
        }
    }
    
}
```

## ⑤クロスフェード
```objc:ViewController.m
@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    [UIView animateWithDuration:1.0f
                     animations:^{
                         sampleView.backgroundColor = [UIColor blueColor];
                     }];
}

@end
```

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var sampleView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewDidAppear(animated: Bool) {
        UIView.animateWithDuration(1.0) { () -> Void in
            self.sampleView.backgroundColor = UIColor.blueColor()
        }
    }
    
}
```

## ⑥連続アニメーション
completionHandlerも付いているので、finishedブロックの中に連続でアニメーションを書くことも可能。

```objc:ViewController.m
@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    [UIView animateWithDuration:1.0f
                     animations:^{
                         sampleView.alpha = 0.0;
                     }completion:^(BOOL finished) {
                         sampleView.alpha = 1.0;
                         [UIView animateWithDuration:1.0f
                                          animations:^{
                                              sampleView.backgroundColor = [UIColor yellowColor];
                                          }];
                     }];
}

@end
```


```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
    
    var sampleView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        sampleView = UIView(frame: CGRectMake(0, 0, 100, 100))
        sampleView.backgroundColor = UIColor.redColor()
        sampleView.center = self.view.center;
        self.view.addSubview(sampleView)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    override func viewDidAppear(animated: Bool) {
        UIView.animateWithDuration(1.0, animations: { () -> Void in
            self.sampleView.alpha = 0.0
            }) { (finished: Bool) -> Void in
                self.sampleView.alpha = 1.0
                UIView.animateWithDuration(1.0, animations: { () -> Void in
                    self.sampleView.backgroundColor = UIColor.yellowColor()
                })
        }
    }
    
}
```


## ⑦オプションの追加
オプションを追加すると、細かいアニメーション設定ができます。
// TODO:

## ⑧UIViewによる基本アニメーション総括
- とても直感的で便利
- 連続アニメーションさせる場合は気をつけないとブロックが入れ子構造になりがち

# 3. CoreAnimationを使ったアニメーション
*"CoreAnimationは、グラフィック描画、投影、アニメーション化のためのObjective-Cクラスのコレクションです。Core Animationは、Application KitとCocoa Touchのビューアーキテクチャを使用するデベロッパにとっては親しみのある階層レイヤの抽象化を維持しながら、高度な合成効果を用いた流体アニメーションを可能にします。"*
引用元: [Apple Developer/ Core Animation](https://developer.apple.com/jp/documentation/CoreAnimation_guide.pdf#page=13)より

## ①CABasicAnimation
CABasicAnimationを使ったアニメーションには2つの観点があります。

- UIViewのプロパティのlayerを動かす

```objc:ViewController.m
@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    
    CGPoint finishPoint = CGPointMake(250, 250);
    sampleView.layer.position = finishPoint;
    
    animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(250, 250)];
    animation.duration = 1.5;
    animation.repeatCount = 1;
    
    [sampleView.layer addAnimation:animation forKey:@"move"];
}

@end
```

- CALayerのインスタンスを新しく作り、viewに貼り付けてそのlayerを操作

```objc:ViewController.m
@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    CALayer *caLayer = [CALayer layer];
    caLayer.frame = CGRectMake(0, 0, 100, 100);
    caLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:caLayer];
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    
    CGPoint finishPoint = CGPointMake(100, 100);
    caLayer.position = finishPoint;
    
    animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(250, 300)];
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
    animation.duration = 1.5;
    animation.repeatCount = 1;
    
    [caLayer addAnimation:animation forKey:@"move"];
}

@end
```

## ②CATransaction
// TODO:

## ③CAAnimationGroup
// TODO;

## ④CoreAnimation総括
- CoreAnimationは非同期処理。
- CoreAnimationは「レイヤー(CALayer)を動かしている」という感覚。
- CALayerはUIViewのプロパティ。
- UIViewよりも描画がやや速い。

★より高度なCAAnimationの実装を知りたい人は下記リンクより中級へ移動
[Core Animation 中級編](http://qiita.com/inamiy/items/bdc0eb403852178c4ea7)
by @inamiyさん

# 4. キーフレームアニメーション
**Q.キーフレームアニメーションとは？**
*キーフレームアニメーションとは、アニメーションのフレームごとに、要素の位置や状態を定義することで 複雑なアニメーションをシンプルに実現することできる機能です。*
(引用元:[TYRANO SCRIPT](http://tyrano.jp/adv/next/keyframe))

**iOS7以降**、複雑なキーフレームアニメーションを作るための手段として以下の2つの処理が追加されました。とくに2つめの"addKeyframeWithRelativeStartTime:"メソッドは単独では使わず、"animateKeyframesWithDuration:"メソッド内に組み込む形で利用します。

- animateKeyframesWithDuration: delay: options: animations: completion:
- addKeyframeWithRelativeStartTime: relativeDuration: animations:

## ①キーフレームアニメーションの作成例
```objc:ViewController.m
@implementation ViewController {
    UIView *sampleView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    sampleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    sampleView.backgroundColor = [UIColor redColor];
    sampleView.center = self.view.center;
    [self.view addSubview:sampleView];
}

- (void)viewDidAppear:(BOOL)animated {
    [UIView animateKeyframesWithDuration:1.0
                                   delay:0.0
                                 options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
                              animations:^{
                                  [UIView addKeyframeWithRelativeStartTime:0.0
                                                          relativeDuration:0.1
                                                                animations:^{
                                                                    sampleView.backgroundColor = [UIColor yellowColor];
                                                                }];
                                  
                                  [UIView addKeyframeWithRelativeStartTime:0.1
                                                          relativeDuration:0.3
                                                                animations:^{
                                                                    sampleView.backgroundColor = [UIColor blueColor];
                                                                }];
                              } completion:nil];
}

@end
```

## ②キーフレームアニメーション総括
・複雑で繊細な連続アニメーションを作成したい場合は検討の余地あり。
・ViewController内に書くと肥大化しそうなのでうまくクラスを分けるなどで対応したい。

# 5. オープンソースライブラリの活用
よく使われるアニメーションや、複雑なアニメーションを簡単に実装することができるオープンソースライブラリも多数公開されています。以下の2つはとくにオススメなので是非使ってみてください。

・[POP](https://github.com/facebook/pop) (Facebook社製ライブラリ・人気)
・[GLDTween](https://github.com/theguildjp/GLDTween) (和製ライブラリ・オススメ)