【问题标题】:How to make Swift Coordinator reflect parent's bindings changes?如何使 Swift Coordinator 反映父级的绑定更改?
【发布时间】:2020-05-31 12:36:41
【问题描述】:

假设我有:

  • 结构体Document,代表文本文档。
  • EditorView — 一个 NSTextView,用 Combine 包裹,绑定到 Document.content<String>

Document 是复杂store:ObservableObject 的一部分,因此它可以绑定到EditorView 实例。

当我第一次创建绑定时,它按预期工作——编辑 NSTextView 会更改 Document.content 中的值。

let document1 = Document(...)
let document2 = Document(...)
var editor = EditorView(doc: document1)

但是如果更改绑定到另一个文档...

editor.doc = document2

...然后updateNSView 可以看到新的document2。但是在 Coordiantor 的textDidChange 里面还是有对document1 的引用。

func textDidChange(_ notification: Notification) {
    guard let textView = notification.object as? NSTextView else {
        return
    }

    self.parent.doc.content = textView.string
    self.selectedRanges = textView.selectedRanges
}

所以,最初,当我设置新的 bindint 时,NSTextView 将其内容更改为 document2,但当我键入时,协调器将更改发送到 document1

Coordiantor 是否保留了自己的 parent 副本,即使父级更改(@Binding doc 已更新),它仍然引用旧的?

如何让 Coordinator 反映父级绑定的变化?

谢谢!

struct Document: Identifiable, Equatable {
    let id: UUID = UUID()
    var name: String
    var content: String
}

struct EditorView: NSViewRepresentable {
    @Binding var doc: Document

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeNSView(context: Context) -> CustomTextView {
        let textView = CustomTextView(
            text: doc.content,
            isEditable: isEditable,
            font: font
        )
        textView.delegate = context.coordinator

        return textView
    }

    func updateNSView(_ view: CustomTextView, context: Context) {
        view.text = doc.content
        view.selectedRanges = context.coordinator.selectedRanges

    }
}


// MARK: - Coordinator

extension EditorView {

    class Coordinator: NSObject, NSTextViewDelegate {
        var parent: EditorView
        var selectedRanges: [NSValue] = []

        init(_ parent: EditorView) {
            self.parent = parent
        }

        func textDidBeginEditing(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else {
                return
            }

            self.parent.doc.content = textView.string
            self.parent.onEditingChanged()
        }

        func textDidChange(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else {
                return
            }

            self.parent.doc.content = textView.string
            self.selectedRanges = textView.selectedRanges
        }

        func textDidEndEditing(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else {
                return
            }

            self.parent.doc.content = textView.string
            self.parent.onCommit()
        }
    }
}

// MARK: - CustomTextView

final class CustomTextView: NSView {
    private var isEditable: Bool
    private var font: NSFont?

    weak var delegate: NSTextViewDelegate?

    var text: String {
        didSet {
            textView.string = text
        }
    }
    // ...

【问题讨论】:

    标签: swift swiftui appkit combine


    【解决方案1】:

    是真的吗,Coordiantor 保留了它自己的 parent 副本,甚至 如果父级更改(@Binding 文档已更新),它仍然引用 旧的?

    父类,即这里的EditorView,是struct,所以答案是肯定的,一般情况下是可以复制的。

    如何让 Coordinator 反映父级的绑定变化?

    代替(或附加到)父级,显式地(通过构造函数)注入到协调器的绑定并直接使用它。

    【讨论】:

    • 谢谢!我不太确定,如何做到这一点属性:Swift 新手。您能否看一下,如何正确地将数据分配给绑定值? Swift 告诉我绑定是不可变的结构,所以它的值不能直接赋值。
    • @f1nn,请参阅this answer,例如将绑定传递给协调器并使用它。
    • @Asperi - 我发现这不起作用 - 我有同样的问题,并且正在使用与上面的“这个答案”相同的方法。但是,绑定更改并未反映,文本字段中的编辑仍会发送到原始对象。我有一个主细节安排,任何编辑总是发送到第一个选择的文档,因为当从列表中选择一个新文档时,协调员似乎永远不会更改绑定。
    • @DuncanGroenewald,请参见此处的示例stackoverflow.com/a/63761738/12299030
    • @Asperi - 谢谢,但说什么!!在该示例中,您为什么要从头开始构建整个堆栈?使用上述方法有问题吗?
    猜你喜欢
    • 2020-10-12
    • 1970-01-01
    • 1970-01-01
    • 2010-12-10
    • 2013-07-07
    • 1970-01-01
    • 2020-08-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多