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

Log*_*gan 5 swift swiftui combine

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

class Model: ObservableObject {
    @Published var name: String? = nil
}
Run Code Online (Sandbox Code Playgroud)

如果该状态存在,我想显示一个子视图。在此示例中,显示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()
    }
}
Run Code Online (Sandbox Code Playgroud)

rob*_*off 18

Child应该将 aBinding设为非可选字符串,而不是使用@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)
    }
}
Run Code Online (Sandbox Code Playgroud)

Binding一个将 a 转换为 的初始值设定项Binding<V?>,您Binding<V>?可以像这样使用它:

            if let binding = Binding<String>($model.name) {
                Child(text: binding)
            }
Run Code Online (Sandbox Code Playgroud)

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

            if let text = model.name {
                Child(text: Binding(
                    get: { model.name ?? text },
                    set: { model.name = $0 }
                ))
            }
Run Code Online (Sandbox Code Playgroud)