设置
几分钟内为您的 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 = "通用"
let toolbarItemIcon = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "通用设置")!
override var nibName: NSNib.Name? { "GeneralSettingsViewController" }
override func viewDidLoad() {
super.viewDidLoad()
// 在此处设置内容
}
}
注意:如果您需要支持比 macOS 11 更早的 macOS 版本,您必须为 toolbarItemIcon
添加一个后备方案。
AdvancedSettingsViewController.swift
import Cocoa
import Settings
final class AdvancedSettingsViewController: NSViewController, SettingsPane {
let paneIdentifier = Settings.PaneIdentifier.advanced
let paneTitle = "高级"
let toolbarItemIcon = NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: "高级设置")!
override var nibName: NSNib.Name? { "AdvancedSettingsViewController" }
override func viewDidLoad() {
super.viewDidLoad()
// 在此处设置内容
}
}
如果您需要间接响应操作,设置窗口控制器会将响应链操作转发给活动面板(如果它响应该选择器)。
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
样式:
API
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 } // 使用.`segmentedControl`样式时不需要
}
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
。请参阅此仓库中的示例项目以查看演示。
SwiftUI支持
如果你的部署目标是macOS 10.15或更高版本,你可以使用捆绑的SwiftUI组件来创建面板。使用你的自定义视图和必要的工具栏信息创建一个Settings.Pane
(使用AppKit时是SettingsPane
)。
在此仓库的Xcode项目中运行Example
目标以查看真实示例。Accounts
标签页使用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: "节标题") {
// 一些视图
}
Settings.Section(label: {
// 自定义标签对齐在右侧
}) {
// 一些视图
}
…
}
}
}
然后在AppDelegate
中,初始化一个新的SettingsWindowController
并传入面板视图。
// …
private lazy var settingsWindowController = SettingsWindowController(
panes: [
Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
CustomPane()
},
Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
AnotherCustomPane()
}
]
)
// …
如果你想在标准AppKit NSViewController
旁边使用SwiftUI面板,可以将面板视图包装到Settings.PaneHostingController
中,然后像使用标准面板一样将它们传递给SettingsWindowController
。
let CustomViewSettingsPaneViewController: () -> SettingsPane = {
let paneView = Settings.Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
// 你的自定义视图(如果需要的话还可以添加修饰符)
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
。我们打算修复这个问题。
macOS 10.13及更早版本没有动画
在 macOS 10.13 或更早版本上,SettingsWindowController.init
的 animated
参数没有效果,因为这些版本不支持 NSViewController.TransitionOptions.crossfade
。
常见问题
如何本地化窗口标题?
SettingsWindowController
遵循 macOS 人机界面指南,并使用以下规则来确定窗口标题:
- 多个设置面板: 使用当前选中的
paneTitle
作为窗口标题。本地化你的paneTitle
以获得本地化的窗口标题。 - 单个设置面板: 将窗口标题设置为
应用名称 设置
。应用名称从你的应用程序包中获取。你可以本地化其Info.plist
来自定义标题。"设置"部分取自"设置…"菜单项,参见 #12。从你的应用程序包中查找应用名称的顺序:CFBundleDisplayName
CFBundleName
CFBundleExecutable
- 如果缺少某些设置,将回退到
"<未知应用名称>"
。
为什么我应该使用这个而不是自己手动实现?
看起来不难,对吧?其实很复杂:
- 推荐的方式是使用故事板实现。但是故事板... 如果你想要分段控制样式,你必须以编程方式实现,这相当复杂。
- 即使苹果也经常做错。
- 你必须正确处理窗口和标签恢复。
- 窗口标题格式取决于你是有单个还是多个面板。
- 很难正确实现过渡动画。许多应用在面板之间的动画都不流畅。
- 你最终不得不处理许多复杂的自动布局问题。
它比 MASPreferences
好在哪里?
- 用 Swift 编写。(无需桥接头文件!)
- 使用协议的 Swift 风格 API。
- 支持分段控制样式标签。
- 支持 SwiftUI。
- 完整的文档。
- 符合 macOS 人机界面指南。
- 窗口标题通过使用系统字符串自动本地化。
相关项目
- Defaults - 优雅现代的 UserDefaults
- LaunchAtLogin - 为你的 macOS 应用添加"登录时启动"功能
- KeyboardShortcuts - 为你的 macOS 应用添加用户可自定义的全局键盘快捷键
- DockProgress - 在应用的 Dock 图标中显示进度
- 更多…
你可能也会喜欢 Sindre 的应用程序。
这些应用使用了本项目
- TableFlip - 可视化 Markdown 表格编辑器,作者:Christian Tietze
- The Archive - 笔记应用,作者:Christian Tietze
- Word Counter - 衡量作者生产力的工具,作者:Christian Tietze
- Medis - Redis GUI,作者:Zihua Li
- OK JSON - 可编程的 JSON 格式化工具,作者:Francis Feng
- Menu Helper - Finder 扩展应用,作者:Kyle-Ye
想告诉世界你的应用正在使用这个包吗?欢迎提交 PR!