将可选绑定桥接到非可选子项 (SwiftUI)

Bridging Optional Binding to Non-Optional Child (SwiftUI)

我有一个 可能 存在的父状态:

class Model: ObservableObject {
    @Published var name: String? = nil
}

如果那个状态存在,我想显示一个子视图。在此示例中,显示 name.

如果 name 可见,我希望它可以显示和编辑。我希望这是双向可编辑的,这意味着如果 Model.name 发生变化,我希望它推送到 ChildUI,如果 ChildUI 编辑它,我希望它反映回 Model.name.

但是,如果Model.name变成nil,我想ChildUI隐藏。

当我这样做时,通过解包 Model.name,然后只有第一个值被现在控制该状态的 Child 捕获。随后的更改不会向上游推送,因为它不是 Binding.

问题

当可选的存在时,我可以将非可选的上游绑定到可选的吗? (这些词对吗?)

完整示例

import SwiftUI

struct Child: View {
    // within Child, I'd like the value to be NonOptional
    @State var text: String
    
    var body: some View {
        TextField("OK: ", text: $text).multilineTextAlignment(.center)
    }
}

class Model: ObservableObject {
    // within the parent, value is Optional
    @Published var name: String? = nil
}

struct Parent: View {
    @ObservedObject var model: Model = .init()
    
    var body: some View {
        VStack(spacing: 12) {
            Text("Demo..")

            // whatever Child loads the first time will retain
            // even on change of model.name
            if let text = model.name {
                Child(text: text)
            }
            
            // proof that model.name changes are in fact updating other state
            Text("\(model.name ?? "<waiting>")")
        }
        .onAppear {
            model.name = "first change of optionality works"
            loop()
        }
    }
    
    @State var count = 0
    func loop() {
        async(after: 1) {
            count += 1
            model.name = "updated: \(count)"
            loop()
        }
    }
}

func async(_ queue: DispatchQueue = .main,
           after: TimeInterval,
           run work: @escaping () -> Void) {
    queue.asyncAfter(deadline: .now() + after, execute: work)
}

struct OptionalEditingPreview: PreviewProvider {
    static var previews: some View {
        Parent()
    }
}

Child 应该对非可选字符串使用 Binding,而不是使用 @State,因为您希望它与其父级共享状态:

struct Child: View {
    // within Child, I'd like the value to be NonOptional
    @Binding var text: String

    var body: some View {
        TextField("OK: ", text: $text).multilineTextAlignment(.center)
    }
}

Binding 具有将 Binding<V?> 转换为 Binding<V>?an initializer,您可以像这样使用它:

            if let binding = Binding<String>($model.name) {
                Child(text: binding)
            }

如果您因此而崩溃,那是 SwiftUI 中的错误,但您可以像这样解决它:

            if let text = model.name {
                Child(text: Binding(
                    get: { model.name ?? text },
                    set: { model.name = [=12=] }
                ))
            }

像这样绑定你的变量。使用自定义绑定并使您的 child 视图变量 @Binding.

struct Child: View {
    @Binding var text: String //<-== Here
   // Other Code 

if model.name != nil {
   Child(text: Binding($model.name)!)
}