Implicit Animations
What Are Implicit Animations?
Implicit animations automatically animate when the animatable properties of a view change. You attach the .animation() modifier to a view, and SwiftUI handles the transition smoothly whenever state changes.
The .animation Modifier
The simplest way to add animation is by attaching .animation() to any view. When any animatable property changes, the animation plays automatically.
struct PulseView: View {
@State private var isScaled = false
var body: some View {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.scaleEffect(isScaled ? 1.5 : 1.0)
.animation(
.easeInOut(duration: 0.8)
.repeatForever(autoreverses: true),
value: isScaled
)
.onAppear {
isScaled = true
}
}
}
Animation Curves
SwiftUI provides several built-in timing curves that control the acceleration and deceleration of animations:
.linear- Constant speed throughout.easeIn- Starts slow, speeds up.easeOut- Starts fast, slows down.easeInOut- Slow start and end, fast middle.spring(response:dampingFraction:blendDuration:)- Physics-based spring animation
struct AnimationCurvesDemo: View {
@State private var offset: CGFloat = 0
var body: some View {
VStack(spacing: 20) {
Capsule()
.fill(.red)
.frame(width: 80, height: 40)
.offset(x: offset)
.animation(.linear(duration: 1), value: offset)
Capsule()
.fill(.green)
.frame(width: 80, height: 40)
.offset(x: offset)
.animation(.easeIn(duration: 1), value: offset)
Capsule()
.fill(.blue)
.frame(width: 80, height: 40)
.offset(x: offset)
.animation(.spring(response: 0.5, dampingFraction: 0.6), value: offset)
}
.onTapGesture {
offset = offset == 0 ? 150 : 0
}
}
}
Animatable Properties
Not all properties are animatable. SwiftUI animates changes to properties like frame, offset, opacity, rotation, scale, and foregroundColor. Custom properties can be animated using the Animatable protocol.
Conditional Animation
You can make animations apply only to specific properties or use the .animation(_:value:) syntax to trigger animation only when a specific value changes.
struct ConditionalAnimation: View {
@State private var isActive = false
@State private var color: Color = .blue
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 12)
.fill(color)
.frame(
width: isActive ? 200 : 100,
height: isActive ? 200 : 100
)
.animation(.spring(), value: isActive)
.animation(.easeInOut(duration: 1), value: color)
Button("Toggle Size") {
isActive.toggle()
}
Button("Change Color") {
color = color == .blue ? .purple : .blue
}
}
}
}
Explicit Animations
The withAnimation Block
Explicit animations give you precise control over what animates and when. The withAnimation closure wraps state changes, and SwiftUI animates all view changes that result from those state changes.
struct ExplicitAnimationDemo: View {
@State private var isRotated = false
@State private var isScaled = false
@State private var opacity: Double = 1
var body: some View {
VStack(spacing: 30) {
Rectangle()
.fill(.blue)
.frame(width: 100, height: 100)
.rotationEffect(.degrees(isRotated ? 45 : 0))
.scaleEffect(isScaled ? 1.5 : 1.0)
.opacity(opacity)
HStack {
Button("Rotate") {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
isRotated.toggle()
}
}
Button("Scale") {
withAnimation(.easeInOut(duration: 0.5)) {
isScaled.toggle()
}
}
Button("Fade") {
withAnimation(.linear(duration: 1)) {
opacity = opacity == 1 ? 0.3 : 1
}
}
}
}
}
}
Controlling Animation Speed
You can customize animation duration and behavior by passing different animation types to withAnimation:
// Fast spring
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
stateChange()
}
// Slow ease
withAnimation(.easeInOut(duration: 2.0)) {
stateChange()
}
// Repeating animation
withAnimation(.linear(duration: 1).repeatCount(3, autoreverses: true)) {
stateChange()
}
// Delayed animation
withAnimation(.easeIn(duration: 0.5).delay(0.2)) {
stateChange()
}
Animating Multiple Properties
When using withAnimation, all animatable properties that change within the block animate simultaneously. You can stagger animations by using nested withAnimation calls or by using the .animation modifier with specific values.
struct StaggeredAnimation: View {
@State private var showItems = false
let items = ["Item 1", "Item 2", "Item 3", "Item 4"]
var body: some View {
VStack {
ForEach(Array(items.enumerated()), id: \ .offset) { index, item in
Text(item)
.padding()
.background(Color.blue.opacity(0.2))
.cornerRadius(8)
.offset(x: showItems ? 0 : -200)
.opacity(showItems ? 1 : 0)
.animation(
.spring(response: 0.5, dampingFraction: 0.7)
.delay(Double(index) * 0.1),
value: showItems
)
}
Button("Show Items") {
showItems.toggle()
}
}
.padding()
}
}
Transitions & matchedGeometryEffect
View Transitions
Transitions define how a view is inserted into or removed from the view hierarchy. SwiftUI provides built-in transitions and lets you combine them for complex effects.
struct TransitionDemo: View {
@State private var showDetail = false
var body: some View {
VStack {
if showDetail {
Text("Detail View")
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.transition(.slide)
}
Button("Toggle") {
withAnimation {
showDetail.toggle()
}
}
}
}
}
Built-in Transitions
SwiftUI includes several built-in transitions:
.opacity- Fades in and out.slide- Slides in from leading edge.move(edge:)- Moves in from specified edge.scale- Scales from zero to full size.offset(x:y:)- Offsets from specified position
// Combining transitions
.transition(.asymmetric(
insertion: .slide.combined(with: .opacity),
removal: .scale.combined(with: .opacity)
))
// Using modifiers with transitions
.transition(.offset(x: 0, y: 100).combined(with: .opacity))
matchedGeometryEffect
matchedGeometryEffect creates seamless hero animations between views. It links the geometry of one view to another, allowing smooth transitions when views appear, disappear, or move.
struct MatchedGeometryDemo: View {
@Namespace private var animationNamespace
@State private var isExpanded = false
var body: some View {
VStack {
if isExpanded {
// Expanded view
RoundedRectangle(cornerRadius: 20)
.fill(
LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 350, height: 500)
.overlay(
VStack {
Text("Expanded Card")
.font(.title)
.foregroundColor(.white)
Text("Tap to collapse")
.foregroundColor(.white.opacity(0.8))
}
)
.matchedGeometryEffect(id: "card", in: animationNamespace)
} else {
// Collapsed view
RoundedRectangle(cornerRadius: 12)
.fill(.blue)
.frame(width: 100, height: 80)
.overlay(
Text("Card")
.foregroundColor(.white)
)
.matchedGeometryEffect(id: "card", in: animationNamespace)
}
Button("Toggle") {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
isExpanded.toggle()
}
}
.padding(.top, 40)
}
}
}
Namespace and Identifiers
Each matchedGeometryEffect requires a unique ID within a Namespace. The ID can be any Hashable value. Ensure that the same ID is used for the source and destination views to create the matching effect.
Practical Use Cases
- Navigation bar title to large title transitions
- List item to detail view hero animations
- Tab bar icon to full-screen transitions
- Card expand/collapse animations in feeds
- Image gallery thumbnail to full-screen preview
Quiz
1. What is the difference between implicit and explicit animations in SwiftUI?
2. Which parameter controls the bounciness of a spring animation?
3. What does matchedGeometryEffect require to link two views?
4. How do you combine multiple transitions?
Flashcards
Question
What is the difference between .animation() modifier and withAnimation {}?
Click to reveal answer
Answer
.animation() is implicit - it animates any property changes on the view. withAnimation {} is explicit - it wraps specific state changes to trigger animation.
Question
What does dampingFraction control in spring animations?
Click to reveal answer
Answer
It controls how much the spring oscillates before settling. Lower values create more bounce, higher values dampen the oscillation.
Question
What is matchedGeometryEffect used for?
Click to reveal answer
Answer
Creating smooth hero animations by linking the geometry of two views, allowing seamless transitions when views appear, disappear, or change position.
Question
Name three built-in SwiftUI transitions.
Click to reveal answer
Answer
.opacity (fade), .slide (slide from edge), .move(edge:) (move from specific edge), .scale (scale from zero), .offset (offset from position).
Revision Notes
Key Takeaways
- 1. Implicit animations use .animation() modifier, explicit use withAnimation {}
- 2. Spring animations provide natural, physics-based motion
- 3. Transitions control how views enter and leave the view hierarchy
- 4. matchedGeometryEffect creates hero animations between views
- 5. Combine transitions for complex insertion and removal effects
Interview Tips
- • Explain when to use implicit vs explicit animations
- • Describe how to create a spring animation and what parameters control it
- • Discuss practical uses of matchedGeometryEffect in navigation flows
Cheat Sheet
Animations & Transitions Quick Reference
- Implicit: view.animation(.spring(), value: triggerValue)
- Explicit: withAnimation(.spring()) { stateChange() }
- Spring: .spring(response: 0.5, dampingFraction: 0.7)
- Transitions: .transition(.slide), .transition(.opacity), .transition(.scale)
- Combined: .transition(.slide.combined(with: .opacity))
- matchedGeometryEffect: @Namespace + .matchedGeometryEffect(id:in:)
- Asymmetric: .transition(.asymmetric(insertion:removal:))