名称: swiftui-animation 描述: 本技能提供在 SwiftUI 中实现高级动画、过渡、匹配几何效果和 Metal 着色器集成的全面指导。用于构建 iOS 和 macOS 应用中的动画、视图过渡、英雄动画或 GPU 加速效果。
SwiftUI 动画专家
为实施高级 SwiftUI 动画和 Metal 着色器集成的专家指导。涵盖动画曲线、弹簧效果、过渡、匹配几何效果、PhaseAnimator、KeyframeAnimator 和 GPU 加速着色器效果。
何时使用此技能
- 理解运动设计原则以及何时使用动画
- 使动画可访问且适合平台
- 在 SwiftUI 中实现动画(弹簧、缓动、关键帧)
- 创建视图过渡(淡入淡出、滑动、缩放、自定义)
- 使用 matchedGeometryEffect 构建英雄动画
- 通过 Metal 着色器添加 GPU 加速效果
- 优化动画性能
- 创建多阶段编排动画
快速参考
动画基础
// 显式动画(推荐)
withAnimation(.spring(response: 0.4, dampingFraction: 0.75)) {
isExpanded.toggle()
}
// iOS 17+ 弹簧预设
withAnimation(.snappy) { ... } // 快速,轻微弹跳
withAnimation(.smooth) { ... } // 温和,无弹跳
withAnimation(.bouncy) { ... } // 更多弹跳
常见过渡
// 基本
.transition(.opacity)
.transition(.scale)
.transition(.slide)
.transition(.move(edge: .bottom))
// 组合
.transition(.move(edge: .trailing).combined(with: .opacity))
// 非对称
.transition(.asymmetric(
insertion: .move(edge: .bottom),
removal: .opacity
))
匹配几何效果
@Namespace var namespace
// 源视图
ThumbnailView()
.matchedGeometryEffect(id: "hero", in: namespace)
// 目标视图
DetailView()
.matchedGeometryEffect(id: "hero", in: namespace)
Metal 着色器效果 (iOS 17+)
// 颜色操作
.colorEffect(ShaderLibrary.invert())
// 像素位移
.distortionEffect(
ShaderLibrary.wave(.float(time)),
maxSampleOffset: CGSize(width: 20, height: 20)
)
// 完整层访问
.layerEffect(ShaderLibrary.blur(.float(radius)), maxSampleOffset: .zero)
参考材料
详细文档可在 references/ 中找到:
-
motion-guidelines.md - HIG 运动设计原则
- 目的驱动的运动哲学
- 可访问性要求
- 平台特定考虑(iOS、visionOS、watchOS)
- 应避免的动画反模式
-
animations.md - 完整的动画 API 指南
- 隐式与显式动画
- 弹簧参数和预设
- 动画修饰符(速度、延迟、重复)
- PhaseAnimator 用于多步序列
- KeyframeAnimator 用于属性特定时间线
- 自定义可动画属性
-
transitions.md - 视图过渡指南
- 内置过渡(透明度、缩放、滑动、移动)
- 组合和非对称过渡
- 匹配几何效果实施
- 英雄动画模式
- 内容过渡 (iOS 17+)
- 自定义过渡创建
-
metal-shaders.md - GPU 着色器集成
- SwiftUI 着色器修饰符 (colorEffect, distortionEffect, layerEffect)
- 编写 Metal 着色器函数
- 使用 UIViewRepresentable 嵌入 MTKView
- 跨平台 Metal 集成(iOS/macOS)
- 性能考虑
常见模式
可扩展卡片
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()
}
}
}
}
列表项出现
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)
}
脉动指示器
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
}
}
最佳实践
- 运动应有目的 - 不要为动画本身添加动画;支持体验而不掩盖它
- 使运动可选 - 补充触觉和音频;切勿将运动作为唯一的交流方式
- 力求简洁 - 简短、精确的动画感觉轻盈并有效传达信息
- 偏好显式动画 - 使用
withAnimation而非.animation()修饰符以提高清晰度 - 使用弹簧动画 - 它们感觉更自然且原生 iOS
- 从
.spring(response: 0.35, dampingFraction: 0.8)开始 - 大多数交互的良好默认值 - 保持动画在 400 毫秒以下 - 更长感觉迟钝
- 让人们取消运动 - 不要强迫用户等待动画完成
- 在设备上测试 - 模拟器动画时间不同
- 分析着色器性能 - 对于复杂效果,GPU 时间很重要
故障排除
动画不工作
- 确保状态变化包装在
withAnimation中 - 检查属性是否可动画
- 验证视图是否实际变化
匹配几何跳跃
- 两个视图必须使用相同的 ID 和命名空间
- 切换时使用显式
withAnimation - 检查
zIndex以正确分层
着色器不显示
- 验证
.metal文件已添加到目标 - 检查着色器函数签名是否符合预期格式
- 确保为失真效果正确设置
maxSampleOffset