我在 SwiftUI 中遇到一个问题,其中绑定的 @State 变量 (displayString) 在呈现工作表时似乎没有及时更新。提供的代码由父视图(SimpleParentView)和子视图(SimpleChildView)组成。当 showSheet 设置为 true 时,父视图呈现一个工作表,并且该工作表显示 displayString 值。
第一次按下 SimpleChildView 中的“按钮 1”或“按钮 2”时,就会出现问题。当按下时,它会更新 displayString,然后通过将 showSheet 设置为 true 来显示工作表。但是,当第一次按下按钮时显示工作表时,它仍然显示“初始字符串”,表明显示字符串尚未及时更新以显示工作表。
有趣的是,如果用户关闭工作表,然后再次按下相反的按钮或相同的按钮,工作表会正确显示更新后的显示字符串,并且任何后续按钮按下也会按预期工作。
我尝试使用 DispatchQueue.main.asyncAfter 延迟将 showSheet 设置为 true,希望这能为 displayString 提供足够的时间来更新,但这种方法似乎没有任何区别。
我也厌倦了通过首先分配一个废弃值来“启动”分配,希望这能解决最初神秘的错误分配,但这也不起作用。
为什么会出现这种情况?
import Foundation
import SwiftUI
struct SimpleParentView: View {
@State private var showSheet = false
@State private var displayString = "Initial String"
var body: some View {
VStack {
SimpleChildView(showSheet: $showSheet, displayString: $displayString)
}
.sheet(isPresented: $showSheet) {
VStack {
Text(displayString)
Button("Close") {
showSheet = false
}
.padding()
}
}
}
}
struct SimpleChildView: View {
@Binding var showSheet: Bool
@Binding var displayString: String
var body: some View {
Button(action: {
// This "priming the assignment" hack makes no difference
// displayString = ""
displayString = "Updated String 1"
showSheet = true
// This delayed assignment hack makes no difference
/*
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
showSheet = true
}*/
}) {
Text("Button 1 (show sheet in parent)")
}
Button(action: {
displayString = "Updated String 2"
showSheet = true
}) {
Text("Button 2 (show sheet in parent)")
}
}
}
struct SimpleParentView_Previews: PreviewProvider {
static var previews: some View {
SimpleParentView()
}
}
这似乎不是故意的,可能是 SwiftUI 中的一个错误。
看来 SwiftUI 制作了工作表的初始版本,并表明当
showSheet
变为 true 时,没有注意到工作表的内容使用 displayString
,因此应该重新绘制。 ContentView.body
仅评估一次,直到您按下不同的按钮。
ContentView
的 body
(不包括纸张)不使用 displayString
- 仅使用它的装订。这可能导致 SwiftUI 认为它不需要更新。但这只是猜测。
无论如何,如果你让
ContentView
对displayString
有最轻微的依赖,这种行为就不会发生。这可能是一个隐藏的Text
:
Text(displayString).hidden()
甚至只是一个
onChange
修饰符:
VStack {
...
}
.onChange(of: displayString) { _ in }
这是我最喜欢的 - 即使只是将
displayString
与 let _ =
一起丢弃也有效:
VStack {
let _ = displayString
SimpleChildView(showSheet: $showSheet, displayString: $displayString)
.sheet(...) { ... }
}