【问题标题】:Bridging Optional Binding to Non-Optional Child (SwiftUI)将可选绑定桥接到非可选子项 (SwiftUI)
【发布时间】:2021-12-09 09:00:00
【问题描述】:

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

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()
    }
}

【问题讨论】:

  • 如果我可能遗漏了一些关于正确的 SwiftUI 架构的东西,我也会感谢您提供指向涵盖此内容的文档或架构的指针

标签: swift swiftui combine


【解决方案1】:

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&lt;V?&gt; 转换为Binding&lt;V&gt;?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 = $0 }
                ))
            }

【讨论】:

  • 谢谢,rob,要奖励这个,因为它有原始版本,如果让大部分维护
  • heyoo,如果稍后将绑定设置为nil,这将导致崩溃,出于某种原因需要包装绑定,例如这里的某件事:stackoverflow.com/questions/63958106/…
  • 我添加了一个你可以尝试的解决方法。
  • 哦,是的,我的意思是告诉你这是解决方法!
  • 嘿,只是在这里评论说,这有一些奇怪的怪癖,用户要小心。我目前的理论是,当我们以这种方式实例化时,在 swiftui 上下文之外,它不再绑定到视图并创建多个绑定实例。绑定正确地指向model.name,但某些视图可能会与绑定链断开连接并且无法接收更新,这有时会表现为奇怪的行为。
【解决方案2】:

像这样绑定你的变量。使用自定义绑定并让您的孩子查看 var @Binding

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

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

【讨论】:

  • Swift 有更好的机制来处理解包选项:if let.
  • 嘿,请注意,如果在其他地方将该值设置为 nil,这将导致崩溃。您对 get {} set 的原始答案是正确的 stackoverflow.com/questions/63958106/…
  • Xcode 12.5.1 不会崩溃。
猜你喜欢
  • 2021-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
  • 2020-04-03
  • 1970-01-01
相关资源
最近更新 更多