Skip to content

Controlled Animations

Controlling the timing and movements of your animations

.phaseAnimator()

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Animations/Controlled-Animations

import SwiftUI

struct PhaseAnimatorExample: View {
    @State private var trigger = false

    enum Phase: CaseIterable {
        case small, large
    }

    var body: some View {
        VStack {
            Image(systemName: "star.fill")
                .font(.system(size: 50))
                .phaseAnimator(
                    [Phase.small, .large],
                    trigger: trigger,
                    content: { content, phase in

                        let color = switch phase {
                        case .small: Color.blue
                        case .large: Color.orange
                        }

                        let scale = switch phase {
                        case .small: 0.5
                        case .large: 1.2
                        }

                        let yOffset = switch phase {
                        case .small: 0.0
                        case .large: -40.0
                        }

                        content
                            .foregroundStyle(color)
                            .scaleEffect(scale)
                            .offset(y: yOffset)
                    },
                    animation: { phase in
                        switch phase {
                        case .small: .easeIn(duration: 0.2)
                        case .large: .spring(duration: 0.5, bounce: 0.4)
                        }
                    }
                )
                .frame(height: 150)

            Button("Animate", systemImage: "play.fill") {
                trigger.toggle()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

#Preview {
    PhaseAnimatorExample()
}

.keyframeAnimator()

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Animations/Controlled-Animations

import SwiftUI

struct KeyframeAnimatorExample: View {
    @State private var trigger = false

    struct AnimationValues {
        var scale = 1.0
        var rotation = Angle.zero
        var yOffset = 0.0
    }

    var body: some View {
        VStack {
            Image(systemName: "star.fill")
                .font(.system(size: 50))
                .foregroundStyle(.orange)
                .keyframeAnimator(
                    initialValue: AnimationValues(),
                    trigger: trigger,
                    content: { content, value in
                        content
                            .scaleEffect(value.scale)
                            .rotationEffect(value.rotation)
                            .offset(y: value.yOffset)
                    },
                    keyframes: { _ in
                        KeyframeTrack(\.scale) {
                            LinearKeyframe(1.5, duration: 0.2)
                            SpringKeyframe(0.8, spring: .bouncy)
                            CubicKeyframe(1.2, duration: 0.3)
                            LinearKeyframe(1.0, duration: 0.2)
                        }

                        KeyframeTrack(\.rotation) {
                            LinearKeyframe(.degrees(45), duration: 0.3)
                            CubicKeyframe(.degrees(-30), duration: 0.4)
                            SpringKeyframe(.degrees(360), spring: .snappy)
                        }

                        KeyframeTrack(\.yOffset) {
                            LinearKeyframe(-30, duration: 0.2)
                            SpringKeyframe(20, spring: .bouncy(duration: 0.4))
                            CubicKeyframe(-10, duration: 0.2)
                            LinearKeyframe(0, duration: 0.3)
                        }

                    }
                )
                .frame(height: 150)

            Button("Animate", systemImage: "play.fill") {
                trigger.toggle()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

#Preview {
    KeyframeAnimatorExample()
}