mcpField
M
macos-settings-ui

macos-settings-ui

github.com/fayazara设计与 UI685660 次安装22 周前更新
代码仓库

关于 macos-settings-ui

用于构建符合 macOS 26 设计规范的原生设置窗口的实现指南,面向开发 macOS 应用的人员。它采用 NSWindowController 与 NavigationSplitView 组合,以实现 Liquid Glass 窗口外观和侧边栏设置布局。

主要功能

  1. 01NSWindowController 窗口架构
  2. 02侧边栏与详情视图导航
  3. 03Liquid Glass 窗口外观
  4. 04分组表单与透明背景
  5. 05菜单栏应用激活策略

适用场景

  1. 01为 macOS 应用创建原生设置窗口
  2. 02按类别组织设置页面和详情面板
  3. 03为菜单栏应用打开设置窗口
  4. 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.swift
SettingsView.swift
SettingsWindowController.swift

详细介绍

采用 Liquid Glass 的 macOS 设置界面

此技能会生成一个原生 macOS 设置窗口,遵循 Apple 的 macOS 26 设计指南。最终界面包含侧边栏和详情视图的 NavigationSplitView、采用 Liquid Glass 窗口边框、支持工具栏中的前进/后退导航、带透明背景的分组 Form 区段,以及正确的滚动边缘效果。

架构概览

设置界面由 3 层组成:

  1. 窗口控制器(SettingsWindowController.swift)—— 一个以编程方式创建 NSWindow 并设置 .fullSizeContentView 的 NSWindowController。这会让窗口呈现圆角 Liquid Glass 边角。使用 SwiftUI Window 场景无法实现这种效果。

  2. 根视图(SettingsView.swift)—— 包含侧边栏列表和详情窗格的 NavigationSplitView。工具栏中包含前进/后退导航历史记录,这也会强制创建 NSToolbar(这是实现 Liquid Glass 标题栏效果所必需的)。

  3. 详情窗格(每个标签页对应一个文件)—— 每个窗格都使用 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 枚举成员,使其与你的设置类别匹配
  • SettingsDetailView switch,使其返回你的窗格
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 及更高版本。

评论

支持 Markdown