Preferences
将偏好设置窗口添加到您的macOS应用仅需几分钟
只需传入一些视图控制器,这个包将处理其余部分。内置SwiftUI支持。
要求
- macOS 10.10+
- Xcode 12.5+
- Swift 5.4+
安装
Swift包管理器
在Xcode的“Swift包管理器”标签页中添加https://github.com/sindresorhus/Preferences。
Carthage
github "sindresorhus/Preferences"
高速 conflicts-tooltipJapanese
pod 'Preferences'使用方法
在 Xcode 中运行 PreferencesExample target 以尝试实时示例(需要 macOS 11 或更高版本)。
首先,创建一些首选项面板标识符
import Preferences
extension Preferences.PaneIdentifier {
static let general = Self("general")
static let advanced = Self("advanced")
}其次,创建您想要的首选项面板的视图控制器。与实现正常的视图控制器相比,唯一的区别是您必须添加 PreferencePane 协议并实现 preferencePaneIdentifier、toolbarItemTitle 和 toolbarItemIcon 属性,如下所示。如果您正在使用 .segmentedControl 风格,则可以省略 toolbarItemIcon。
GeneralPreferenceViewController.swift
import Cocoa
import Preferences
final class GeneralPreferenceViewController: NSViewController, PreferencePane {
let preferencePaneIdentifier = Preferences.PaneIdentifier.general
let preferencePaneTitle = "General"
let toolbarItemIcon = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "General preferences")!
override var nibName: NSNib.Name? { "GeneralPreferenceViewController" }
override func viewDidLoad() {
super.viewDidLoad()
// Setup stuff here
}
}注意:如果您需要支持低于 macOS 11 的 macOS 版本,您必须为 fallback for the toolbarItemIcon 添加备用。
AdvancedPreferenceViewController.swift
import Cocoa
import Preferences
final class AdvancedPreferenceViewController: NSViewController, PreferencePane {
let preferencePaneIdentifier = Preferences.PaneIdentifier.advanced
let preferencePaneTitle = "Advanced"
let toolbarItemIcon = NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: "Advanced preferences")!
override var nibName: NSNib.Name? { "AdvancedPreferenceViewController" }
override func viewDidLoad() {
super.viewDidLoad()
// Setup stuff here
}
}如果您需要间接响应操作,PreferencesWindowController 将将响应链操作转发到活动面板,如果活动面板响应该选择器。
final class AdvancedPreferenceViewController: NSViewController, PreferencePane {
@IBOutlet private var fontLabel: NSTextField!
private var selectedFont = NSFont.systemFont(ofSize: 14)
@IBAction private func changeFont(_ sender: NSFontManager) {
font = sender.convert(font)
}
}在 AppDelegate 中,初始化一个新的 PreferencesWindowController 并将视图控制器传递给它。然后添加一个操作出口来显示首选项窗口。
AppDelegate.swift
import Cocoa
import Preferences
@main
final class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet private var window: NSWindow!
private lazy var preferencesWindowController = PreferencesWindowController(
preferencePanes: [
GeneralPreferenceViewController(),
AdvancedPreferenceViewController()
]
)
func applicationDidFinishLaunching(_ notification: Notification) {}
@IBAction
func preferencesMenuItemActionHandler(_ sender: NSMenuItem) {
preferencesWindowController.show()
}
}首选项选项卡样式
在创建 PreferencesWindowController 时,您可以选择基于 NSToolbarItem 的样式(默认)和 NSSegmentedControl。
// …
private lazy var preferencesWindowController = PreferencesWindowController(
preferencePanes: [
GeneralPreferenceViewController(),
AdvancedPreferenceViewController()
],
style: .segmentedControl
)
// ….toolbarItem样式
.segmentedControl样式
API
public enum Preferences {}
extension Preferences {
public enum Style {
case toolbarItems
case segmentedControl
}
}
public protocol PreferencePane: NSViewController {
var preferencePaneIdentifier: Preferences.PaneIdentifier { get }
var preferencePaneTitle: String { get }
var toolbarItemIcon: NSImage { get } // Not required when using the .`segmentedControl` style
}
public final class PreferencesWindowController: NSWindowController {
init(
preferencePanes: [PreferencePane],
style: Preferences.Style = .toolbarItems,
animated: Bool = true,
hidesToolbarForSingleItem: Bool = true
)
init(
panes: [PreferencePaneConvertible],
style: Preferences.Style = .toolbarItems,
animated: Bool = true,
hidesToolbarForSingleItem: Bool = true
)
func show(preferencePane: Preferences.PaneIdentifier? = nil)
}与任何 NSWindowController 一样,调用 NSWindowController#close() 关闭首选项窗口。
推荐
在每个面板中创建用户界面的最简单方法是在 Interface Builder 中使用 NSGridView。请参见本存储库中的示例项目。
SwiftUI 支持
如果您的部署目标为 macOS 10.15 或更高版本,您可以使用捆绑的 SwiftUI 组件来创建面板。使用您的自定义视图和必要的工具栏信息创建一个 Preferences.Pane(当使用 AppKit 时,取代 PreferencePane)。
运行本存储库中 Xcode 项目中的 PreferencesExample 目标,查看实际示例。Accounts 标签位于 SwiftUI 中。
还有一些捆绑的便利 SwiftUI 组件,如 Preferences.Container 和 Preferences.Section,以便自动实现与 AppKit 的 NSGridView 相似的对齐。还有一个 .preferenceDescription() 视图修饰符,可以用于将文本样式设置为偏好设置描述。
提示:Defaults 软件包使用户可以轻松保存偏好设置。
struct CustomPane: View {
var body: some View {
Preferences.Container(contentWidth: 450.0) {
Preferences.Section(title: "Section Title") {
// Some view.
}
Preferences.Section(label: {
// Custom label aligned on the right side.
}) {
// Some view.
}
…
}
}
}然后在 AppDelegate 中,初始化一个新的 PreferencesWindowController 并将面板视图传递给它。
// …
private lazy var preferencesWindowController = PreferencesWindowController(
panes: [
Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
CustomPane()
},
Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
AnotherCustomPane()
}
]
)
// …如果您想同时使用 SwiftUI 面板和标准 AppKit NSViewController,请将面板视图包装进 Preferences.PaneHostingController,然后将它们作为标准面板一样传递给 PreferencesWindowController。
let CustomViewPreferencePaneViewController: () -> PreferencePane = {
let paneView = Preferences.Pane(
identifier: …,
title: …,
toolbarIcon: NSImage(…)
) {
// Your custom view (and modifiers if needed).
CustomPane()
// .environmentObject(someSettingsManager)
}
return Preferences.PaneHostingController(paneView: paneView)
}
// …
private lazy var preferencesWindowController = PreferencesWindowController(
preferencePanes: [
GeneralPreferenceViewController(),
AdvancedPreferenceViewController(),
CustomViewPreferencePaneViewController()
],
style: .segmentedControl
)
// …向后兼容性
macOS 11 及更高版本支持 SF Symbols,可以作为工具栏图标方便地使用。如果您需要支持较旧的 macOS 版本,则需要添加回退。Apple 建议即使是旧系统也使用相同的图标。实现此目的的最佳方法是导出相关的 SF Symbols 图标到图像中,并将它们添加到您的资产目录中。
已知问题
偏好设置窗口未显示
当您未使用自动布局或未为视图控制器设置大小的情况下,可能会发生这种情况。您可以通过使用自动布局或设置显式大小来解决这个问题,例如,在viewDidLoad()中使用preferredContentSize。 我们打算修复这个问题。
在macOS 10.13及以下版本中没有动画效果
PreferencesWindowController.init的animated参数在macOS 10.13或更早版本上无效,因为这些版本不支持NSViewController.TransitionOptions.crossfade。
常见问题解答
如何本地化窗口标题?
PreferencesWindowController遵循macOS 人机界面指南并使用这组规则来确定窗口标题
- 多个偏好设置面板:使用当前选定的
preferencePaneTitle作为窗口标题。本地化您的preferencePaneTitle以获取本地化窗口标题。 - 单个偏好设置面板:将窗口标题设置为
APPNAME Preferences。应用名称从您的应用包中获取。您可以本地化其Info.plist以自定义标题。Preferences部分摘自“偏好设置…”菜单项,见#12。从您的包中查找应用名称的顺序CFBundleDisplayNameCFBundleNameCFBundleExecutable- 如果您丢失了一些设置,将回退到
"<未知应用名称>"以显示。
为什么我会选择使用它而不是手动实现呢?
这难道不是一项简单的任务吗?事实上,并非如此。
- 建议使用故事板来实现。 但是,故事板... 如果你想要分段控制风格的选项卡,你必须程序化地实现, 这相当复杂。
- 即使是苹果,也经常出错。
- 你必须正确处理 窗口 和 标签页还原。
- 窗口标题格式取决于你是否只有一个或多个窗格。
- 很难得到过渡动画的正确效果。很多应用程序在窗格之间有闪烁的动画。
- 你最终需要处理大量的复杂性的自动布局。
它比 MASPreferences 有什么优点?
- 使用 Swift 编写。 (无需桥接头!)
- 使用协议的 Swifty API。
- 支持分段控制风格选项卡。
- 支持 SwiftUI。
- 全面文档。
- 遵循 macOS 人机界面指南。
- 窗口标题自动使用系统字符串进行本地化。
相关
- Defaults - Swift 的现代 UserDefaults
- LaunchAtLogin - 为您的 macOS 应用程序添加“登录时启动”功能
- KeyboardShortcuts - 将用户可定制的全局快捷键添加到您的 macOS 应用程序中
- DockProgress - 在您的应用程序图标中显示进度
- Regex - Swift 的正则表达式
- 更多…
您可能还会喜欢 Sindre 的 应用程序。
在这些应用中使用
- TableFlip - 由 Christian Tietze 开发的可视化 Markdown 表格编辑器
- The Archive - 由 Christian Tietze 开发的笔记应用程序
- Word Counter - 由 Christian Tietze 开发的衡量作家生产力的工具
想要告诉世界你的应用正在使用 Preferences?请提交一个 PR!


