[SwiftUI] サイコロを振ってみる (SwiftUI アニメーションの練習)

SwiftUI

SwiftUI でアニメーションを使う練習として、サイコロを振るビューを作ってみます。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.2
  • Xcode 12.4
  • iOS 14.4

説明したいこと

概要

乱数で、サイコロを目を表示するだけは普通なので、しばらく回転してから 目が表示されるようなビューを作ってみます。

SwiftUI のアニメーションを使って、実装してみます。


サイコロの目を表示

サイコロの目のイメージ画像を6枚 "1", "2", ..., "6" という名前でリソース内に用意しました。

Roll Dice

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/02/03
//  © 2021  SmallDeskSoftware
//
//
import SwiftUI

struct ContentView: View {
    // (1)
    @State private var dice:Int = Int.random(in: 1...6)
    var body: some View {
        VStack {
            // (2)
            Image("\(dice)")
                .resizable()
                .scaledToFit()
            Button(action: {
                // (3)
                self.dice = Int.random(in: 1...6)
            }, label: {
                Text("Roll")
            })
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
コード解説
  1. サイコロの値を保持する変数です。初期値を乱数を使って設定しています。
  2. 数字を名前に持つイメージを用意したので、そのまま使っています。
  3. Roll ボタンを押されたときに、サイコロの値を乱数で再設定しています。

パッと表示されてしまうと、すこしシンプルすぎる感じを受けます。

アニメーションを追加

複雑なアニメーションを作ると大変なので、SwiftUI で用意されている rotation3DEffect を使った アニメーションにします。

Rotation3DEffect を使うと表示要素を3次元的に回転させることができます。

rotation3DEffect アニメーション

rotation3DEffect

func rotation3DEffect(_ angle: Angle, axis: (x: CGFloat, y: CGFloat, z: CGFloat), anchor: UnitPoint = .center, anchorZ: CGFloat = 0, perspective: CGFloat = 1) -> some View
Axis を回転軸として、anchor を中心に、angle 分回転するアニメーションとなります。

MEMO
今回はイメージを使用しています。SwiftUI 視点では、イメージは、その名称でしかないので、デフォルトでアニメーションをつけることはできません。
ですので、明示的に、アニメーションを指定する必要があります。

アニメーションを動作させる起点が必要ですので、@State 変数を1つ追加します。
ボタンが押されたときにその変数を操作することで、再描画のきっかけとします。
再描画される側は、回転角度を変更することでアニメーションさせます。

以下のような動作になります。

使ったコードはこちら。

Roll Dice アニメーション版

//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/02/03
//  © 2021  SmallDeskSoftware
//
// assset from https://chicodeza.com/freeitems/saikoro-illust.html

import SwiftUI

struct ContentView: View {
    @State private var dice:Int = Int.random(in: 1...6)
    // (1)
    @State private var animate = false
    var body: some View {
        VStack {
            Image("\(dice)")
                .resizable()
                .scaledToFit()
                // (2)
                .rotation3DEffect(
                    // (3)
                    Angle.degrees(animate ? 360 * 20 : 0),
                    axis: (x:1, y:1, z:1))
            Button(action: {
                self.dice = Int.random(in: 1...6)
                // (4)
                withAnimation {
                    animate.toggle()
                }
            }, label: {
                Text("Roll")
            })
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
コード解説
  1. アニメーションの起点となる変数
  2. Image に対して rotation3DEffect を設定します。
  3. animate 変数を使用して回転角を指定することでアニメーションします(角度や軸そのものは、適当な値です)
  4. animate 変数を withAnimation 内で操作することで、アニメーションが行われるようになります。

アニメーションを改良

このままでも良いのですが、もう少し改良します。

アニメーションをよくみると、アニメーション開始時には、新しい数値になっています。

これをアニメーション途中で数字を切り替えるようにしてみます。(機能的には意味はないです・・・)

以下のようにアニメーションのコードを変更しました。

example

            Button(action: {
                // (1)
                withAnimation(Animation.default.repeatForever()) {
                    animate.toggle()
                }
                // (2)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    withAnimation {
                        self.dice = Int.random(in: 1...6)
                        animate.toggle()
                    }
                }
            }, label: {
コード解説
  1. 最初の数字でアニメーションを実行します
  2. 0.5秒後に、新しい数字を設定してアニメーションを実行します

こうすることで、途中までは最初の数字、後半から別の数字でアニメーションするようになります。

少しのコードを追加するだけで、動きが加わるのでアニメーションは作っていて楽しいです。

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です