(2020/10/04 更新)
SwiftUIではViewの変化を簡単にアニメーション化できます。
本記事ではアニメーションの基本について解説します。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.3
【iOS】14.0
【macOS】Catalina バージョン 10.15.6
SwiftUIでのアニメーションとは
SwiftUIではプロパティの変化によってViewの形状を変更できますが、形状の切り替え時に特定の視覚効果をつけられるのがアニメーション(animation)です。
他に、Viewの表示/非表示が切り替わる際に発生する特別なアニメーションもありますが、これはトランジション(transition)と呼ばれます。
トランジションについては別の記事【SwiftUI】トランジション(transition)の使い方で説明します。
Viewにアニメーションを適用する
Viewにアニメーションを適用したサンプルを紹介します。
次の例では、「アニメーション」ボタンをタップすると、青い円の拡大率が変更され、拡大・縮小します。
animationモディファイアの適用で、ぱっと切り替わるのではなく徐々に変化するような視覚効果を付与します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
struct ContentView: View { @State private var flag = true var body: some View { ZStack { Circle() .fill(Color.blue) .frame(width: 100, height: 100) .scaleEffect(flag ? 1.0 : 2.0) // flagによって円の拡大率が変わる .animation(.default) // animationモディファイアの適用 VStack { Spacer() Button("アニメーション") { self.flag.toggle() // ボタンタップでflagを変更 } } } } } |
animationモディファイアについて
Viewの形状やエフェクトの変化に対して、アニメーションを適用するには、animationモディファイアを使用します。
1 2 3 |
.animation(Animation) |
引数に指定したAnimation構造体のインスタンスで、アニメーション効果のタイプを設定します。
.defaultを指定するとデフォルトのアニメーションが適用されます。
アニメーション効果のタイプについては別の記事【SwiftUI】アニメーション効果の種類で解説します。
アニメーションの効果は複数の状態変化に対して適用できます。
先程のサンプルで、円の色を描画する.fillモディファイアを次のように置き換えると、拡大率の変化に加えて、色も徐々に変化する効果が加わります。
1 2 3 |
.fill(flag ? Color.blue : Color.yellow) |
なお、アニメーション効果が適用されるのは、.animationモディファイアよりも前に定義したModifierのみです。
注意点
Appleの公式ドキュメントに次のような気になる記述があります。
Use this modifier on leaf views rather than container views. The animation applies to all child views within this view; calling animation(_:) on a container view can lead to unbounded scope.
「この修飾子は、コンテナビューではなくリーフビューで使用します。アニメーションは、このビュー内のすべての子ビューに適用されます。 コンテナビューで animation(_:) を呼び出すと、スコープが無制限になる可能性があります。」
試してみた範囲では、VStackやHStackなどのコンテナに適用しても問題なく動いているように見えるので、どういう条件で発生する問題かわかりませんが、もし.animationモディファイアがうまく機能しなかった場合はこの点を疑ってみましょう。
条件付きアニメーション
1 2 3 |
.animation(Animation, value: Value) |
引数(value)で指定したプロパティが変更された時のみ、Viewをアニメーション化します。
監視されるプロパティはあくまでアニメーションを有効にするか否かの判断に使うだけですので、アニメーション化の範囲は、このプロパティ以外の変化も含みます。
プロパティの変化にアニメーションを適用する
Viewではなく、特定のプロパティの変化対してアニメーションを適用するには、withAnimation関数をいます。
特定のプロパティが関連する複数のViewをまとめてアニメーション化する場合に役立ちます。
1 2 3 4 5 |
withAnimation(Animation = .default) { // アニメーション化するプロパティの変更処理を記述 } |
引数に指定したAnimation構造体のインスタンスでアニメーションのタイプを設定します。
デフォルト値(.default)が規定されてる為、引数無しの場合はデフォルトのアニメーションが適用されます。
nilが指定された場合は、アニメーションは実行されません。
使用例
「アニメーション」ボタンをクリックすると、2つの円がアニメーションを伴って、拡大・縮小します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
struct ContentView: View { @State private var flag = true var body: some View { ZStack { HStack { Circle() .fill(Color.blue) .frame(width: 100, height: 100) .scaleEffect(flag ? 1.0 : 2.0) Circle() .fill(Color.red) .frame(width: 200, height: 200) .scaleEffect(flag ? 1.0 : 0.5) } VStack { Spacer() Button("アニメーション") { withAnimation() { self.flag.toggle() // flagの変更がアニメーション化される } } } } } } |
Bindingプロパティにアニメーションを適用する
プロパティの更新が、Toggleなどの画面部品オブジェクトの中で行われる場合は、バインドするプロパティに直接animationモディファイアを適用するとwithAnimation()と同様の効果を得られます。
1 2 3 |
.animation(Animation = .default) |
このケースも、デフォルト値(.default)が規定されてる為、引数無しの場合はデフォルトのアニメーションが適用されます。
使用例
Toggleボタンをクリックすると、2つの円がアニメーションを伴って、拡大・縮小します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
struct ContentView: View { @State private var flag = true var body: some View { ZStack { HStack { Circle() .fill(Color.blue) .frame(width: 100, height: 100) .scaleEffect(flag ? 1.0 : 2.0) Circle() .fill(Color.red) .frame(width: 200, height: 200) .scaleEffect(flag ? 1.0 : 0.5) } VStack { Spacer() Toggle("", isOn: $flag.animation()) // bindingプロパティにアニメーションを適用 .labelsHidden() } } } } |
アニメーション処理のキャンセル
.animationモディファイアの引数にnilを指定すると、それより前に適用されたModifierのアニメーション処理を全てキャンセルします。
1 2 3 |
.animation(nil) |
プロパティの変化に対してアニメーションが指定された場合、そののプロパティに関連する全てのViewがアニメーション化の対象となりますが、animation(nil)モディファイアを使って、一部を対象外にできます。
使用例
Toggleボタンをクリックすると、2つの円が拡大・縮小しますが、右側の円はアニメーション処理を伴いません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
struct ContentView: View { @State private var flag = true var body: some View { ZStack { HStack { Circle() .fill(Color.blue) .frame(width: 100, height: 100) .scaleEffect(flag ? 1.0 : 2.0) Circle() .fill(Color.red) .frame(width: 200, height: 200) .scaleEffect(flag ? 1.0 : 0.5) .animation(nil) // アニメーションのキャンセル } VStack { Spacer() Toggle("", isOn: $flag.animation()) .labelsHidden() } } } } |