如何提高 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
}
}
}
在将小型应用程序从 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
}
}
}