如何提高 MacOS SwiftUI Picker 的性能

How to improve MacOS SwiftUI Picker performance

在将小型应用程序从 UIKit 迁移到 SwiftUI 的持续练习中,我遇到了 UIKit PopUpButton 和 SwiftUI Picker 之间的显着性能差异。这是一个带有较长列表的选择器(433 项 - ISO639 语言列表)。在 UIKit 下,PopUpButton 立即打开所有意图和目的。在 SwiftUI 下,选择器从点击到显示列表需要 4-5 秒。足够长的时间让旋转的沙滩球出现。

我猜测它是在鼠标单击后动态创建项目的子视图。有没有人经历过长选择器列表并遇到性能问题?我做了一个实验,将选择器构建的 ForEach 循环展开到一个视图中,其中组内组内组(确实有效)....但是,它花费的时间稍长。

PickerView 是

struct ISO639Picker: View {
  @Binding var selection: ISO639LanguageCode
  var body: some View {
    Picker("", selection: $selection) {
      ForEach(codeSet.codes) { code in
        Text(code.alpha3B).tag(code)
      }
    }
  }
}

为了完整起见,codeSet 是 Class 的全局实例,它从 ISO639 文本源填充,并在应用程序启动时实例化。 “代码”成员是一个结构数组,如下所示。

public struct ISO639LanguageCode: Hashable,Identifiable {
  public var id = UUID()
  public var alpha3B: String
  public var alpha3T: String
  public var alpha2: String
  public var name: String
  public var family: String
}

如能就性能问题可能出现的位置提出任何建议,我们将不胜感激。

对于遇到相同问题的任何人,对我来说,解决方案是使用 Adams 的建议,即使用 NSViewRepresentable 包装的 NSPopupButton。花了一段时间才确定要连接的正确通知。下面提供了足以满足我需求的实现;

struct NSPopUpButtonView<ItemType>: NSViewRepresentable where ItemType:Equatable {
  @Binding var selection: ItemType
  var popupCreator: () -> NSPopUpButton
  
  typealias NSViewType = NSPopUpButton
  
  func makeNSView(context: NSViewRepresentableContext<NSPopUpButtonView>) -> NSPopUpButton {
    let newPopupButton = popupCreator()
    setPopUpFromSelection(newPopupButton, selection: selection)
    return newPopupButton
  }
  
  func updateNSView(_ nsView: NSPopUpButton, context: NSViewRepresentableContext<NSPopUpButtonView>) {
    setPopUpFromSelection(nsView, selection: selection)
  }
  
  func setPopUpFromSelection(_ button:NSPopUpButton, selection:ItemType)
  {
    let itemsList = button.itemArray
    let matchedMenuItem = itemsList.filter{([=10=].representedObject as! ItemType) == selection}.first
    if matchedMenuItem != nil
    {
      button.select(matchedMenuItem)
    }
  }
  
  func makeCoordinator() -> Coordinator {
    return Coordinator(self)
  }
  
  class Coordinator:NSObject {
    var parent: NSPopUpButtonView!
    
    init(_ parent: NSPopUpButtonView)
    {
      super.init()
      self.parent = parent
      NotificationCenter.default.addObserver(self,
                                             selector: #selector(dropdownItemSelected),
                                             name: NSMenu.didSendActionNotification,
                                             object: nil)
    }
    
    @objc func dropdownItemSelected(_ notification: NSNotification)
    {
      let menuItem = (notification.userInfo?["MenuItem"])! as! NSMenuItem
      parent.selection = menuItem.representedObject as! ItemType
    }
  }
}