在几分钟内为你的 macOS 应用添加设置窗口
只需传入一些视图控制器,此软件包将处理其余事项。内置 SwiftUI 支持。
此软件包兼容 macOS 13,并在 macOS 13 及更高版本的窗口标题中自动使用 设置
而不是 偏好设置
。
此项目以前称为 Preferences
。
macOS 10.13 及更高版本。
在 Xcode 的 “Swift Package Manager” 选项卡中添加 https://github.com/sindresorhus/Settings
。
运行 Example
Xcode 项目以尝试实时示例(需要 macOS 11 或更高版本)。
首先,创建一些设置面板标识符
import Settings
extension Settings.PaneIdentifier {
static let general = Self("general")
static let advanced = Self("advanced")
}
其次,为你需要的设置面板创建几个视图控制器。与实现普通视图控制器的唯一区别是,你必须添加 SettingsPane
协议并实现 paneIdentifier
、toolbarItemTitle
和 toolbarItemIcon
属性,如下所示。如果使用 .segmentedControl
样式,则可以省略 toolbarItemIcon
。
GeneralSettingsViewController.swift
import Cocoa
import Settings
final class GeneralSettingsViewController: NSViewController, SettingsPane {
let paneIdentifier = Settings.PaneIdentifier.general
let paneTitle = "General"
let toolbarItemIcon = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "General settings")!
override var nibName: NSNib.Name? { "GeneralSettingsViewController" }
override func viewDidLoad() {
super.viewDidLoad()
// Setup stuff here
}
}
注意:如果需要支持 macOS 11 之前的 macOS 版本,则必须为 toolbarItemIcon
添加后向兼容性。
AdvancedSettingsViewController.swift
import Cocoa
import Settings
final class AdvancedSettingsViewController: NSViewController, SettingsPane {
let paneIdentifier = Settings.PaneIdentifier.advanced
let paneTitle = "Advanced"
let toolbarItemIcon = NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: "Advanced settings")!
override var nibName: NSNib.Name? { "AdvancedSettingsViewController" }
override func viewDidLoad() {
super.viewDidLoad()
// Setup stuff here
}
}
如果需要间接响应操作,则设置窗口控制器会将响应链操作转发到活动面板(如果它响应选择器)。
final class AdvancedSettingsViewController: NSViewController, SettingsPane {
@IBOutlet private var fontLabel: NSTextField!
private var selectedFont = NSFont.systemFont(ofSize: 14)
@IBAction private func changeFont(_ sender: NSFontManager) {
font = sender.convert(font)
}
}
在 AppDelegate
中,初始化一个新的 SettingsWindowController
并传入视图控制器。然后为 设置…
菜单项添加一个操作出口,以显示设置窗口。
AppDelegate.swift
import Cocoa
import Settings
@main
final class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet private var window: NSWindow!
private lazy var settingsWindowController = SettingsWindowController(
panes: [
GeneralSettingsViewController(),
AdvancedSettingsViewController()
]
)
func applicationDidFinishLaunching(_ notification: Notification) {}
@IBAction
func settingsMenuItemActionHandler(_ sender: NSMenuItem) {
settingsWindowController.show()
}
}
创建 SettingsWindowController
时,可以在基于 NSToolbarItem
的样式(默认)和 NSSegmentedControl
之间进行选择
// …
private lazy var settingsWindowController = SettingsWindowController(
panes: [
GeneralSettingsViewController(),
AdvancedSettingsViewController()
],
style: .segmentedControl
)
// …
.toolbarItem
样式
.segmentedControl
样式
public enum Settings {}
extension Settings {
public enum Style {
case toolbarItems
case segmentedControl
}
}
public protocol SettingsPane: NSViewController {
var paneIdentifier: Settings.PaneIdentifier { get }
var paneTitle: String { get }
var toolbarItemIcon: NSImage { get } // Not required when using the .`segmentedControl` style
}
public final class SettingsWindowController: NSWindowController {
init(
panes: [SettingsPane],
style: Settings.Style = .toolbarItems,
animated: Bool = true,
hidesToolbarForSingleItem: Bool = true
)
init(
panes: [SettingsPaneConvertible],
style: Settings.Style = .toolbarItems,
animated: Bool = true,
hidesToolbarForSingleItem: Bool = true
)
func show(pane: Settings.PaneIdentifier? = nil)
}
与任何 NSWindowController
一样,调用 NSWindowController#close()
关闭设置窗口。
在每个面板中创建用户界面的最简单方法是在 Interface Builder 中使用 NSGridView
。有关演示,请参阅此仓库中的示例项目。
如果你的部署目标是 macOS 10.15 或更高版本,则可以使用捆绑的 SwiftUI 组件来创建面板。使用你的自定义视图和必要的工具栏信息创建 Settings.Pane
(使用 AppKit 时为 SettingsPane
)。
运行此仓库中 Xcode 项目中的 Example
目标,以查看真实示例。“帐户”选项卡在 SwiftUI 中。
还有一些捆绑的便捷 SwiftUI 组件,例如 Settings.Container
和 Settings.Section
,可自动实现与 AppKit 的 NSGridView
类似的对齐方式。还有一个 .settiingDescription()
视图修饰符,用于将文本样式设置为设置描述。
提示:Defaults
软件包可以非常轻松地持久化设置。
struct CustomPane: View {
var body: some View {
Settings.Container(contentWidth: 450.0) {
Settings.Section(title: "Section Title") {
// Some view.
}
Settings.Section(label: {
// Custom label aligned on the right side.
}) {
// Some view.
}
…
}
}
}
然后在 AppDelegate
中,初始化一个新的 SettingsWindowController
并将面板视图传递给它。
// …
private lazy var settingsWindowController = SettingsWindowController(
panes: [
Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
CustomPane()
},
Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
AnotherCustomPane()
}
]
)
// …
如果想将 SwiftUI 面板与标准 AppKit NSViewController
一起使用,请将面板视图包装到 Settings.PaneHostingController
中,然后像处理标准面板一样将它们传递给 SettingsWindowController
。
let CustomViewSettingsPaneViewController: () -> SettingsPane = {
let paneView = Settings.Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
// Your custom view (and modifiers if needed).
CustomPane()
// .environmentObject(someSettingsManager)
}
return Settings.PaneHostingController(paneView: paneView)
}
// …
private lazy var settingsWindowController = SettingsWindowController(
panes: [
GeneralSettingsViewController(),
AdvancedSettingsViewController(),
CustomViewSettingsPaneViewController()
],
style: .segmentedControl
)
// …
macOS 11 及更高版本支持 SF Symbols,可以方便地用于工具栏图标。如果需要支持较旧的 macOS 版本,则必须添加后备方案。Apple 建议即使对于较旧的系统也使用相同的图标。实现此目的的最佳方法是将相关的 SF Symbols 图标导出到图像,并将它们添加到你的 Asset Catalog。
当你不使用自动布局或未设置视图控制器的大小时,可能会发生这种情况。你可以通过使用自动布局或设置显式大小来解决此问题,例如,在 viewDidLoad()
中设置 preferredContentSize
。我们打算修复此问题。
SettingsWindowController.init
的 animated
参数在 macOS 10.13 或更早版本上无效,因为这些版本不支持 NSViewController.TransitionOptions.crossfade
。
SettingsWindowController
遵循 macOS 人机界面指南,并使用这组规则来确定窗口标题
paneTitle
作为窗口标题。本地化你的 paneTitle
以获得本地化的窗口标题。APPNAME 设置
。应用程序名称从你的应用程序包中获取。你可以本地化其 Info.plist
以自定义标题。“设置”部分取自“设置…”菜单项,请参阅 #12。从你的应用程序包中查找应用程序名称的顺序CFBundleDisplayName
CFBundleName
CFBundleExecutable
"<Unknown App Name>"
以显示你缺少一些设置。这不应该很难吧?好吧,事实证明它确实很难
MASPreferences
相比,它有什么优势?你可能还会喜欢 Sindre 的 应用。
想告诉全世界你的应用正在使用此软件包吗?打开一个 PR!