为什么在通过计算绑定进行操作时,视图的内部状态在第一次变更时没有被更新?

移动开发 2026-07-09

附上演示代码:

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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章