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。从您的包中查找应用名称的顺序CFBundleDisplayName
CFBundleName
CFBundleExecutable
- 如果您丢失了一些设置,将回退到
"<未知应用名称>"
以显示。
为什么我会选择使用它而不是手动实现呢?
这难道不是一项简单的任务吗?事实上,并非如此。
- 建议使用故事板来实现。 但是,故事板... 如果你想要分段控制风格的选项卡,你必须程序化地实现, 这相当复杂。
- 即使是苹果,也经常出错。
- 你必须正确处理 窗口 和 标签页还原。
- 窗口标题格式取决于你是否只有一个或多个窗格。
- 很难得到过渡动画的正确效果。很多应用程序在窗格之间有闪烁的动画。
- 你最终需要处理大量的复杂性的自动布局。
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!