OSX SwiftUI 集成 NSComboBox 不刷新当前选择

OSX SwiftUI integrating NSComboBox Not refreshing current sélected

SwiftUI Picker 在 OSX 上看起来很糟糕,尤其是在处理长项目列表时

Swiftui Picker on OSX with a long item list

并且由于确实找到了任何解决方案来限制 Osx 上 Picker 显示的项目数量,我决定将 NSComboBox 连接到 SwiftUI

在使用 Observable Comboselection class 实例的 @Published 索引以编程方式修改选择索引之前,一切看起来都很好(参见下面的代码):

combo.selectItem(at: selected.index) 
      combo.selectItem(at: selected.index)
      combo.objectValue = combo.objectValueOfSelectedItem
      print("populating index change \(selected.index) to Combo : (String(describing: combo.objectValue))")

这里有人有解释吗?? ;代码有问题吗??

这里是所有代码:

import SwiftUI

class ComboSelection : ObservableObject {
  @Published var index : Int

  init( index: Int ) {
    self.index = index
  }

  func newSelection( newIndex : Int ) {
    index = newIndex
  }
}

//
// SwiftUI NSComboBox component interface
//
struct SwiftUIComboBox : NSViewRepresentable {

  typealias NSViewType = NSComboBox

  var content : [String]
  var nbLines : Int
  var selected : ComboSelection

  final class Coordinator : NSObject ,
  NSComboBoxDelegate {

    var control : SwiftUIComboBox
    var selected : ComboSelection


    init( _ control: SwiftUIComboBox , selected : ComboSelection ) {
      self.selected = selected
      self.control = control
    }

    func comboBoxSelectionDidChange(_ notification: Notification) {
      print ("entering coordinator selection did change")
      let combo = notification.object as! NSComboBox
      selected.newSelection( newIndex: combo.indexOfSelectedItem  )
    }
  }

  func makeCoordinator() -> SwiftUIComboBox.Coordinator {
    return Coordinator(self, selected:selected)
  }

  func makeNSView(context: NSViewRepresentableContext<SwiftUIComboBox>) -> NSComboBox {
    let returned = NSComboBox()
    returned.numberOfVisibleItems = nbLines
    returned.hasVerticalScroller = true
    returned.usesDataSource = false
    returned.delegate = context.coordinator // Important : not forget to define delegate
    for key in content{
      returned.addItem(withObjectValue: key)
    }
    return returned
  }

  func updateNSView(_ combo: NSComboBox, context: NSViewRepresentableContext<SwiftUIComboBox>) {
      combo.selectItem(at: selected.index)
      combo.objectValue = combo.objectValueOfSelectedItem
      print("populating index change \(selected.index) to Combo : \(String(describing: combo.objectValue))")
  }
}

请参阅更新和简化的代码,添加了一些工作演示。问题的主要原因是缺少 SwiftUI 视图层次结构的更新,因此为了进行此类更新,我使用了 Binding,它将更改传输到 UIViewRepresentable 并返回。希望这种方法会有所帮助。

这是演示

下面是一个模块的完整演示代码(只需设置
window.contentView = NSHostingView(rootView:TestComboBox()) 在应用程序委托中

struct SwiftUIComboBox : NSViewRepresentable {

    typealias NSViewType = NSComboBox

    var content : [String]
    var nbLines : Int
    @Binding var selected : Int

    final class Coordinator : NSObject, NSComboBoxDelegate {

        var selected : Binding<Int>

        init(selected : Binding<Int>) {
            self.selected = selected
        }

        func comboBoxSelectionDidChange(_ notification: Notification) {
            print ("entering coordinator selection did change")
            if let combo = notification.object as? NSComboBox, selected.wrappedValue != combo.indexOfSelectedItem {
                selected.wrappedValue = combo.indexOfSelectedItem
            }
        }
    }

    func makeCoordinator() -> SwiftUIComboBox.Coordinator {
        return Coordinator(selected: $selected)
    }

    func makeNSView(context: NSViewRepresentableContext<SwiftUIComboBox>) -> NSComboBox {
        let returned = NSComboBox()
        returned.numberOfVisibleItems = nbLines
        returned.hasVerticalScroller = true
        returned.usesDataSource = false
        returned.delegate = context.coordinator // Important : not forget to define delegate
        for key in content {
            returned.addItem(withObjectValue: key)
        }
        return returned
    }

    func updateNSView(_ combo: NSComboBox, context:  NSViewRepresentableContext<SwiftUIComboBox>) {
        if selected != combo.indexOfSelectedItem {
            DispatchQueue.main.async {
                combo.selectItem(at: self.selected)
                print("populating index change \(self.selected) to Combo : \(String(describing: combo.objectValue))")
            }
        }
    }
}


struct TestComboBox: View {
    @State var selection = 0
    let content = ["Alpha", "Beta", "Gamma", "Delta", "Epselon", "Zetta", "Eta"]

    var body: some View {
        VStack {
            Button(action: {
                if self.selection + 1 < self.content.count {
                    self.selection += 1
                } else {
                    self.selection = 0
                }
            }) {
                Text("Select next")
            }
            Divider()
            SwiftUIComboBox(content: content, nbLines: 3, selected: $selection)
            Divider()
            Text("Current selection: \(selection), value: \(content[selection])")
        }
        .frame(width: 300, height: 300)
    }
}