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
matchedGeometryEffectto animate between views with shared geometry - Must use the same
idacross views to animate between them - Works best with
GeometryReaderandViewModifier
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.