如果使用 的onAppear
方法在 的帮助下滚动到 SwiftUI 视图ScrollViewProxy
,则在使用 的已发布值时,这可能会导致视图中的状态转换丢失ObservableObject
。
该问题出现在所有当前的 iOS SDK(17.5 / 18.0)、iOS 模拟器和真实设备上。
以下是为重现该问题而精简的代码。
SwiftUI 视图:
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
@Namespace private var state2ID
var body: some View {
ScrollViewReader { scrollProxy in
ScrollView(.vertical) {
VStack(spacing: 15) {
if viewModel.state2 {
VStack {
Text("State2 is set")
}
.id(state2ID)
.onAppear {
withAnimation {
scrollProxy.scrollTo(state2ID)
}
}
}
VStack(spacing: 0) {
Text("State1: \(viewModel.state1)")
Text("State1 changes from 'false -> true -> false' when the button is pressed.")
.font(.footnote)
}
Button("Toggle States") {
viewModel.toggleStates()
}
.buttonStyle(.bordered)
Color.teal
.frame(height: 900)
}
.padding()
}
}
}
}
查看模型:
@MainActor
final class ContentViewModel: ObservableObject {
@Published private(set) var state1 = false
@Published private(set) var state2 = false
private var stateToggle = false
func toggleStates() {
Task { @MainActor in
state1 = true
defer {
// This change never becomes visible in the view!
// state1 will be improperly shown as 'true' when this method returns while it actually is 'false'.
print("Resetting state1")
state1 = false
}
stateToggle.toggle()
if stateToggle {
withAnimation {
state2 = true
}
} else {
state2 = false
}
}
}
}
state1
当你按下“切换状态”按钮时,任务完成时的文本显示“State1: true”,但它应该显示“State1: false”,因为在 的任务中state1
设置为。此时,视图模型的实际值和视图中显示的值不再同步。false
toggleStates
如果删除方法scrollProxy.scrollTo(state2ID)
中的调用onAppear
,则问题不会发生。因此,它似乎是由滚动过程触发的。
这似乎非常出乎意料,是一个错误,还是我遗漏了一些基本的东西?你知道为什么会出现这种情况吗?如何使用对象scrollTo
的方法ScrollViewProxy
以避免发生此错误?
该问题可能是与视图模型执行更改相关的时间问题
withAnimation
。我建议,如果视图模型不关心动画等演示细节会更好。
无论如何,您都可以通过将动画控件从视图模型中取出并将其放入视图中来使其工作:
1.在 中
ContentViewModel
,注释掉withAnimation
当然,你可以在此过程中简化代码:
...或者您可以放弃它
stateToggle
,直接切换state2
。这可能有助于使视图模型不那么复杂。2.在 中,向内的顶层
ContentView
添加修饰符.animation
VStack
ScrollView
这会在显示和隐藏时提供滑动动画。如果您想抑制隐藏动画(就像以前一样),那么您可以在修饰符中使用三元运算符
.animation
: