Preferences
在几分钟内将偏好设置窗口添加到您的 macOS 应用中
只需传入一些视图控制器,这个包将处理其余部分。内置 SwiftUI 支持。
系统要求
- macOS 10.10+
- Xcode 12.5+
- Swift 5.4+
安装
Swift 包管理器
在 Xcode 中的“Swift 包管理器”选项卡下添加 https://github.com/sindresorhus/Preferences
。
Carthage
github "sindresorhus/Preferences"
CocoaPods
pod 'Preferences'
使用方法
在 Xcode 中运行 PreferencesExample
目标以尝试实时示例(需要 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 的版本,您必须为 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
并传递相应的视图控制器。然后为 Preferences…
菜单项添加一个操作出口以显示首选项窗口。
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()
来关闭首选项窗口。
推荐
在界面构建器中使用 NSGridView
是在每块中创建用户界面的最简单方法。请查看本仓库中的示例项目进行演示。
SwiftUI支持
如果您部署的目标是macOS 10.15或更高版本,您可以使用捆绑的SwiftUI组件来创建块。使用自定义视图和必要的工具栏信息创建一个Preferences.Pane
(当使用AppKit时,使用PreferencePane
代替)。
运行此仓库中Xcode项目中的PreferencesExample
目标,以查看实际示例。在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
。我们将修复这个问题。《a href="https://github.com/sindresorhus/Preferences/pull/28">我们打算修复。
在 macOS 10.13 及更早版本中没有动画
PreferencesWindowController.init
的 animated
参数在 macOS 10.13 或更早版本中没有效果,因为这些版本不支持 NSViewController.TransitionOptions.crossfade
。
常见问题解答 (FAQ)
如何本地化窗口标题?
PreferencesWindowController
遵循 macOS 人机界面指南 并使用这一套规则来决定窗口标题
- 多个偏好设置标签页:使用当前选中的
preferencePaneTitle
作为窗口标题。本地化您的preferencePaneTitle
以获取本地化的窗口标题。 - 单个偏好设置标签页:将窗口标题设置为 `
APPNAME Preferences
`。应用名称是从您的应用包中获取的。您可以通过编辑其Info.plist
来自定义标题。标题中的 “Preferences” 来自 “首选项…” 菜单项。从您包中获取应用程序名称的查找顺序CFBundleDisplayName
CFBundleName
CFBundleExecutable
- 如果遗漏了一些设置,将回退到
"<Unknown App Name>"
来显示问题。
为什么我应该使用它而不是手动实现呢?
这听起来不应该很难吧?然而,实际情况并非如此。
- 建议的方式是使用故事板实现。请参阅此链接 关于故事板的信息。如果您想实现分段控制风格,必须通过编程实现,这是一项相当复杂的工作,请参阅 相关代码。
- 即使是苹果,做得也有很多不对的地方。
- 您必须正确处理 窗口 和 标签页还原。
- 窗口标题的格式取决于你是否只有一个或多个面板。
- 很难正确处理过渡动画。很多应用在面板间的动画效果都不稳定。
- 您最终不得不处理许多复杂的自动布局问题。
MASPreferences
更好?
为什么它比 - 使用 Swift 编写。(没有桥接头!)
- 使用协议的 Swift API。
- 支持分段控制风格标签。
- 支持 SwiftUI。
- 完全文档化。
- 遵循 macOS Human Interface Guidelines。
- 窗口标题将自动通过使用系统字符串进行本地化。
相关
- Defaults - Swifty 和现代的 UserDefaults
- LaunchAtLogin - 为您的 macOS 应用添加 "启动时运行" 功能
- KeyboardShortcuts - 为您的 macOS 应用添加用户自定义的全局键盘快捷键
- DockProgress - 在您的应用 Dock 图标中显示进度
- Regex - Swifty 正则表达式
- 更多…
您可能还会喜欢 Sindre 的 应用。
在以下应用中使用
- TableFlip - 由 Christian Tietze 开发的视觉Markdown表格编辑器
- The Archive - 由 Christian Tietze 开发的笔记应用
- Word Counter - 通过 Christian Tietze 测量作者的生产力
想要向世界介绍使用Preference的功能的应用?提交一个PR!