Skip to main content

Working with SwiftUI Animations

1. Using withAnimation Function

Parameter Type Description
animation Animation? The animation to apply to the view
content () -> View The view to animate

Example: Basic withAnimation Usage

struct ContentView: View {
    @State private var isAnimating = false
    
    var body: some View {
        Button("Animate") {
            withAnimation {
                isAnimating.toggle()
            }
        }
        .padding()
    }
}

Example: Custom Animation

struct ContentView: View {
    @State private var scale: CGFloat = 1.0
    
    var body: some View {
        Button("Scale") {
            withAnimation(.easeInOut(duration: 0.5)) {
                scale = scale == 1.0 ? 1.5 : 1.0
            }
        }
        .scaleEffect(scale)
        .padding()
    }
}

2. Applying .animation Modifier

Property Description Values
.animation Applies animation to a view .easeInOut(duration: TimeInterval), .linear(duration: TimeInterval), .easeIn(duration: TimeInterval), .easeOut(duration: TimeInterval)

Example: Basic Animation

struct ContentView: View {
    @State private var isAnimating = false

    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .animation(.easeInOut(duration: 1.0), value: isAnimating)
            .onTapGesture {
                isAnimating.toggle()
            }
    }
}

Example: Animation with Custom Timing

struct ContentView: View {
    @State private var scale: CGFloat = 1.0

    var body: some View {
        Rectangle()
            .fill(Color.red)
            .frame(width: 100, height: 100)
            .scaleEffect(scale)
            .animation(.easeInOut(duration: 0.5), value: scale)
            .onTapGesture {
                scale = scale == 1.0 ? 1.5 : 1.0
            }
    }
}

3. Using Animation Types

Type Description Usage
.easeIn Starts slowly and accelerates Animation.easeIn(duration: 0.5)
.easeOut Starts quickly and decelerates Animation.easeOut(duration: 0.5)
.easeInOut Starts and ends slowly, speeds up in the middle Animation.easeInOut(duration: 0.5)
.linear Constant speed throughout Animation.linear(duration: 0.5)
.spring Simulates a spring with bounce effect Animation.spring(dampingRatio: 0.5, response: 0.5)
.default Uses the system default animation Animation.default

Example: Applying Different Animations

struct ContentView: View {
    @State private var scale = 1.0
    @State private var rotation = 0.0

    var body: some View {
        VStack {
            Text("Animated Text")
                .scaleEffect(scale)
                .rotationEffect(.degrees(rotation))
                .animation(.easeInOut(duration: 0.5), value: scale)
                .onTapGesture {
                    scale = scale == 1.0 ? 1.5 : 1.0
                    rotation += 360
                }
        }
    }
}

Example: Spring Animation

struct SpringExample: View {
    @State private var position = CGSize.zero

    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 50, height: 50)
            .offset(position)
            .animation(.spring(), value: position)
            .onTapGesture {
                position = CGSize(width: 100, height: 100)
            }
    }
}

4. Creating Custom Timing Curves

Curve Type Description Usage
Custom Allows defining custom timing functions using CubicBezier Animation.easeInOut(duration: 1.0)
Linear Constant speed animation Animation.linear(duration: 1.0)
EaseIn Starts slow, speeds up Animation.easeIn(duration: 1.0)
EaseOut Starts fast, slows down Animation.easeOut(duration: 1.0)
EaseInOut Starts and ends slowly, speeds up in the middle Animation.easeInOut(duration: 1.0)

Example: Using Custom Timing Curve

struct ContentView: View {
    @State private var animatedValue = 0.0
    
    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .scaleEffect(animatedValue)
            .animation(
                Animation.easeInOut(duration: 1.0).timingCurve(0.25, 0.1, 0.25, 1.0),
                value: animatedValue
            )
    }
}
Note: The timingCurve method accepts four parameters representing control points for a cubic Bézier curve.
Warning: Custom timing curves may not be supported in all iOS versions. Always test on target devices.

5. Using Matched Geometry Effect

Property Description Usage
matchedGeometryEffect Applies a geometry effect to animate between views .matchedGeometryEffect(<span class="highlight">effect</span>, <span class="highlight">phase</span>)

Example: Basic Matched Geometry Effect

struct ContentView: View {
    @State private var isExpanded = false
    
    var body: some View {
        HStack {
            if isExpanded {
                Text(<span class="highlight">"Expanded"</span>)
                    .matchedGeometryEffect(<span class="highlight">id</span>: <span class="highlight">"shared"</span>, <span class="highlight">phase</span>: .identity)
            } else {
                Text(<span class="highlight">"Collapsed"</span>)
                    .matchedGeometryEffect(<span class="highlight">id</span>: <span class="highlight">"shared"</span>, <span class="highlight">phase</span>: .identity)
            }
        }
        .onTapGesture {
            isExpanded.toggle()
        }
    }
}
Note: The id must be consistent across views to animate between them.

Key Points

  • Use matchedGeometryEffect to animate between views with shared geometry
  • Must use the same id across views to animate between them
  • Works best with GeometryReader and ViewModifier

6. Implementing Transitions

Property Value Description
.transition .opacity, .scale, .move, .slide Defines the visual transition effect for views
.animation .easeInOut, .linear, .easeIn, .easeOut Applies animation to a view's state changes

Example: Using .transition with .opacity

struct ContentView: View {
    @State private var showView = false

    var body: some View {
        VStack {
            Button("Toggle View") {
                showView.toggle()
            }

            if showView {
                Text("Animated View")
                    .transition(.opacity)
            }
        }
    }
}

Example: Combining .transition and .animation

struct AnimatedView: View {
    @State private var isVisible = false

