如果操作未立即完成,则 SwiftUI 视图在绑定到 @Published var 时没有动画
Posted
技术标签:
【中文标题】如果操作未立即完成,则 SwiftUI 视图在绑定到 @Published var 时没有动画【英文标题】:SwiftUI view not animating when bound to @Published var if action isn't completed immediately 【发布时间】:2020-03-10 11:23:11 【问题描述】:我有一个 SwiftUI 视图,它根据状态换出某些控件。我正在尝试使用 MVVM,所以我的大部分/所有逻辑都被推到了视图模型上。我发现,在执行修改视图模型上的@Published var
的复杂操作时,View
不会动画。
这是一个示例,其中视图模型中的 1.0 秒计时器模拟在更改 @Published var
值之前正在完成的其他工作:
struct ContentView: View
@State var showCircle = true
@ObservedObject var viewModel = ViewModel()
var body: some View
VStack
VStack
if showCircle
Circle().frame(width: 100, height: 100)
Button(action:
withAnimation
self.showCircle.toggle()
)
Text("With State Variable")
VStack
if viewModel.showCircle
Circle().frame(width: 100, height: 100)
Button(action:
withAnimation
self.viewModel.toggle()
)
Text("With ViewModel Observation")
class ViewModel: ObservableObject
@Published var showCircle = true
public func toggle()
// Do some amount of work here. The Time is just to simulate work being done that may not complete immediately.
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) [weak self] _ in
self?.showCircle.toggle()
【问题讨论】:
【参考方案1】:在视图模型工作流的情况下,您的 withAnimation
什么都不做,因为在这种情况下不会更改状态(它只是一个函数调用),只有计时器被安排,所以你宁愿需要它
Button(action:
self.viewModel.toggle() // removed from here
)
Text("With ViewModel Observation")
...
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) [weak self] _ in
withAnimation // << added here
self?.showCircle.toggle()
但是我更愿意建议重新考虑视图设计......就像
VStack
if showCircle2 // same declaration as showCircle
Circle().frame(width: 100, height: 100)
Button(action:
self.viewModel.toggle()
)
Text("With ViewModel Observation")
.onReceive(viewModel.$showCircle) value in
withAnimation
self.showCircle2 = value
使用 Xcode 11.2 / ios 13.2 测试
【讨论】:
我正在尝试构建视图,以便它能够很好地响应视图模型的变化。有没有更好的方法来实现这一点? @lepolt,见更新。动画是 View 的特性,所以最好保持在视图中。查看模型异步属性更新的情况,请参阅更新部分。 我不知道.onReceive...但我认为这可能有效...稍后会测试并报告! 这是我第一个使用 SwiftUI 的复杂应用程序,我认为我需要退后一步重新思考一些架构。.onReceive
有效,除了我在 .sheet
演示文稿中遇到另一个问题,它的绑定触发不止一次,但这是另一天的话题。【参考方案2】:
父视图以动画方式隐藏和显示其子视图。如果您在第一个 VStack 的末尾放置一个 .animation(.easeIn)
(或 .easeOut 或任何您喜欢的),它应该可以按预期工作。
这样……
struct ContentView: View
@State var showCircle = true
@ObservedObject var viewModel = ViewModel()
var body: some View
VStack
VStack
if showCircle
Circle().frame(width: 100, height: 100)
Button(action:
withAnimation
self.showCircle.toggle()
)
Text("With State Variable")
VStack
if viewModel.showCircle
Circle().frame(width: 100, height: 100)
Button(action:
withAnimation
self.viewModel.toggle()
)
Text("With ViewModel Observation")
.animation(.easeIn)
【讨论】:
这种方法的缺点是它会将动画应用于所有子视图和大子视图。它可能会解决 OP 提到的这个特定问题,但对于其他具有复杂子视图的一般用例可能不是最佳解决方案。【参考方案3】:您必须对动画使用延迟,而不是使用计时器来延迟您的动画。
Button(action:
withAnimation(Animation.linear.delay(1.0))
self.viewModel.showCircle.toggle()
)
Text("With ViewModel Observation")
否则,当您不使用 Timer 时,视图模型中的更改将按预期进行。
class ViewModel: ObservableObject
@Published var showCircle = true
public func toggle()
/* other operations */
self.showCircle.toggle()
【讨论】:
计时器只是为了模拟“正在进行但可能不会立即完成的工作”。我不能保证知道这项工作需要多长时间,因此添加 1.0 秒的延迟并不是一个好的解决方案。 @lepolt 在这种情况下,当 Timer 中的状态未更改时,您的视图模型将正常工作。顺便说一句,你投反对票了吗?以上是关于如果操作未立即完成,则 SwiftUI 视图在绑定到 @Published var 时没有动画的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI如何让绑定到同一个状态的多个TextField呈现出不同输入行为
SwiftUI如何让绑定到同一个状态的多个TextField呈现出不同输入行为
检查后在 SwiftUI 中将可选绑定设置为 nil 时出现异常