Help us understand the problem. What is going on with this article?

[Objective-C] フリップアニメーションでビューを切り替える

More than 5 years have passed since last update.

色々方法があるみたいなので、それをまとめておきます。
まずは一番シンプル?な方法。

UIView#transitionFromView:toView:duration:options:completion:を使う

横にフリップして表示を切り替えるパターンの場合は、上記メソッドを実行するだけで簡単に制御できます。

UIView *view1 = [[UIView alloc] initWithFrame:self.view.bounds];
view1.backgroundColor = [UIColor redColor];

UIView *view2 = [[UIView alloc] initWithFrame:self.view.bounds];
view2.backgroundColor = [UIColor blueColor];

[self.view addSubview:view1];
[self.view addSubview:view2];

// 追加と同時に行うとアニメーションしないので、サンプルでは処理をちょっとだけ遅延させています
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_MSEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    [UIView transitionFromView:view2 toView:view1 duration:5 options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
        NSLog(@"Animation end.");
    }]
});

options

オプションに指定できるのは以下の項目が指定できるよう。

オプション名 効果
UIViewAnimationOptionTransitionNone アニメーションなし
UIViewAnimationOptionTransitionFlipFromLeft フリップが左から右へ
UIViewAnimationOptionTransitionFlipFromRight フリップが右から左へ
UIViewAnimationOptionTransitionCurlUp 本のページがめくれるように上に
UIViewAnimationOptionTransitionCurlDown 本のページがめくれるように下に
UIViewAnimationOptionTransitionCrossDissolve ディゾルブ(クロスフェード)
UIViewAnimationOptionTransitionFlipFromTop フリップが上から下に
UIViewAnimationOptionTransitionFlipFromBottom フリップが下から上に

CATranform3Dを使ってアニメーションを自作する

CATransform3Dを使うことでフリップなどの3Dの演出を自作することが出来ます。
簡単なアニメーションだけであれば、サンプルを変更するだけで比較的それらしいアニメーションが作れると思います。

今回のサンプルは、Githubにサンプルを上げているのでよかったら見てみてください。

とはいえ3Dについての知識があったほうが、なにがどんな処理をしているかが分かるので簡単にでも学んでおくといいと思います。
3Dに関しては以前、自作の3Dエンジンを作ってみる際にまとめた記事があるので、よかったら見てみてください。(専門家ではないのであまり分かりやすくはまとめられてませんが・・)

3Dエフェクトを作る

3Dのアニメーションを作るには、view.layer.sublayerTransformにパースペクティブを設定する必要があります。
これを設定しないと3Dが有効にならず、2Dによる動きになってしまうので注意が必要です。

※ちなみにパースペクティブとは遠近感を表す用語です。数値を色々変更すると遠近感のかかり方が変わります。

パースペクティブを設定する

// パースペクティブを設定
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = 1.0 / 500;
self.view.layer.sublayerTransform = perspective;

ここで行っていることは、CATransform3D型の変数に初期値を設定します。
(Identityは3Dマトリクスの単位行列で、数字で言えば1と同じ、演算しても結果が変わらない値です)
そして、m34要素にパースペクティブの値を設定します。

その値をCALayerのプロパティであるsublayerTransformに設定しています。
これは、サブレイヤーのレンダリング時に適用されるtransformの値で、3D的には親の座標空間の設定、ということになります。

背面の描画をオフに

3Dの世界ではカリングという概念があります。
3Dはただでさえ重い処理なので、少しでも軽くする必要があります。
そこで、いわゆるポリゴンの裏は描画する必要がない部分です。背面カリングを有効にすることで、この背面をレンダリングしない、ということが可能になります。

これに相当する設定は以下のようにすることで、背面(つまり変形した結果が裏)の場合はレンダリングされなくなります。

view.layer.doubleSided = NO;

アニメーションを実装する

以上の準備を踏まえて、実際のアニメーションのサンプルコードを載せておきます。

ビューの生成
@property (strong, nonatomic) UIView *frontView;
@property (strong, nonatomic) UIView *backView;
/**
 *  Viewのセットアップ
 */
