SwiftUI 绑定在项目重新排序后编辑错误的 TextField

SwiftUI Binding Edits Wrong TextField after Item Reordered

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

我有一个 parent SwiftUI 视图,其中列出了一些 children。每个 child 都绑定到一个 NSViewRepresentable。一切正常,我可以按预期编辑值。但是一旦我重新排序列表中的项目并编辑一个字段,它编辑了错误的字段之前的商品订单

的绑定似乎完好无损

这是它的样子:

这是parent:

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

...这是 child 视图:

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 的绑定在重新排序后不跟在字段后面?

Here is a sample project下载并试用。

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

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

添加到 ThingModel:

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

ParentView:

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