swiftui-animation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SwiftUI Animation Expert

SwiftUI 动画专家

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.
为实现高级SwiftUI动画和Metal着色器集成提供专业指导。涵盖动画曲线、弹簧效果、转场、匹配几何效果、PhaseAnimator、KeyframeAnimator以及GPU加速着色器效果。

When to Use This Skill

何时使用该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
  • 理解动效设计原则以及何时使用动画
  • 制作可访问且符合平台规范的动画
  • 在SwiftUI中实现动画(弹簧、缓动、关键帧)
  • 创建视图转场(淡入淡出、滑动、缩放、自定义)
  • 使用matchedGeometryEffect构建Hero动画
  • 通过Metal着色器添加GPU加速效果
  • 优化动画性能
  • 创建多阶段编排动画

Quick Reference

快速参考

Animation Basics

动画基础

swift
// 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
swift
// 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

常见转场

swift
// 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
))
swift
// 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

匹配几何效果

swift
@Namespace var namespace

// Source view
ThumbnailView()
    .matchedGeometryEffect(id: "hero", in: namespace)

// Destination view
DetailView()
    .matchedGeometryEffect(id: "hero", in: namespace)
swift
@Namespace var namespace

// Source view
ThumbnailView()
    .matchedGeometryEffect(id: "hero", in: namespace)

// Destination view
DetailView()
    .matchedGeometryEffect(id: "hero", in: namespace)

Metal Shader Effects (iOS 17+)

Metal着色器效果(iOS 17+)

swift
// 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)
swift
// 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
详细文档可在
references/
目录中获取:
  • motion-guidelines.md - HIG动效设计原则
    • 目标导向的动效理念
    • 可访问性要求
    • 平台特定考量(iOS、visionOS、watchOS)
    • 需要避免的动画反模式
  • animations.md - 完整动画API指南
    • 隐式与显式动画
    • 弹簧参数与预设
    • 动画修饰符(速度、延迟、重复)
    • 用于多步骤序列的PhaseAnimator
    • 用于属性特定时间线的KeyframeAnimator
    • 自定义可动画属性
  • transitions.md - 视图转场指南
    • 内置转场(淡入淡出、缩放、滑动、移动)
    • 组合与非对称转场
    • 匹配几何效果实现
    • Hero动画模式
    • 内容转场(iOS 17+)
    • 自定义转场创建
  • metal-shaders.md - GPU着色器集成
    • SwiftUI着色器修饰符(colorEffect、distortionEffect、layerEffect)
    • 编写Metal着色器函数
    • 使用UIViewRepresentable嵌入MTKView
    • 跨平台Metal集成(iOS/macOS)
    • 性能考量

Common Patterns

常见模式

Expandable Card

可展开卡片

swift
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()
            }
        }
    }
}
swift
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

列表项出现效果

swift
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)
}
swift
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

脉冲指示器

swift
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
        }
    }
swift
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

最佳实践

  1. Motion should be purposeful - Don't add animation for its own sake; support the experience without overshadowing it
  2. Make motion optional - Supplement with haptics and audio; never use motion as the only way to communicate
  3. Aim for brevity - Brief, precise animations feel lightweight and convey information effectively
  4. Prefer explicit animations - Use
    withAnimation
    over
    .animation()
    modifier for clarity
  5. Use spring animations - They feel more natural and iOS-native
  6. Start with
    .spring(response: 0.35, dampingFraction: 0.8)
    - Good default for most interactions
  7. Keep animations under 400ms - Longer feels sluggish
  8. Let people cancel motion - Don't force users to wait for animations to complete
  9. Test on device - Simulator animation timing differs
  10. Profile shader performance - GPU time matters for complex effects
  1. 动效应具备目的性 - 不要为了动画而添加动画;应辅助体验而非喧宾夺主
  2. 让动效可选项 - 配合触觉反馈和音频;永远不要将动效作为唯一的信息传递方式
  3. 力求简洁 - 简短、精准的动画感觉轻盈且能有效传达信息
  4. 优先使用显式动画 - 为了清晰性,优先使用
    withAnimation
    而非
    .animation()
    修饰符
  5. 使用弹簧动画 - 它们感觉更自然,更符合iOS原生风格
  6. .spring(response: 0.35, dampingFraction: 0.8)
    开始
    - 这是大多数交互的良好默认值
  7. 保持动画时长在400ms以内 - 更长的动画会让人感觉迟缓
  8. 允许用户取消动效 - 不要强迫用户等待动画完成
  9. 在设备上测试 - 模拟器的动画计时与实际设备不同
  10. 剖析着色器性能 - 对于复杂效果,GPU耗时至关重要

Troubleshooting

故障排除

Animation not working

动画不生效

  • Ensure state change is wrapped in
    withAnimation
  • Check that the property is animatable
  • Verify the view is actually changing
  • 确保状态变化被包裹在
    withAnimation
  • 检查属性是否支持动画
  • 验证视图是否真的发生了变化

Matched geometry jumps

匹配几何效果出现跳跃

  • Both views must use the same ID and namespace
  • Use explicit
    withAnimation
    when toggling
  • Check
    zIndex
    for proper layering
  • 两个视图必须使用相同的ID和命名空间
  • 切换时使用显式的
    withAnimation
  • 检查
    zIndex
    以确保正确的层级

Shader not appearing

着色器不显示

  • Verify
    .metal
    file is added to target
  • Check shader function signature matches expected format
  • Ensure
    maxSampleOffset
    is set correctly for distortion effects
  • 确认
    .metal
    文件已添加到目标中
  • 检查着色器函数签名是否与预期格式匹配
  • 确保为扭曲效果正确设置了
    maxSampleOffset