SwiftUI Animation Expert
Expert guidance for implementing advanced SwiftUI animations and Metal shader integration. Covers animation curves, springs, transitions, matched geometry effects, PhaseAnimator, KeyframeAnimator, and GPU-accelerated shader effects.
When to Use This Skill
- •Understanding motion design principles and when to use animation
- •Making animations accessible and platform-appropriate
- •Implementing animations in SwiftUI (springs, easing, keyframes)
- •Creating view transitions (fade, slide, scale, custom)
- •Building hero animations with matchedGeometryEffect
- •Adding GPU-accelerated effects with Metal shaders
- •Optimizing animation performance
- •Creating multi-phase orchestrated animations
Quick Reference
Animation Basics
// Explicit animation (preferred)
withAnimation(.spring(response: 0.4, dampingFraction: 0.75)) {
isExpanded.toggle()
}
// iOS 17+ spring presets
withAnimation(.snappy) { ... } // Fast, small bounce
withAnimation(.smooth) { ... } // Gentle, no bounce
withAnimation(.bouncy) { ... } // More bounce
Common Transitions
// Basic
.transition(.opacity)
.transition(.scale)
.transition(.slide)
.transition(.move(edge: .bottom))
// Combined
.transition(.move(edge: .trailing).combined(with: .opacity))
// Asymmetric
.transition(.asymmetric(
insertion: .move(edge: .bottom),
removal: .opacity
))
Matched Geometry Effect
@Namespace var namespace
// Source view
ThumbnailView()
.matchedGeometryEffect(id: "hero", in: namespace)
// Destination view
DetailView()
.matchedGeometryEffect(id: "hero", in: namespace)
Metal Shader Effects (iOS 17+)
// Color manipulation
.colorEffect(ShaderLibrary.invert())
// Pixel displacement
.distortionEffect(
ShaderLibrary.wave(.float(time)),
maxSampleOffset: CGSize(width: 20, height: 20)
)
// Full layer access
.layerEffect(ShaderLibrary.blur(.float(radius)), maxSampleOffset: .zero)
Reference Materials
Detailed documentation is available in references/:
- •
motion-guidelines.md - HIG Motion design principles
- •Purpose-driven motion philosophy
- •Accessibility requirements
- •Platform-specific considerations (iOS, visionOS, watchOS)
- •Animation anti-patterns to avoid
- •
animations.md - Complete animation API guide
- •Implicit vs explicit animations
- •Spring parameters and presets
- •Animation modifiers (speed, delay, repeat)
- •PhaseAnimator for multi-step sequences
- •KeyframeAnimator for property-specific timelines
- •Custom animatable properties
- •
transitions.md - View transition guide
- •Built-in transitions (opacity, scale, slide, move)
- •Combined and asymmetric transitions
- •Matched geometry effect implementation
- •Hero animation patterns
- •Content transitions (iOS 17+)
- •Custom transition creation
- •
metal-shaders.md - GPU shader integration
- •SwiftUI shader modifiers (colorEffect, distortionEffect, layerEffect)
- •Writing Metal shader functions
- •Embedding MTKView with UIViewRepresentable
- •Cross-platform Metal integration (iOS/macOS)
- •Performance considerations
Common Patterns
Expandable Card
struct ExpandableCard: View {
@State private var isExpanded = false
var body: some View {
VStack {
RoundedRectangle(cornerRadius: isExpanded ? 20 : 12)
.fill(.blue)
.frame(
width: isExpanded ? 300 : 150,
height: isExpanded ? 400 : 100
)
}
.onTapGesture {
withAnimation(.spring(response: 0.35, dampingFraction: 0.75)) {
isExpanded.toggle()
}
}
}
}
List Item Appearance
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
ItemRow(item: item)
.transition(.asymmetric(
insertion: .move(edge: .trailing).combined(with: .opacity),
removal: .move(edge: .leading).combined(with: .opacity)
))
.animation(.spring().delay(Double(index) * 0.05), value: items)
}
Pulsing Indicator
Circle()
.fill(.blue)
.frame(width: 20, height: 20)
.scaleEffect(isPulsing ? 1.2 : 1.0)
.opacity(isPulsing ? 0.6 : 1.0)
.onAppear {
withAnimation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
isPulsing = true
}
}
Best Practices
- •Motion should be purposeful - Don't add animation for its own sake; support the experience without overshadowing it
- •Make motion optional - Supplement with haptics and audio; never use motion as the only way to communicate
- •Aim for brevity - Brief, precise animations feel lightweight and convey information effectively
- •Prefer explicit animations - Use
withAnimationover.animation()modifier for clarity - •Use spring animations - They feel more natural and iOS-native
- •Start with
.spring(response: 0.35, dampingFraction: 0.8)- Good default for most interactions - •Keep animations under 400ms - Longer feels sluggish
- •Let people cancel motion - Don't force users to wait for animations to complete
- •Test on device - Simulator animation timing differs
- •Profile shader performance - GPU time matters for complex effects
Troubleshooting
Animation not working
- •Ensure state change is wrapped in
withAnimation - •Check that the property is animatable
- •Verify the view is actually changing
Matched geometry jumps
- •Both views must use the same ID and namespace
- •Use explicit
withAnimationwhen toggling - •Check
zIndexfor proper layering
Shader not appearing
- •Verify
.metalfile is added to target - •Check shader function signature matches expected format
- •Ensure
maxSampleOffsetis set correctly for distortion effects
Technique Map
- •Explicit over implicit animation — withAnimation over .animation(); because intent is clearer; easier to reason about.
- •Spring presets (iOS 17+) — .snappy, .smooth, .bouncy; because HIG-aligned defaults.
- •Matched geometry for hero — @Namespace + matchedGeometryEffect(id:in:); because smooth shape morphing across views.
- •Metal shader modifiers — colorEffect, distortionEffect, layerEffect; because GPU-accelerated effects in SwiftUI.
- •Asymmetric transitions — insertion vs removal different; because list add/remove often needs different motion.
- •Under 400ms — Keep animations brief; because longer feels sluggish.
Technique Notes
References: motion-guidelines.md, animations.md, transitions.md, metal-shaders.md. Default spring: response 0.35, dampingFraction 0.75. Test on device; simulator timing differs. Profile shader performance.
Prompt Architect Overlay
Role Definition: SwiftUI animation expert. Springs, transitions, matched geometry, PhaseAnimator, KeyframeAnimator, Metal shader integration. Motion design principles, accessibility.
Input Contract: Accepts animation request, transition need, hero animation, Metal effect, or "animation not working." Code snippet or description.
Output Contract: Code pattern with appropriate modifier. Spring parameters or preset. Transition type. Reference to detailed doc. Best practices reminder. Troubleshooting if issue described.
Edge Cases & Fallbacks: If matched geometry jumps→check same ID/namespace, explicit withAnimation. If shader not appearing→verify .metal in target, signature, maxSampleOffset. If animation not working→ensure withAnimation, animatable property, view actually changing.