如何在@Observable属性变更时实现SwiftUI动画并重构逻辑
重构方案:将动画逻辑移至SwiftUI视图内
核心思路是把耗时任务执行和UI状态切换+动画触发分离,让MyData只负责处理耗时操作,视图层控制动画的启动时机,同时保留@Observable API的使用。
步骤1:修改MyData,剥离状态修改逻辑
让doSomethingExpensive仅专注于耗时任务,不再直接修改isCircle:
import SwiftUI @Observable final class MyData { var isCircle = false // 仅执行耗时操作,不修改UI状态 func doSomethingExpensive() async throws { try await Task.sleep(for: .milliseconds(Int.random(in: 300...800))) } }
步骤2:在视图内触发动画
在ContentView中,点击按钮后先等待耗时任务完成,再通过withAnimation包裹状态切换,将动画逻辑完全移至视图层:
struct ContentView: View { @State private var myData = MyData() var body: some View { VStack { // 根据状态展示不同形状 if myData.isCircle { Circle().fill(.blue).frame(width: 200) } else { Rectangle().fill(.red).frame(width: 200, height: 200) } Button("Animate later") { Task { // 等待耗时任务完成 try await myData.doSomethingExpensive() // 在视图内触发动画并切换状态 withAnimation { myData.isCircle.toggle() } } } } } }
另一种可选方案:保留MyData的状态管理,用@Bindable绑定
如果需要MyData仍持有状态,但动画由视图控制,可以使用@Bindable包装@Observable实例:
struct ContentView: View { private let myData = MyData() @Bindable private var bindableData: MyData init() { self.bindableData = myData } var body: some View { VStack { if bindableData.isCircle { Circle().fill(.blue).frame(width: 200) } else { Rectangle().fill(.red).frame(width: 200, height: 200) } Button("Animate later") { Task { try await myData.doSomethingExpensive() withAnimation { bindableData.isCircle.toggle() } } } } } }
两种方案都能实现原有的动画效果,且完全符合将动画逻辑移至视图内、使用@Observable API的要求。
内容的提问来源于stack exchange,提问作者Lou Zell
相关产品推荐
相关产品推荐

