SwiftUI animation モディファイアと暗黙的アニメーション
SwiftUI には .animation モディファイアを使った暗黙的アニメーションという仕組みがある。withAnimation が「このタイミングで」と明示するのに対し、暗黙的アニメーションは「この値が変わったら常に」という宣言的なアプローチだ。
animation モディファイアの基本
.animation モディファイアは、特定の値の変化を監視し、その値に依存するビューの変更をアニメーション化する。
struct ContentView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Rectangle()
.fill(.blue)
.frame(width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100)
.animation(.easeInOut, value: isExpanded)
Button("Toggle") {
isExpanded.toggle()
}
}
}
}withAnimation で囲まなくても、isExpanded が変わるたびに自動でアニメーションが実行される。
value パラメータの重要性
iOS 15 以降、.animation には必ず value パラメータを指定する必要がある。これは「どの値の変化に反応するか」を明示するためだ。
// iOS 15 以降の正しい書き方
.animation(.spring(), value: someValue)
// 非推奨(iOS 15 で deprecated)
.animation(.spring())value を省略した古い書き方は、意図しないアニメーションが発生する原因になっていた。どの状態変化に対してアニメーションするかを明確にすることで、予測可能な動作が保証される。
複数の値を監視
複数の値の変化に対してアニメーションさせたい場合は、animation モディファイアを複数つけるか、値をまとめる。
struct MultiValueView: View {
@State private var offset: CGFloat = 0
@State private var rotation: Double = 0
var body: some View {
Rectangle()
.frame(width: 100, height: 100)
.offset(y: offset)
.rotationEffect(.degrees(rotation))
.animation(.spring(), value: offset)
.animation(.easeInOut, value: rotation)
}
}offset の変化にはスプリング、rotation の変化には easeInOut と、別々のアニメーションを適用できる。
暗黙的 vs 明示的アニメーション
ビューの定義時にアニメーションを宣言。値が変われば常にアニメーション。宣言的で SwiftUI らしいアプローチ。
状態変更時にアニメーションを指定。特定のアクションだけアニメーションさせたい場合に有効。手続き的なアプローチ。
どちらを使うかは設計思想による。「このビューは常にアニメーションする」なら暗黙的、「このボタンを押したときだけ」なら明示的が適している。
animation(nil) でアニメーションを無効化
特定の変更だけアニメーションさせたくない場合、.animation(nil, value:) を使う。
struct SelectiveAnimationView: View {
@State private var position: CGFloat = 0
@State private var color: Color = .blue
var body: some View {
Rectangle()
.fill(color)
.frame(width: 100, height: 100)
.offset(x: position)
.animation(.spring(), value: position)
.animation(nil, value: color) // 色の変化はアニメーションしない
}
}これにより、位置の変化はアニメーションするが、色の変化は即座に反映される。
適用順序の注意点
animation モディファイアは、それより上に書かれたモディファイアにのみ影響する。
Rectangle()
.fill(color)
.animation(.easeInOut, value: color) // fill に影響
.frame(width: size, height: size)
.animation(.spring(), value: size) // frame に影響モディファイアチェーンの順序を意識することで、部分的なアニメーション制御が可能になる。このあたりは SwiftUI のモディファイア適用順序の理解が前提となる。