    var body: some View {
        VStack {
            Button("Animate") {
                isVisible.toggle()
            }

            Text("Animated Text")
                .opacity(isVisible ? 1 : 0)
                .transition(.scale.combined(with: .opacity))
                .animation(.easeInOut(duration: 0.5), value: isVisible)
        }
    }
}
Note: Transitions are applied to views that are conditionally displayed or removed from the view hierarchy.

7. Combining Multiple Animations

Example: Sequential and Parallel Animations

struct ContentView: View {
    @State private var isAnimating = false
    
    var body: some View {
        VStack(spacing: 20) {
            Rectangle()
                .fill(Color.blue)
                .frame(width: 100, height: 100)
                .onTapGesture {
                    isAnimating.toggle()
                }
                .animation(
                    Animation.easeInOut(duration: 1.0)
                        .delay(0.2),
                    value: isAnimating
                )
            
            Rectangle()
                .fill(Color.red)
                .frame(width: 100, height: 100)
                .animation(
                    Animation.easeInOut(duration: 1.0)
                        .delay(0.5),
                    value: isAnimating
                )
        }
    }
}
Animation Type Description Usage
Animation.easeInOut(duration:) Starts and ends slowly, speeds up in the middle Animation.easeInOut(duration: 1.0)
Animation.linear(duration:) Constant speed throughout the animation Animation.linear(duration: 0.5)
Animation.easeIn(duration:) Starts slowly, ends quickly Animation.easeIn(duration: 0.8)
Animation.easeOut(duration:) Starts quickly, ends slowly Animation.easeOut(duration: 0.8)
Note: When combining multiple animations, ensure that each animation has a unique .delay or .repeat to avoid overlapping effects.

Example: Using .animation with .delay

struct ContentView: View {
    @State private var scale = 1.0
    @State private var opacity = 1.0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Hello SwiftUI")
                .scaleEffect(scale)
                .opacity(opacity)
                .animation(
                    Animation.easeInOut(duration: 0.5).delay(0.2),
                    value: scale
                )
                .animation(
                    Animation.easeInOut(duration: 0.5).delay(0.4),
                    value: opacity
                )
        }
    }
}

8. Using Phase Animator

Example: Basic Phase Animator Usage

import SwiftUI

struct ContentView: View {
    @State private var phase: Double = 0.0
    
    var body: some View {
        PhaseAnimator(phase) { phase in
            Circle()
                .fill(Color.blue)
                .frame(width: 100, height: 100)
                .scaleEffect(phase)
        }
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                phase = 1.0
            }
        }
    }
}
Property Description Values
phase The current phase value (0.0 to 1.0) Double
animation Animation to use for the phase transition Animation?
Note: The PhaseAnimator is a SwiftUI view that animates based on a phase value between 0.0 and 1.0.

9. Using Keyframe Animator

Property Value Description
keyframes [Keyframe] Array of keyframe values defining animation sequence
duration Double Total duration of the animation in seconds
timing AnimationTimingFunction Timing function to control animation speed curve

Example: Basic Keyframe Animation

struct ContentView: View {
    @State private var scale = 1.0
    
    var body: some View {
        Circle()
            .fill(Color.blue)
            .scaleEffect(scale)
            .animation(
                Animation.keyframe(
                    0.5,
                    keyframes: [
                        Keyframe(0, value: 1.0),
                        Keyframe(0.25, value: 1.5),
                        Keyframe(0.5, value: 0.8)
                    ],
                    timing: .easeInOut
                ),
                value: scale
            )
    }
}

Example: Keyframe with Custom Timing

struct AnimatedView: View {
    @State private var position = CGSize.zero
    
    var body: some View {
        Rectangle()
            .fill(Color.red)
            .offset(position)
            .animation(
                Animation.keyframe(
                    2.0,
                    keyframes: [
                        Keyframe(0, value: CGSize.zero),
                        Keyframe(0.5, value: CGSize(width: 100, height: 0)),
                        Keyframe(1.0, value: CGSize(width: 100, height: 100)),
                        Keyframe(1.5, value: CGSize(width: 0, height: 100)),
                        Keyframe(2.0, value: CGSize.zero)
                    ],
                    timing: .easeOut
                ),
                value: position
            )
    }
}
Note: Keyframe animations are ideal for complex motion paths or precise timing control.

10. Creating Animation Sequences

Property Description Usage
animation Applies an animation to a view .animation(.easeInOut(duration: 0.5))
animation(.easeIn(duration: 0.3)) Applies an ease-in animation .animation(.easeIn(duration: 0.3))
animation(.easeOut(duration: 0.3)) Applies an ease-out animation .animation(.easeOut(duration: 0.3))
animation(.easeInOut(duration: 0.5)) Applies an ease-in-out animation .animation(.easeInOut(duration: 0.5))

Example: Basic Animation Sequence

struct ContentView: View {
    @State private var isAnimating = false

    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .scaleEffect(isAnimating ? 1.5 : 1)
            .animation(.easeInOut(duration: 0.5))
            .onTapGesture {
                isAnimating.toggle()
            }
    }
}

Example: Sequential Animation

struct SequentialAnimationView: View {
    @State private var scale = 1.0
    @State private var opacity = 1.0

    var body: some View {
        Circle()
            .fill(Color.red)
            .frame(width: 100, height: 100)
            .scaleEffect(scale)
            .opacity(opacity)
            .animation(.easeInOut(duration: 0.5))
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    scale = 1.2
                    opacity = 0.7
                }
            }
    }
}
Note: SwiftUI animations are applied to the view and its subviews. The .animation modifier can be chained with other modifiers to create complex sequences.

Was this article helpful?