LoginSignup
86
78

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-01-29

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

0. アニメーションとは

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

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

1. NSTimerを使ったアニメーション

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

①移動

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

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)
        }
    }
}

②点滅

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
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
        }
    }
}

③拡大縮小

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

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が動いているといわれています。
今回は、移動・点滅・拡大縮小・回転・クロスフェード

①移動

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
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)
        }
    }

}

②点滅

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
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)を使って簡単に拡大の実装をしています。

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
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)を使って簡単に回転の実装をしています。

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
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)
        }
    }

}

⑤クロスフェード

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
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ブロックの中に連続でアニメーションを書くことも可能。

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
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より

①CABasicAnimation

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

  • UIViewのプロパティのlayerを動かす
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を操作
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 中級編
by @inamiyさん

4. キーフレームアニメーション

Q.キーフレームアニメーションとは?
キーフレームアニメーションとは、アニメーションのフレームごとに、要素の位置や状態を定義することで 複雑なアニメーションをシンプルに実現することできる機能です。
(引用元:TYRANO SCRIPT)

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

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

①キーフレームアニメーションの作成例

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 (Facebook社製ライブラリ・人気)
GLDTween (和製ライブラリ・オススメ)

86
78
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
86
78