SwiftUI カスタム Transition の作成
組み込みの Transition だけでは表現できない動きが必要な場合、カスタム Transition を作成できる。ViewModifier を 2 つ用意し、それらの間を補間することで独自のアニメーション効果を実現する。
AnyTransition.modifier の基本
カスタム Transition は、active 状態と identity 状態の 2 つの ViewModifier を定義して作る。
struct RotateModifier: ViewModifier {
let angle: Double
let anchor: UnitPoint
func body(content: Content) -> some View {
content
.rotationEffect(.degrees(angle), anchor: anchor)
.opacity(angle == 0 ? 1 : 0)
}
}
extension AnyTransition {
static var rotate: AnyTransition {
.modifier(
active: RotateModifier(angle: 90, anchor: .bottomLeading),
identity: RotateModifier(angle: 0, anchor: .bottomLeading)
)
}
}active は「ビューがまだ見えない状態」、identity は「ビューが完全に見えている状態」を表す。挿入時は active から identity へ、削除時は identity から active へアニメーションする。
Text("回転して登場")
.transition(.rotate)より実践的な例:フリップ Transition
カードがめくれるような効果を作ってみる。
struct FlipModifier: ViewModifier {
let angle: Double
let axis: (x: CGFloat, y: CGFloat, z: CGFloat)
func body(content: Content) -> some View {
content
.rotation3DEffect(
.degrees(angle),
axis: axis,
perspective: 0.5
)
.opacity(abs(angle) > 90 ? 0 : 1)
}
}
extension AnyTransition {
static var flipFromLeft: AnyTransition {
.modifier(
active: FlipModifier(angle: -180, axis: (0, 1, 0)),
identity: FlipModifier(angle: 0, axis: (0, 1, 0))
)
}
static var flipFromBottom: AnyTransition {
.modifier(
active: FlipModifier(angle: 90, axis: (1, 0, 0)),
identity: FlipModifier(angle: 0, axis: (1, 0, 0))
)
}
}3D 回転を使うことで、平面的なスライドやフェードとは違った奥行きのある動きを実現できる。
asymmetric と組み合わせる
カスタム Transition も asymmetric と組み合わせて、挿入と削除で異なる動きにできる。
extension AnyTransition {
static var rotateInOut: AnyTransition {
.asymmetric(
insertion: .modifier(
active: RotateModifier(angle: -90, anchor: .topTrailing),
identity: RotateModifier(angle: 0, anchor: .topTrailing)
),
removal: .modifier(
active: RotateModifier(angle: 90, anchor: .bottomLeading),
identity: RotateModifier(angle: 0, anchor: .bottomLeading)
)
)
}
}挿入時は右上を軸に回転して登場し、削除時は左下を軸に回転して消えていく。
Animatable プロトコルとの連携
より複雑なアニメーションを実現するには、ViewModifier に Animatable プロトコルを実装する。
struct WaveModifier: ViewModifier, Animatable {
var progress: Double
var animatableData: Double {
get { progress }
set { progress = newValue }
}
func body(content: Content) -> some View {
content
.offset(y: sin(progress * .pi * 2) * 10)
.scaleEffect(1 + cos(progress * .pi) * 0.1)
}
}animatableData を定義することで、SwiftUI がその値を補間してくれる。これにより、単純な線形補間では表現できない複雑な動きも可能になる。
注意点とベストプラクティス
カスタム Transition は描画負荷が高くなりがち。3D 回転や複雑なエフェクトは、多数の要素に同時適用しないよう注意する。
アプリ内で使う Transition は統一感を持たせる。あちこちで異なる派手な Transition を使うと、ユーザーが混乱する原因になる。
シミュレーターだけでなく実機でも確認する。特に古いデバイスではパフォーマンスの問題が顕著に現れることがある。
カスタム Transition は表現の幅を広げる強力なツールだが、使いすぎには注意したい。標準の Transition で十分な場面も多いので、本当に必要な場面で効果的に使うことが大切だ。