SwiftUI:如何实现符合加载要求的旋转刷新按钮动画
实现符合需求的刷新按钮正确方案
需求回顾
- 数据加载期间按钮持续旋转
- 至少完成一整圈旋转
- 加载完成时必须完成当前旋转周期,不能中途停止或回退
当前方案的问题
- 依赖日期计算同步动画,精度差
- 时间延迟计算不可靠,易出现动画回退
- 手动控制动画状态逻辑复杂,维护成本高
正确实现方案
利用SwiftUI的动画系统,通过角度状态而非时间计算来控制旋转,确保动画行为符合需求。以下是完整实现代码:
struct RefreshButton: View { @State private var isLoading = false @State private var rotationAngle = 0.0 var body: some View { Button(action: triggerRefresh) { Image(systemName: "arrow.triangle.2.circlepath") .rotationEffect(.degrees(rotationAngle)) } .onChange(of: isLoading) { newValue in newValue ? startRotationAnimation() : finishRotationAnimation() } } private func triggerRefresh() { guard !isLoading else { return } isLoading = true // 替换为实际异步加载逻辑 Task { try? await Task.sleep(nanoseconds: 2_500_000_000) // 模拟2.5秒加载过程 await MainActor.run { isLoading = false } } } private func startRotationAnimation() { withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) { rotationAngle += 360 } } private func finishRotationAnimation() { // 计算目标角度:确保至少完成一圈,且完成当前正在进行的旋转周期 let completedFullRotations = floor(rotationAngle / 360) let targetAngle = max(completedFullRotations + 1, ceil(rotationAngle / 360)) * 360 // 根据剩余角度计算所需动画时长,保持1秒/圈的速度 let remainingDuration = (targetAngle - rotationAngle) / 360 withAnimation(.linear(duration: remainingDuration)) { rotationAngle = targetAngle } } }
关键逻辑说明
- 旋转状态管理:用
rotationAngle记录总旋转角度,避免手动计算时间差,完全基于动画系统的状态驱动 - 启动动画:加载开始时,通过
repeatForever的线性动画持续累加角度,实现无限旋转效果 - 结束动画:加载完成时,计算需要到达的目标角度:
- 如果加载时间不足一圈,强制完成至少一整圈
- 如果已完成多圈,确保完成当前正在进行的旋转周期
- 根据剩余角度计算动画时长,保持匀速旋转直到目标角度,避免中途停止或回退
这个方案完全依赖SwiftUI的原生动画系统,无需手动同步时间,可靠性和精度更高,逻辑也更简洁易维护。
内容的提问来源于stack exchange,提问作者fer0n
相关产品推荐
相关产品推荐

