关于 macos-hig-designer
用于依据 Apple 人机界面指南设计和审查原生 macOS 应用的技能,涵盖 macOS Tahoe 的 Liquid Glass。提供 SwiftUI 模式、API 更新建议、无障碍实践和界面合规检查清单。
主要功能
- 01Liquid Glass 设计指引
- 02SwiftUI 导航与窗口模式
- 03工具栏、菜单和快捷键模式
- 04无障碍实现建议
- 05弃用 API 替换方案
- 06HIG 审查清单
适用场景
- 01检查 macOS 界面代码是否符合 HIG
- 02更新旧版 SwiftUI 与 AppKit 模式
- 03设计无障碍的应用导航和窗口
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ designnotdrum/ skills --skill macos-hig-designer安装后在 Claude Code 中输入/macos-hig-designer确认已加载。
详细介绍
macOS HIG 设计指南
遵循 Apple 人机界面指南,使用 macOS Tahoe 的 Liquid Glass 设计系统打造原生 macOS 应用。
工作流决策树
User Request
│
├─► "Review my macOS UI code"
│ └─► Run HIG Compliance Check (Section 11)
│ └─► Report violations with fixes
│
├─► "Modernize this macOS code"
│ └─► Identify deprecated APIs
│ └─► Apply Modern API Replacements (Section 10)
│
└─► "Build [feature] for macOS"
└─► Design with HIG principles first
└─► Implement with modern SwiftUI patterns
1. 设计原则(macOS Tahoe)
三大核心准则
| 原则 | 描述 | 实现方式 |
|---|---|---|
| 层级 | 通过 Liquid Glass 的半透明效果呈现视觉层次 | 使用 .glassEffect()、材质和景深 |
| 和谐 | 硬件与软件之间的同心对齐 | 使用圆角、统一的半径和流畅的形状 |
| 一致性 | 根据情境调整的平台惯例 | 遵循标准模式,尊重用户偏好 |
Liquid Glass 设计理念
Liquid Glass 将透明度、反射、折射和流动感与磨砂质感相结合:
// macOS Tahoe Liquid Glass effect
.glassEffect() // Primary Liquid Glass material
.glassEffect(.regular.tinted) // Tinted variant (26.1+)
// Pre-Tahoe fallback
.background(.ultraThinMaterial)
.background(.regularMaterial)
.background(.thickMaterial)
适合使用 Liquid Glass 的场景:
- 侧边栏、工具栏和导航界面
- 浮动面板和弹出框
- Dock 和小组件背景
- 系统级界面元素
不适合使用的场景:
- 主要内容区域(文稿、媒体)
- 密集的数据展示(表格、包含大量项目的列表)
- 可读性至关重要的文本密集型界面
2. 导航模式
NavigationSplitView(主要模式)
适用于文稿类和内容密集型应用的三栏布局:
struct ContentView: View {
@State private var selection: Item.ID?
@State private var columnVisibility: NavigationSplitViewVisibility = .all
var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
// Sidebar (source list)
List(items, selection: $selection) { item in
NavigationLink(value: item) {
Label(item.title, systemImage: item.icon)
}
}
.navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 300)
} content: {
// Content column (optional middle)
ContentListView(selection: selection)
} detail: {
// Detail view
DetailView(item: selectedItem)
}
}
}
侧边栏模式
// Source list with sections
List(selection: $selection) {
Section("Library") {
ForEach(libraryItems) { item in
Label(item.name, systemImage: item.icon)
.tag(item)
}
}
Section("Collections") {
ForEach(collections) { collection in
Label(collection.name, systemImage: "folder")
.tag(collection)
.badge(collection.count)
}
}
}
.listStyle(.sidebar)
检视器面板(尾随侧边栏)
struct DocumentView: View {
@State private var showInspector = true
var body: some View {
MainContentView()
.inspector(isPresented: $showInspector) {
InspectorView()
.inspectorColumnWidth(min: 200, ideal: 250, max: 400)
}
.toolbar {
ToolbarItem {
Button {
showInspector.toggle()
} label: {
Label("Inspector", systemImage: "sidebar.trailing")
}
}
}
}
}
3. 窗口管理
窗口配置
@main
struct MyApp: App {
var body: some Scene {
// Main document window
WindowGroup {
ContentView()
}
.windowStyle(.automatic)
.windowToolbarStyle(.unified)
.defaultSize(width: 900, height: 600)
.defaultPosition(.center)
// Settings window
Settings {
SettingsView()
}
// Utility window
Window("Inspector", id: "inspector") {
InspectorWindow()
}
.windowStyle(.plain)
.windowResizability(.contentSize)
.defaultPosition(.topTrailing)
// Menu bar extra
MenuBarExtra("Status", systemImage: "circle.fill") {
StatusMenu()
}
.menuBarExtraStyle(.window)
}
}
窗口样式
| 样式 | 适用场景 |
|---|---|
.automatic | 标准应用窗口 |
.hiddenTitleBar | 以内容为中心的窗口(媒体播放器) |
.plain | 实用工具窗口、面板 |
.unified | 集成式工具栏外观 |
.unifiedCompact | 紧凑工具栏高度 |
窗口状态恢复
WindowGroup {
ContentView()
}
.handlesExternalEvents(matching: Set(arrayLiteral: "main"))
.commands {
CommandGroup(replacing: .newItem) {
Button("New Document") {
// Handle new document
}
.keyboardShortcut("n")
}
}
基于文稿的应用
@main
struct DocumentApp: App {
var body: some Scene {
DocumentGroup(newDocument: MyDocument()) { file in
DocumentView(document: file.$document)
}
.commands {
CommandGroup(after: .saveItem) {
Button("Export...") { }
.keyboardShortcut("e", modifiers: [.command, .shift])
}
}
}
}
struct MyDocument: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
init(configuration: ReadConfiguration) throws { }
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { }
}
4. 工具栏和菜单栏
工具栏配置
.toolbar {
// Leading items (macOS places these before title)
ToolbarItem(placement: .navigation) {
Button(action: goBack) {
Label("Back", systemImage: "chevron.left")
}
}
// Principal (centered)
ToolbarItem(placement: .principal) {
Picker("View Mode", selection: $viewMode) {
Label("Icons", systemImage: "square.grid.2x2").tag(ViewMode.icons)
Label("List", systemImage: "list.bullet").tag(ViewMode.list)
}
.pickerStyle(.segmented)
}
// Trailing items
ToolbarItemGroup(placement: .primaryAction) {
Button(action: share) {
Label("Share", systemImage: "square.and.arrow.up")
}
Button(action: toggleInspector) {
Label("Inspector", systemImage: "sidebar.trailing")
}
}
}
.toolbarRole(.editor) // or .browser, .automatic
自定义菜单栏
.commands {
// Replace existing menu group
CommandGroup(replacing: .newItem) {
Button("New Project") { }
.keyboardShortcut("n")
Button("New from Template...") { }
.keyboardShortcut("n", modifiers: [.command, .shift])
}
// Add to existing group
CommandGroup(after: .sidebar) {
Button("Toggle Inspector") { }
.keyboardShortcut("i", modifiers: [.command, .option])
}
// Custom menu
CommandMenu("Canvas") {
Button("Zoom In") { }
.keyboardShortcut("+")
Button("Zoom Out") { }
.keyboardShortcut("-")
Divider()
Button("Fit to Window") { }
.keyboardShortcut("0")
}
}
菜单栏应用
MenuBarExtra("App Status", systemImage: statusIcon) {
VStack(alignment: .leading, spacing: 8) {
Text("Status: \(status)")
.font(.headline)
Divider()
Button("Open Main Window") {
openWindow(id: "main")
}
Button("Quit") {
NSApplication.shared.terminate(nil)
}
.keyboardShortcut("q")
}
.padding()
}
.menuBarExtraStyle(.window) // or .menu for simple dropdown
5. 键盘快捷键
macOS 标准快捷键
在适用时始终实现以下快捷键:
| 操作 | 快捷键 | 实现方式 |
|---|---|---|
| 新建 | ⌘N | .keyboardShortcut("n") |
| 打开 | ⌘O | .keyboardShortcut("o") |
| 保存 | ⌘S | .keyboardShortcut("s") |
| 关闭 | ⌘W | .keyboardShortcut("w") |
| 撤销 | ⌘Z | .keyboardShortcut("z") |
| 重做 | ⇧⌘Z | .keyboardShortcut("z", modifiers: [.command, .shift]) |
| 剪切 | ⌘X | .keyboardShortcut("x") |
| 拷贝 | ⌘C | .keyboardShortcut("c") |
| 粘贴 | ⌘V | .keyboardShortcut("v") |
| 全选 | ⌘A | .keyboardShortcut("a") |
| 查找 | ⌘F | .keyboardShortcut("f") |
| 设置 | ⌘, | .keyboardShortcut(",") |
| 隐藏 | ⌘H | 由系统处理 |
| 退出 | ⌘Q | 由系统处理 |
自定义快捷键
Button("Toggle Sidebar") {
toggleSidebar()
}
.keyboardShortcut("s", modifiers: [.command, .control])
// Function keys
Button("Refresh") { }
.keyboardShortcut(KeyEquivalent.init(Character(UnicodeScalar(NSF5FunctionKey)!)))
// Arrow keys
Button("Next") { }
.keyboardShortcut(.rightArrow)
6. 使用 Liquid Glass 的组件
控件尺寸
| 尺寸 | 形状 | 适用场景 |
|---|---|---|
| 极小 | 圆角矩形 | 紧凑工具栏、密集界面 |
| 小 | 圆角矩形 | 次要控件、侧边栏 |
| 标准 | 圆角矩形 | 主要控件(默认) |
| 大 | 胶囊形 | 突出操作 |
| 特大 | 胶囊形 + Glass | 主视觉号召性操作、引导流程 |
// Size modifiers
Button("Action") { }
.controlSize(.mini) // Smallest
.controlSize(.small) // Compact
.controlSize(.regular) // Default
.controlSize(.large) // Prominent
.controlSize(.extraLarge) // Hero (macOS 15+)
按钮
// Primary action (prominent)
Button("Save Changes") { }
.buttonStyle(.borderedProminent)
.controlSize(.large)
// Secondary action
Button("Cancel") { }
.buttonStyle(.bordered)
// Tertiary/link style
Button("Learn More") { }
.buttonStyle(.plain)
.foregroundStyle(.link)
// Destructive
Button("Delete", role: .destructive) { }
.buttonStyle(.bordered)
// Toolbar button
Button { } label: {
Label("Add", systemImage: "plus")
}
.buttonStyle(.borderless)
文本字段
// Standard text field
TextField("Search", text: $query)
.textFieldStyle(.roundedBorder)
// Search field with tokens
TextField("Search", text: $query)
.searchable(text: $query, tokens: $tokens) { token in
Label(token.name, systemImage: token.icon)
}
// Secure field
SecureField("Password", text: $password)
.textFieldStyle(.roundedBorder)
表格
Table(items, selection: $selection) {
TableColumn("Name", value: \.name)
.width(min: 100, ideal: 150)
TableColumn("Date") { item in
Text(item.date, format: .dateTime)
}
.width(100)
TableColumn("Status") { item in
StatusBadge(status: item.status)
}
.width(80)
}
.tableStyle(.inset(alternatesRowBackgrounds: true))
.contextMenu(forSelectionType: Item.ID.self) { selection in
Button("Open") { }
Button("Delete", role: .destructive) { }
}
弹出框和工作表
// Popover
Button("Info") {
showPopover = true
}
.popover(isPresented: $showPopover, arrowEdge: .bottom) {
InfoView()
.frame(width: 300, height: 200)
.padding()
}
// Sheet
.sheet(isPresented: $showSheet) {
SheetContent()
.frame(minWidth: 400, minHeight: 300)
}
// Alert
.alert("Delete Item?", isPresented: $showAlert) {
Button("Cancel", role: .cancel) { }
Button("Delete", role: .destructive) {
deleteItem()
}
} message: {
Text("This action cannot be undone.")
}
7. 字体排版和颜色
系统字体排版
// Semantic styles (preferred)
Text("Title").font(.largeTitle) // 26pt bold
Text("Headline").font(.headline) // 13pt semibold
Text("Subheadline").font(.subheadline) // 11pt regular
Text("Body").font(.body) // 13pt regular
Text("Callout").font(.callout) // 12pt regular
Text("Caption").font(.caption) // 10pt regular
Text("Caption 2").font(.caption2) // 10pt regular
// Monospaced for code
Text("let x = 1").font(.system(.body, design: .monospaced))
语义颜色
// Foreground
.foregroundStyle(.primary) // Primary text
.foregroundStyle(.secondary) // Secondary text
.foregroundStyle(.tertiary) // Tertiary text
.foregroundStyle(.quaternary) // Quaternary text
// Backgrounds
.background(.background) // Window background
.background(.regularMaterial) // Translucent material
// Accent colors
.tint(.accentColor) // App accent color
.foregroundStyle(.link) // Clickable links
// Semantic colors
Color.red // System red (adapts to light/dark)
Color.blue // System blue
活力效果和材质
// Materials (adapt to background content)
.background(.ultraThinMaterial) // Most transparent
.background(.thinMaterial)
.background(.regularMaterial) // Default
.background(.thickMaterial)
.background(.ultraThickMaterial) // Least transparent
// Vibrancy in sidebars
List { }
.listStyle(.sidebar)
.scrollContentBackground(.hidden)
.background(.ultraThinMaterial)
8. 间距和布局
8 点网格系统
// Standard spacing values
VStack(spacing: 8) { } // Standard
VStack(spacing: 16) { } // Section spacing
VStack(spacing: 20) { } // Group spacing
// Padding
.padding(8) // Tight
.padding(12) // Standard
.padding(16) // Comfortable
.padding(20) // Spacious
// Content margins
.contentMargins(16) // Uniform margins
.contentMargins(.horizontal, 20) // Horizontal only
安全区域
// Respect toolbar safe area
.safeAreaInset(edge: .top) {
ToolbarContent()
}
// Ignore safe area for backgrounds
.ignoresSafeArea(.container, edges: .top)
// Content that should avoid toolbar
.safeAreaPadding(.top)
最小触控/点击目标
// Minimum 44x44 points for clickable elements
Button { } label: {
Image(systemName: "gear")
}
.frame(minWidth: 44, minHeight: 44)
// Use contentShape for larger hit areas
RoundedRectangle(cornerRadius: 8)
.frame(width: 200, height: 100)
.contentShape(Rectangle())
.onTapGesture { }
自适应布局
// Responsive to window size
GeometryReader { geometry in
if geometry.size.width > 600 {
HStack { content }
} else {
VStack { content }
}
}
// Grid that adapts
LazyVGrid(columns: [
GridItem(.adaptive(minimum: 150, maximum: 250))
], spacing: 16) {
ForEach(items) { ItemView(item: $0) }
}
9. 辅助功能
VoiceOver
// Labels and hints
Button { } label: {
Image(systemName: "plus")
}
.accessibilityLabel("Add item")
.accessibilityHint("Creates a new item in your library")
// Grouping related elements
VStack {
Text(item.title)
Text(item.subtitle)
}
.accessibilityElement(children: .combine)
// Custom actions
.accessibilityAction(named: "Delete") {
deleteItem()
}
键盘导航
// Focus management
@FocusState private var focusedField: Field?
TextField("Name", text: $name)
.focused($focusedField, equals: .name)
.onSubmit {
focusedField = .email
}
// Focusable custom views
.focusable()
.onMoveCommand { direction in
handleArrowKey(direction)
}
动态字体
// Scales with user preference
Text("Content")
.dynamicTypeSize(.large ... .accessibility3)
// Fixed size when necessary (use sparingly)
Text("Fixed")
.dynamicTypeSize(.large)
减少动态效果
@Environment(\.accessibilityReduceMotion) var reduceMotion
.animation(reduceMotion ? .none : .spring(), value: isExpanded)
// Alternative non-animated transitions
.transaction { transaction in
if reduceMotion {
transaction.animation = nil
}
}
高对比度
@Environment(\.colorSchemeContrast) var contrast
// Increase contrast when needed
.foregroundStyle(contrast == .increased ? .primary : .secondary)
10. 现代 API 替换方案
已弃用 → 现代替代方案
| 已弃用 | 现代替代方案 | 备注 |
|---|---|---|
NavigationView | NavigationSplitView / NavigationStack | macOS 使用分栏视图,简单流程使用堆栈 |
.navigationViewStyle(.columns) | NavigationSplitView | 内置分栏支持 |
使用 NavigationLink 的 List { }.listStyle(.sidebar) | NavigationSplitView 侧边栏 | 正确的分栏视图行为 |
详情视图中的 .toolbar { ToolbarItem(...) } | NavigationSplitView 上的 .toolbar | 工具栏应用于正确的作用域 |
NSWindowController | WindowGroup / Window | 纯 SwiftUI 窗口管理 |
NSMenu / NSMenuItem | .commands { } / CommandMenu | 声明式菜单 |
NSToolbar | .toolbar { } | SwiftUI 工具栏 API |
NSTouchBar | .touchBar { } | SwiftUI Touch Bar |
NSOpenPanel.begin() | .fileImporter() | SwiftUI 文件对话框 |
NSSavePanel.begin() | .fileExporter() | SwiftUI 保存对话框 |
用于材质的 .background(Color.clear) | .background(.regularMaterial) | 正确的材质支持 |
| 自定义模糊效果 | .glassEffect()(Tahoe) | 原生 Liquid Glass |
AppKit 互操作(按需使用)
// Wrap AppKit view
struct NSViewWrapper: NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
// Create and configure NSView
}
func updateNSView(_ nsView: NSView, context: Context) {
// Update when SwiftUI state changes
}
}
// Access NSWindow
.background(WindowAccessor { window in
window?.titlebarAppearsTransparent = true
})
11. 审查清单
Liquid Glass 与材质
- 侧边栏使用
.glassEffect()或适当的材质 - 工具栏具有半透明外观
- 内容区域保持清晰(不过度透明)
- 材质能正确适配浅色/深色模式
- 为 Tahoe 之前的版本提供备用材质
导航与窗口
- 多栏布局使用
NavigationSplitView - 侧边栏设有适当的最小/最大宽度限制
- 提供检查器面板以显示详情/属性
- 已配置窗口恢复
- 基于文稿的应用使用
DocumentGroup - 已测试多种窗口尺寸
控件与交互
- 控件尺寸适合使用场景
- 主要操作使用
.borderedProminent - 使用
.destructive角色正确标记破坏性操作 - 表格具有上下文菜单
- 弹出框尺寸合适
键盘与快捷键
- 已实现标准快捷键(⌘N、⌘S、⌘W 等)
- 自定义快捷键不会与系统快捷键冲突
- 所有交互元素均可通过键盘访问
- 焦点顺序符合逻辑
- 菜单栏命令具有快捷键
辅助功能
- 所有图像均有辅助功能标签
- 自定义控件具有正确的角色
- 已测试 VoiceOver
- 键盘导航完整
- 遵循“减少动态效果”设置
- 已测试“高对比度”模式
平台约定
- 应用使用系统外观(而非自定义窗口装饰)
- 设置位于“偏好设置”窗口中(而非单独窗口)
- 文件对话框使用系统工作表
- 在适用场景支持拖放
- 集成“服务”菜单(如适用)
视觉润色
- 对齐 8 点网格
- 全局间距一致
- 使用语义颜色(可适配主题)
- 字体排印遵循 SF Pro 指南
- 触控目标最小尺寸为 44 pt
快速参考:按尺寸划分的控件形状
┌─────────────────────────────────────────────────────┐
│ Mini/Small/Medium │ Large/XLarge │
│ ┌──────────────────┐ │ ╭──────────────────╮ │
│ │ Rounded Rect │ │ │ Capsule │ │
│ └──────────────────┘ │ ╰──────────────────╯ │
│ │ │
│ Compact layouts │ Hero actions │
│ Toolbars, sidebars │ Onboarding, CTAs │
└─────────────────────────────────────────────────────┘
快速参考:窗口样式
┌─────────────────────────────────────────────────────┐
│ .automatic Standard window with titlebar │
│ .hiddenTitleBar Full content, titlebar hidden │
│ .plain No chrome, utility panels │
│ .unified Toolbar merges with titlebar │
│ .unifiedCompact Compact unified toolbar │
└─────────────────────────────────────────────────────┘
快速参考:导航模式
┌─────────────┬───────────────┬─────────────────────┐
│ Sidebar │ Content │ Detail │
│ │ │ │
│ Source │ List or │ Selected item │
│ List │ Grid │ properties │
│ │ │ │
│ Collections│ Items │ Inspector panel │
│ Folders │ Browse │ Edit view │
│ │ │ │
│ Min: 180 │ Flexible │ Min: 300 │
│ Max: 300 │ │ Ideal: 400+ │
└─────────────┴───────────────┴─────────────────────┘
NavigationSplitView (three-column)
评论
支持 Markdown