关于 macos-settings-ui
用于构建符合 macOS 26 设计规范的原生设置窗口的实现指南,面向开发 macOS 应用的人员。它采用 NSWindowController 与 NavigationSplitView 组合,以实现 Liquid Glass 窗口外观和侧边栏设置布局。
主要功能
- 01NSWindowController 窗口架构
- 02侧边栏与详情视图导航
- 03Liquid Glass 窗口外观
- 04分组表单与透明背景
- 05菜单栏应用激活策略
适用场景
- 01为 macOS 应用创建原生设置窗口
- 02按类别组织设置页面和详情面板
- 03为菜单栏应用打开设置窗口
- 04添加带有 Toggle、Picker 和 Slider 的表单
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ fayazara/ macos-app-skills --skill macos-settings-ui安装后在 Claude Code 中输入/macos-settings-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/ExampleDetailPane.swiftSettingsView.swiftSettingsWindowController.swift详细介绍
采用 Liquid Glass 的 macOS 设置界面
此技能会生成一个原生 macOS 设置窗口,遵循 Apple 的 macOS 26 设计指南。最终界面包含侧边栏和详情视图的 NavigationSplitView、采用 Liquid Glass 窗口边框、支持工具栏中的前进/后退导航、带透明背景的分组 Form 区段,以及正确的滚动边缘效果。
架构概览
设置界面由 3 层组成:
-
窗口控制器(
SettingsWindowController.swift)—— 一个以编程方式创建NSWindow并设置.fullSizeContentView的NSWindowController。这会让窗口呈现圆角 Liquid Glass 边角。使用 SwiftUIWindow场景无法实现这种效果。 -
根视图(
SettingsView.swift)—— 包含侧边栏列表和详情窗格的NavigationSplitView。工具栏中包含前进/后退导航历史记录,这也会强制创建NSToolbar(这是实现 Liquid Glass 标题栏效果所必需的)。 -
详情窗格(每个标签页对应一个文件)—— 每个窗格都使用
Form { Section(...) { ... } }.formStyle(.grouped).scrollContentBackground(.hidden)。
为什么使用 NSWindowController,而不是 SwiftUI Window 场景
SwiftUI 的声明式 Window 场景不会公开 NSWindow 样式掩码。必须在创建窗口时设置 .fullSizeContentView 标志,这样 macOS 26 才能渲染 Liquid Glass 窗口边框(圆角、半透明侧边栏、模糊标题栏)。尝试通过 NSViewRepresentable 在之后注入该标志并不可靠,因为 SwiftUI 会重置窗口配置。
使用 NSWindowController 还可以直接控制工具栏样式、窗口框架自动保存、最小尺寸和代理生命周期。
关键修饰符
每个详情窗格的 Form 都必须包含以下三个修饰符:
.formStyle(.grouped)
.scrollContentBackground(.hidden)
.contentMargins(.top, 8, for: .scrollContent)
.formStyle(.grouped)— 提供原生的内嵌圆角矩形区段样式.scrollContentBackground(.hidden)— 使表单背景透明,以便透出 Liquid Glass 窗口边框。若不设置此项,背景会是不透明的白色或深色,从而破坏玻璃效果。.contentMargins(.top, 8)— 在工具栏与第一个区段之间留出适当间距
侧边栏 List 必须包含:
.listStyle(.sidebar)
.scrollEdgeEffectStyleSoftIfAvailable() // macOS 26 progressive blur at scroll edges
.navigationTitle("Settings")
NavigationSplitView 必须包含:
NavigationSplitView(columnVisibility: .constant(.all)) // sidebar always visible
// ...
.navigationTitle("Settings")
.navigationSplitViewStyle(.balanced)
参考实现
请阅读参考文件,查看完整可运行的代码:
references/SettingsWindowController.swift— 窗口控制器(按原样复制,并根据你的应用调整激活策略调用)references/SettingsView.swift— 包含侧边栏、详情路由和导航历史记录的根视图references/ExampleDetailPane.swift— 展示常见控件模式(Toggle、Picker、Slider、LabeledContent)的详情窗格模板
分步指南:为新应用添加设置界面
1. 创建标签页枚举
定义设置类别。每个枚举成员都需要一个标题和 SF Symbol 图标:
enum SettingsTab: String, CaseIterable, Identifiable {
case general
case appearance
case about
var id: Self { self }
var title: String {
switch self {
case .general: "General"
case .appearance: "Appearance"
case .about: "About"
}
}
var systemImage: String {
switch self {
case .general: "gearshape"
case .appearance: "paintbrush"
case .about: "info.circle"
}
}
}
2. 创建窗口控制器
将 references/SettingsWindowController.swift 复制到你的项目中。调整以下内容:
- 初始
contentRect尺寸(默认的700x540适合大多数应用) minSize(默认的620x460)- 激活策略调用(
AppActivationPolicy.enter()/leave())—— 如果你的应用不是仅包含菜单栏的应用,请移除这些调用
3. 创建根设置视图
复制 references/SettingsView.swift。调整以下内容:
SettingsTab枚举成员,使其与你的设置类别匹配SettingsDetailViewswitch,使其返回你的窗格
4. 创建详情窗格
为每个标签页创建一个窗格文件。使用以下模板:
import SwiftUI
struct GeneralSettingsPane: View {
@AppStorage("someKey") private var someValue = false
var body: some View {
Form {
Section("Section Name") {
Toggle("Toggle label", isOn: $someValue)
}
}
.formStyle(.grouped)
.scrollContentBackground(.hidden)
.contentMargins(.top, 8, for: .scrollContent)
}
}
5. 从应用中打开设置
从菜单栏、按钮或其他任意位置打开:
SettingsWindowController.show(tab: .general)
不要使用 SwiftUI Window 场景。不要使用 openWindow(id:)。窗口控制器会处理所有事情。
6. 移除任何 SwiftUI Window 场景
如果你的 App 结构体中此前包含 Window("Settings", id: "SETTINGS") 场景,请将其彻底移除。SettingsWindowController 会取代它。
表单区域中的常见控件模式
带说明的切换开关:
Toggle(isOn: $value) {
VStack(alignment: .leading, spacing: 2) {
Text("Primary label")
Text("Description text explaining what this does.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
.toggleStyle(.switch)
选择器(下拉菜单):
Picker("Label", selection: $value) {
Text("Option A").tag(OptionEnum.a)
Text("Option B").tag(OptionEnum.b)
}
.pickerStyle(.menu)
选择器(分段式):
Picker("Label", selection: $value) {
ForEach(SomeEnum.allCases) { item in
Text(item.title).tag(item)
}
}
.pickerStyle(.segmented)
带数值标签的滑块:
LabeledContent("Size") {
HStack(spacing: 12) {
Slider(value: $size, in: 24...96, step: 2)
.frame(width: 180)
Text("\(Int(size)) pt")
.monospacedDigit()
.foregroundStyle(.secondary)
.frame(width: 46, alignment: .trailing)
}
}
按钮行:
HStack(spacing: 8) {
Button("Action") { doSomething() }
.controlSize(.small)
Button("Reset") { reset() }
.controlSize(.small)
.disabled(isDefault)
}
仅菜单栏应用
如果你的应用使用 NSApp.setActivationPolicy(.accessory)(没有 Dock 图标),则需要管理激活策略,以便设置窗口将应用置于前台。参考实现 SettingsWindowController 会在窗口显示时调用 AppActivationPolicy.enter(),并在窗口关闭时调用 .leave()。你可以将其实现为一个简单的引用计数包装器:
@MainActor
enum AppActivationPolicy {
private static var count = 0
static func enter() {
count += 1
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
}
static func leave() {
count = max(0, count - 1)
guard count == 0 else { return }
Task { @MainActor in
NSApp.setActivationPolicy(.accessory)
}
}
}
如果你的应用始终显示在 Dock 中,请从窗口控制器中移除 AppActivationPolicy 调用,并在 showWindow 中直接使用 NSApp.activate(ignoringOtherApps: true)。
macOS 版本兼容性
scrollEdgeEffectStyle(.soft) API 仅适用于 macOS 26 及更高版本。请始终使用可用性检查将其包装起来:
private extension View {
@ViewBuilder
func scrollEdgeEffectStyleSoftIfAvailable() -> some View {
if #available(macOS 26.0, *) {
scrollEdgeEffectStyle(.soft, for: .all)
} else {
self
}
}
}
其余模式(使用 .fullSizeContentView 的 NSWindowController、分组 Form、NavigationSplitView)适用于 macOS 14 及更高版本。