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

时间:2019-11-23 20:48:26

标签: macos swiftui picker nscombobox

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

Swiftui Picker on OSX with a long item list

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

在使用Observable Comboselection类实例的@Published索引以编程方式修改选择索引之前,一切看起来都很好:

  • 然后正确调用NSViewRepresentable实例的updateNSView函数(在日志上显示打印消息)
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))")
  • 正确执行,并且打印的日志显示正确的信息

  • 但是NSComboBox文本字段未使用准确的对象值刷新

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

这里是所有代码:

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

1 个答案:

答案 0 :(得分:0)

请查看更新并简化的代码,并添加一些有效的演示。问题的主要原因是SwiftUI视图层次结构没有更新,因此为了进行这样的更新,我使用了Binding,该绑定将更改转移到UIViewRepresentable并返回。希望这种方法会有所帮助。

这是演示

SwiftUI UIComboBox

下面是一个模块的完整演示代码(只需设置
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)
    }
}