- (void)setup
{
    CGRect viewFrame = CGRectMake(0, 100, self.view.bounds.size.width, 44);
    CGRect btnFrame  = CGRectMake(0, 0, viewFrame.size.width, viewFrame.size.height);

    // フリップするふたつのビューを生成
    self.frontView = [[UIView alloc] initWithFrame:viewFrame];
    self.frontView.backgroundColor   = [UIColor redColor];
    self.frontView.layer.doubleSided = NO; // 背面をレンダリングしない

    self.backView = [[UIView alloc] initWithFrame:viewFrame];
    self.backView.backgroundColor   = [UIColor blueColor];
    self.backView.layer.doubleSided = NO; // 背面をレンダリングしない
    self.backView.layer.transform   = CATransform3DMakeRotation(M_PI, 1.0, 0.0, 0); // X軸に対して180度回転

    UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    backBtn.frame = btnFrame;
    [backBtn setTitle:@"フリップ" forState:UIControlStateNormal];
    [backBtn addTarget:self
                action:@selector(toggle:)
      forControlEvents:UIControlEventTouchUpInside];
    [self.backView addSubview:backBtn];

    UIButton *frontBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    frontBtn.frame = btnFrame;
    [frontBtn setTitle:@"フリップ" forState:UIControlStateNormal];
    [frontBtn addTarget:self
                 action:@selector(toggle:)
       forControlEvents:UIControlEventTouchUpInside];
    [self.frontView addSubview:frontBtn];

    // ビューを追加
    [self.view addSubview:self.backView];
    [self.view addSubview:self.frontView];


    // パースペクティブを設定
    CATransform3D perspective = CATransform3DIdentity; //Identityは3Dマトリクスの初期値。数字で言えば`1`と同じ。演算しても結果が変わらない
    perspective.m34 = 1.0 / 1000;
    self.view.layer.sublayerTransform = perspective;
}
フリップのトグル
/**
 *  アニメーションのトグル
 */
- (void)toggle:(id)sender
{
    static BOOL toggleFlg = NO;

    [CATransaction begin];

    CATransform3D toFrontTransform = CATransform3DMakeRotation(M_PI * 2, 1.0, 0.0, 0.0);
    CATransform3D toBackTransform  = CATransform3DMakeRotation(M_PI,     1.0, 0.0, 0.0);

    // BasicAnimationを開始
    CABasicAnimation *toFrontAnim = [CABasicAnimation animationWithKeyPath:@"transform"];
    CABasicAnimation *toBackAnim  = [CABasicAnimation animationWithKeyPath:@"transform"];

    // アニメーション終了後の状態を維持
    toFrontAnim.removedOnCompletion = NO;
    toFrontAnim.fillMode            = kCAFillModeForwards;
    toBackAnim.removedOnCompletion  = NO;
    toBackAnim.fillMode             = kCAFillModeForwards;

    static const CFTimeInterval duration = 0.5;

    toFrontAnim.toValue   = [NSNumber valueWithCATransform3D:toFrontTransform];
    toFrontAnim.duration  = duration;

    toBackAnim.toValue    = [NSNumber valueWithCATransform3D:toBackTransform];
    toBackAnim.duration   = duration;

    // アニメーション終了時の処理
    [CATransaction setCompletionBlock:^{
        // アニメーション後の状態を設定
        if (toggleFlg) {
            self.frontView.layer.transform = CATransform3DMakeRotation(M_PI, 1.0, 0.0, 0);
            self.backView.layer.transform  = CATransform3DMakeRotation(0,    1.0, 0.0, 0);
            self.frontView.hidden = YES;
        }
        else {
            self.frontView.layer.transform = CATransform3DMakeRotation(0,    1.0, 0.0, 0);
            self.backView.layer.transform  = CATransform3DMakeRotation(M_PI, 1.0, 0.0, 0);
            self.backView.hidden = YES;
        }
    }];

    self.frontView.hidden = NO;
    self.backView.hidden  = NO;

    if ((toggleFlg = !toggleFlg)) {
        [self.frontView.layer addAnimation:toBackAnim  forKey:@"toBack"];
        [self.backView.layer  addAnimation:toFrontAnim forKey:@"toFront"];
    }
    else {
        [self.frontView.layer addAnimation:toFrontAnim forKey:@"toBack"];
        [self.backView.layer  addAnimation:toBackAnim  forKey:@"toFront"];
    }


    // アニメーションをコミット
    [CATransaction commit];
}

ざっくり説明すると、背面用と表面用のViewを作っておき、それをアニメーションで逆にしている、というものです。

注意点
  • サンプルには載せていませんが、上記のままだと背面のボタンなどのアクションが行えません。
    理由は、上記の演出はあくまで見た目上だけなのでレイヤーの重なり順などは変化していません。
    なので、アニメーション終了後にレイヤーの重なり順を変えるか、表用ViewのhiddenプロパティをNOにしてシステム上も非表示扱いにしておかないとなりません。

  • それから、アニメーション後の設定をあえて書いていますが、アニメーション後の状態は見た目だけのようで、ボタンの位置は回転の適用前の位置のままでした。なので、アニメーションさせる以外に、実際の値も変更しておく必要があります。

  • setCompletion:メソッドは、addAnimation:forKey:メソッドの前に設定しておかないと、即座に実行されてしまってうまく動きません。

参考記事・リファレンス

edo_m18
現在はUnity ARエンジニア。 主にARのコンテンツ制作をしています。 最近は機械学習にも興味が出て勉強中です。 Unityに関するブログは別で書いています↓ https://edom18.hateblo.jp/
http://edom18.hateblo.jp/
unity-game-dev-guild
趣味・仕事問わずUnityでゲームを作っている開発者のみで構成されるオンラインコミュニティです。Unityでゲームを開発・運用するにあたって必要なあらゆる知見を共有することを目的とします。
https://unity-game-dev-guild.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした