为什么在通过计算绑定进行操作时,视图的内部状态在第一次变更时没有被更新?
附上演示代码:
import SwiftUI
struct BufferTest: View {
@Binding var text: String
@State private var buffer: String = ""
@State private var presentModificationSheet: Bool = false
var bufferedBinding: Binding<String> {
Binding {
text
} set: {
buffer = $0
if text != buffer {
presentModificationSheet = true
}
}
}
var body: some View {
Text("Binding: \(text)")
// Text("Buffer: \(buffer)")
InternalModder(binding: bufferedBinding)
.sheet(isPresented: $presentModificationSheet) {
TerminalSheet(text: $text, buffer: buffer)
}
}
struct InternalModder: View {
@Binding var binding: String
var body: some View {
Button("Set") { binding = "New Value" + (1...10).randomElement()!.formatted() }
}
}
struct TerminalSheet: View {
@Binding var text: String
var buffer: String
var body: some View {
Text("Binding: \(text)")
Text("Buffer: \(buffer)")
Button("Update") { text = buffer }
}
}
}
@available(iOS 17.0, *)
#Preview {
@Previewable @State var text = ""
BufferTest(text: $text)
}
在这里,首次按下按钮“Set”时,sheet会被展示,但尚未包含更新后的缓冲值。不过之后的变更会正确地发生。
如果我们取消注释
Text("Buffer: \(buffer)")
在BufferTest的主体中,更新将正确发生。
这是为什么?如果这是预期的行为,这样的变更是不是不推荐?
此外,解决问题的直接方法是通过onChange修饰符:
.onChange(of: buffer) { new in
if new != text {
presentModificationSheet = true
}
}
解决方案
另一种变通办法是通过捕获列表,例如
.sheet(isPresented: $presentModificationSheet) { [buffer] in
缺点是在任何导致主体被调用的状态变更时,sheet的闭包都会重新创建。因此你可能希望保持使用 .sheet 的主体尽量小(例如放在一个子视图中)。
另一种方法是计算 isPresented 绑定,使其在getter中使用缓冲区,然而这有点不太标准。
第三种方法,可能也是最好的方法,是在sheet的闭包中向子视图传递一个绑定。当它的主体被调用时,它将获取最新的缓冲区值,因为这发生在sheet闭包被调用之后。这个选项可能是最有效的,因为现在父视图的主体在sheet使用的数据变化时不需要被不必要地重新计算。
我会避免你尝试的 .onChange,因为它是为外部动作设计的,不是用来把状态连接在一起的,这与你SwiftUI的单一信息源真相设计相悖。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。