SwiftUI中ViewModel的@Published属性初始变更无法触发动画求解
问题分析
首次从isLoading状态切换到显示CircularProgressView时,进度动画未触发,但后续progress变更能正常触发动画,核心原因是:
- 首次创建
CircularProgressView时,视图是全新初始化渲染的,此时progress从ViewModel的初始值0直接跳转为Timer设置的随机值,SwiftUI默认不会对视图的初始渲染状态应用动画(无前置状态可过渡)。 - 后续
progress变更都是在视图已存在的前提下发生的,状态变化的前后对比清晰,动画能正常触发。
解决方案
方案一:修正动画触发的依赖值(最简洁)
修改GaugeProgressStyle,将动画的触发值从外部传入的progress改为ProgressView自身的fractionCompleted,这样首次渲染时,进度从0过渡到目标值的变化会被动画捕捉:
struct GaugeProgressStyle: ProgressViewStyle { func makeBody(configuration: Configuration) -> some View { let fractionCompleted = configuration.fractionCompleted ?? 0 return ZStack { Circle() .trim(from: 0, to: fractionCompleted) .stroke(Color.blue, style: StrokeStyle(lineWidth: 10, lineCap: .round)) .rotationEffect(.degrees(-90)) .animation(.easeOut.speed(0.35), value: fractionCompleted) } } }
同时简化CircularProgressView中样式的调用:
ProgressView(value: progress, total: 1.0) .progressViewStyle(GaugeProgressStyle()) // 移除多余的progress参数
方案二:显式包裹状态变更的动画
在ViewModel的Timer闭包里,用withAnimation显式包裹状态变更,强制SwiftUI为首次状态切换应用动画:
@MainActor class ViewModel: ObservableObject { @Published var progress: Double = 0.00 @Published var isLoading: Bool = true init() { Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in withAnimation(.easeOut.speed(0.35)) { self.progress = Double.random(in: 0...1) self.isLoading = false } } } }
方案三:手动触发初始动画过渡
在CircularProgressView中添加一个临时状态变量,通过onAppear手动触发从0到目标进度的动画过渡:
struct CircularProgressView: View { let progress: Double @State private var displayedProgress: Double = 0.0 var body: some View { ZStack { Circle() .stroke(Color.gray.opacity(0.2), style: StrokeStyle(lineWidth: 10)) ProgressView(value: displayedProgress, total: 1.0) .progressViewStyle(GaugeProgressStyle(progress: displayedProgress)) .contentShape(Rectangle()) Text("\(Int(displayedProgress*100.rounded()))") } .padding() .onAppear { withAnimation(.easeOut.speed(0.35)) { displayedProgress = progress } } .onChange(of: progress) { newValue in withAnimation(.easeOut.speed(0.35)) { displayedProgress = newValue } } } }
内容的提问来源于stack exchange,提问作者stompy
相关产品推荐
相关产品推荐

