首页 > 解决方案 > 项目重新排序后,SwiftUI 绑定编辑错误的文本字段

问题描述

Xcode 13 测试版 5、iOS 14、macOS 11.6

我有一个列出一些孩子的父 SwiftUI 视图。每个孩子都绑定到一个NSViewRepresentable. 一切正常,我可以按预期编辑值。但是,一旦我对列表中的项目重新排序并编辑了一个字段,它就会编辑错误的字段。看起来绑定与之前的项目订单保持不变。

看起来是这样的:

在此处输入图像描述

这是父母:

struct ParentView: View {
  @StateObject var model = ThingModel.shared
  
  var body: some View {
    VStack{
      ForEach($model.things){ $thing in
        ChildView(thing: $thing)
        //Reorder
        .onDrag{
          model.draggedThing = thing
          return NSItemProvider(object: NSString())
        }
      }
      Text("Value: \(model.value)").font(.title)
    }
    .frame(width:300, height: 200)
  }
}

...这是子视图:

struct ChildView: View {
  @Binding var thing: Thing
  @StateObject var model = ThingModel.shared
  
  var body: some View{
    HStack{
      GrowingField(text: $thing.text, submit: {
        model.value = thing.text
        print(thing.text)
      })
      Text(" = ")
        .opacity(0.4)
    }
    .padding(10)
    .onDrop(of: [UTType.text], delegate: ThingReorderDelegate(hoveredThing: thing))
  }
}

最后,NSViewRepresentable这就是所谓的GrowingField. 为简单起见,我省略了NSTextField子类。

struct GrowingField: NSViewRepresentable{
  @Binding var text: String
  var submit:(() -> Void)? //Hit enter
  
  func makeNSView(context: Context) -> NSTextField {
    let textField = NSTextField()
    textField.delegate = context.coordinator
    textField.stringValue = text
    
    return textField
  }
  func updateNSView(_ nsView: NSTextField, context: Context) {
    nsView.stringValue = text
    context.coordinator.textBinding = $text
  }

  //Delegates
  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
  class Coordinator: NSObject, NSTextFieldDelegate {
    let parent: GrowingField
    var textBinding : Binding<String>?
    
    init(_ field: GrowingField) {
      self.parent = field
    }
    
    func controlTextDidChange(_ obj: Notification) {
      guard let textField = obj.object as? NSTextField else { return }
      self.textBinding?.wrappedValue = textField.stringValue
    }
    
    //Listen for certain keyboard keys
    func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
      switch commandSelector{
        case #selector(NSStandardKeyBindingResponding.insertNewline(_:)):
        //- Enter -
        parent.submit?()
        textView.window?.makeFirstResponder(nil) //Blur cursor
        return true
      default:
        return false
      }
    }
    
  }
}

为什么在NSViewRepresentable重新排序后绑定到不跟随字段?

这是一个示例项目,可供下载并试用。

标签: swiftcocoaswiftuinsviewrepresentable

解决方案


我相信问题(错误?)与生成的ForEach绑定有关。

如果您放弃生成的绑定并创建自己的绑定,那么一切似乎都按预期工作。

添加到ThingModel

func bindingForThing(id: String) -> Binding<Thing> {
    .init {
        self.things.first { $0.id == id }!
    } set: { newThing in
        self.things = self.things.map { $0.id == id ? newThing : $0 }
    }
}

ParentView

ForEach(model.things){ thing in
    ChildView(thing: model.bindingForThing(id: thing.id))

推荐阅读