mcpField
M
macos-design-guidelines

macos-design-guidelines

github.com/ehmo设计与 UI6314.3k 次安装32 周前更新
代码仓库

关于 macos-design-guidelines

此 Skill 将 macOS 人机界面指南转化为可操作的设计规则,并提供 SwiftUI 和 AppKit 示例,面向 Mac 应用开发者,涵盖菜单、窗口、工具栏、侧边栏和键盘交互。

主要功能

  1. 01菜单栏结构与行为规范
  2. 02窗口尺寸与生命周期指南
  3. 03工具栏自定义模式
  4. 04侧边栏导航指南
  5. 05键盘快捷键约定

适用场景

  1. 01设计 Mac 应用的命令菜单
  2. 02规划可调整大小的多窗口界面
  3. 03为应用操作添加键盘控制

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ehmo/platform-design-skills --skill macos-design-guidelines

安装后在 Claude Code 中输入/macos-design-guidelines确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
metadata.json
rules/
_sections.md

详细介绍

macOS 人机界面指南

Mac 应用面向高级用户,他们期待强大的键盘操控能力、常驻菜单栏、可调整大小的多窗口布局,以及与系统的紧密集成。这些指南将 Apple 的 HIG 转化为可执行的规则,并提供 SwiftUI 和 AppKit 示例。


1. 菜单栏(关键)

每个 Mac 应用都必须有菜单栏。它是用户查找命令的主要途径。找不到某项功能的用户,会先去菜单栏查找。

规则 1.1 — 提供标准菜单

每个应用至少必须包含:App、File、Edit、View、Window、Help。仅当应用不基于文稿时,才可省略 File。在 Edit 与 View 之间,或 View 与 Window 之间添加应用专属菜单。

// SwiftUI — Standard menu structure
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            // Adds to existing standard menus
            CommandGroup(after: .newItem) {
                Button("New from Template...") { newFromTemplate() }
                    .keyboardShortcut("T", modifiers: [.command, .shift])
            }
            CommandMenu("Canvas") {
                Button("Zoom to Fit") { zoomToFit() }
                    .keyboardShortcut("0", modifiers: .command)
                Divider()
                Button("Add Artboard") { addArtboard() }
                    .keyboardShortcut("A", modifiers: [.command, .shift])
            }
        }
    }
}
// AppKit — Building menus programmatically
let editMenu = NSMenu(title: "Edit")
let undoItem = NSMenuItem(title: "Undo", action: #selector(UndoManager.undo), keyEquivalent: "z")
let redoItem = NSMenuItem(title: "Redo", action: #selector(UndoManager.redo), keyEquivalent: "Z")
editMenu.addItem(undoItem)
editMenu.addItem(redoItem)
editMenu.addItem(.separator())
规则 1.2 — 为所有菜单项设置键盘快捷键

每个会执行操作的菜单项都必须有键盘快捷键。标准操作使用标准快捷键(Cmd+C、Cmd+V、Cmd+Z 等)。自定义快捷键应使用 Cmd 加一个字母。将 Cmd+Shift、Cmd+Option 和 Cmd+Ctrl 组合键留给次要操作。

标准快捷键参考:

操作快捷键
新建Cmd+N
打开Cmd+O
关闭Cmd+W
保存Cmd+S
另存为Cmd+Shift+S
打印Cmd+P
撤销Cmd+Z
重做Cmd+Shift+Z
剪切Cmd+X
拷贝Cmd+C
粘贴Cmd+V
全选Cmd+A
查找Cmd+F
查找下一个Cmd+G
偏好设置/设置Cmd+,
隐藏应用Cmd+H
退出Cmd+Q
最小化Cmd+M
全屏Cmd+Ctrl+F
规则 1.3 — 动态更新菜单

菜单项必须反映当前状态。禁用不适用的菜单项。根据上下文更新标题(例如,显示“撤销输入”,而不只是“撤销”)。通过切换勾选标记来表示开/关状态。

// SwiftUI — Add sidebar toggle alongside existing toolbar menu commands
CommandGroup(after: .toolbar) {
    Button(showingSidebar ? "Hide Sidebar" : "Show Sidebar") {
        showingSidebar.toggle()
    }
    .keyboardShortcut("S", modifiers: [.command, .control])
}
// AppKit — Validate menu items
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
    if menuItem.action == #selector(delete(_:)) {
        menuItem.title = selectedItems.count > 1 ? "Delete \(selectedItems.count) Items" : "Delete"
        return !selectedItems.isEmpty
    }
    return super.validateMenuItem(menuItem)
}
规则 1.4 — 上下文菜单

为所有可交互元素提供右键上下文菜单。上下文菜单应包含与所点击元素最相关的菜单栏操作子集,以及针对该元素的操作。

// SwiftUI
Text(item.name)
    .contextMenu {
        Button("Rename...") { rename(item) }
        Button("Duplicate") { duplicate(item) }
        Divider()
        Button("Delete", role: .destructive) { delete(item) }
    }
规则 1.5 — App 菜单结构

App 菜单(最左侧,以粗体显示应用名称)必须包含:关于、偏好设置/设置(Cmd+,)、服务子菜单、隐藏应用(Cmd+H)、隐藏其他应用(Cmd+Option+H)、显示全部、退出(Cmd+Q)。切勿重命名或移除这些标准项目。

// SwiftUI — Settings scene
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
        Settings { SettingsView() }  // Automatically wired to Cmd+,
    }
}
规则 1.6 — 稳定的命令名称和位置

将菜单栏视为应用的命令记忆。将常用操作放在固定菜单中,并保持名称和快捷键稳定,让用户能够快速识别,而不必寻找依赖上下文的变体。


2. 窗口(关键)

Mac 用户期待完全掌控窗口的大小、位置和生命周期。与窗口管理相冲突的应用,在 Mac 上会让人感觉根本无法正常使用。

规则 2.1 — 可调整大小,并设置合理的最小尺寸

所有主窗口都必须允许自由调整大小。设置能够确保界面可用的最小尺寸。除非内容确实无法缩放(这种情况很少见),否则绝不要设置最大尺寸。

// SwiftUI
WindowGroup {
    ContentView()
        .frame(minWidth: 600, minHeight: 400)
}
.defaultSize(width: 900, height: 600)
// AppKit
window.minSize = NSSize(width: 600, height: 400)
window.setContentSize(NSSize(width: 900, height: 600))
规则 2.2 — 支持全屏和分屏浏览

设置适当的窗口集合行为,以启用原生全屏支持。绿色交通灯按钮必须能够进入全屏,或显示平铺选择器。

// AppKit
window.collectionBehavior.insert(.fullScreenPrimary)

SwiftUI 窗口会自动支持全屏。

规则 2.3 — 多窗口

除非应用是单用途实用工具,否则应支持多个窗口。基于文稿的应用必须允许同时打开多份文稿。在 SwiftUI 中使用 WindowGroup 或 DocumentGroup。

// SwiftUI — Document-based app
@main
struct TextEditorApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: TextDocument()) { file in
            TextEditorView(document: file.$document)
        }
    }
}
规则 2.4 — 标题栏显示文稿信息

对于基于文稿的应用,标题栏必须显示文稿名称。支持拖动代理图标。显示已编辑状态(关闭按钮中的圆点)。支持点击标题栏重命名。

// AppKit
window.representedURL = document.fileURL
window.title = document.displayName
window.isDocumentEdited = document.hasUnsavedChanges
// SwiftUI — NavigationSplitView titles
NavigationSplitView {
    SidebarView()
} detail: {
    DetailView()
        .navigationTitle(document.name)
}
规则 2.5 — 记住窗口状态

在应用重新启动后,保留窗口的位置、大小和状态。使用 NSWindow.setFrameAutosaveName 或 SwiftUI 内置的状态恢复功能。

// AppKit
window.setFrameAutosaveName("MainWindow")

// SwiftUI — Automatic with WindowGroup
WindowGroup(id: "main") {
    ContentView()
}
.defaultPosition(.center)
规则 2.6 — 红绿灯按钮

切勿隐藏或重新定位关闭(红色)、最小化(黄色)或缩放(绿色)按钮。它们必须保留在左上角。如果使用自定义标题栏,这些按钮仍必须可见且可正常使用。

// AppKit — Custom title bar that preserves traffic lights
window.titlebarAppearsTransparent = true
window.styleMask.insert(.fullSizeContentView)
// Traffic lights remain functional and visible

3. 工具栏(高优先级)

工具栏是菜单栏之后的次要命令区域。它们提供对常用操作的快速访问,并且应支持自定义。

规则 3.1 — 统一标题栏和工具栏

使用统一标题栏 + 工具栏样式,打造现代外观。工具栏位于标题栏区域,可节省垂直空间。

// SwiftUI
WindowGroup {
    ContentView()
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(action: compose) {
                    Label("Compose", systemImage: "square.and.pencil")
                }
            }
        }
}
.windowToolbarStyle(.unified)
// AppKit
window.titleVisibility = .hidden
window.toolbarStyle = .unified
规则 3.2 — 用户可自定义的工具栏

允许用户添加、移除和重新排列工具栏项目。提供一组默认项目,以及一组包含所有可用项目的完整项目集。

// SwiftUI — Customizable toolbar
.toolbar(id: "main") {
    ToolbarItem(id: "compose", placement: .primaryAction) {
        Button(action: compose) {
            Label("Compose", systemImage: "square.and.pencil")
        }
    }
    ToolbarItem(id: "filter", placement: .secondaryAction) {
        Button(action: toggleFilter) {
            Label("Filter", systemImage: "line.3.horizontal.decrease")
        }
    }
}
.toolbarRole(.editor)
规则 3.3 — 使用分段控件切换视图

使用工具栏中的分段控件或选择器,在不同内容视图(例如列表/网格/分栏)之间切换。这是工具栏模式,不是标签栏。

// SwiftUI
ToolbarItem(placement: .principal) {
    Picker("View Mode", selection: $viewMode) {
        Label("List", systemImage: "list.bullet").tag(ViewMode.list)
        Label("Grid", systemImage: "square.grid.2x2").tag(ViewMode.grid)
        Label("Column", systemImage: "rectangle.split.3x1").tag(ViewMode.column)
    }
    .pickerStyle(.segmented)
}
规则 3.4 — 工具栏中的搜索栏

将搜索栏放在工具栏靠后的一侧。在 SwiftUI 中使用 .searchable(),以实现包含建议和令牌的标准搜索行为。

// SwiftUI
NavigationSplitView {
    SidebarView()
} detail: {
    ContentListView()
        .searchable(text: $searchText, placement: .toolbar, prompt: "Search items")
        .searchSuggestions {
            ForEach(suggestions) { suggestion in
                Text(suggestion.title).searchCompletion(suggestion.title)
            }
        }
}
规则 3.5 — 工具栏标签和图标

工具栏项目应同时包含图标(SF Symbol)和文本标签。在紧凑模式下,仅显示图标。优先使用带标签的图标,以便用户识别。使用 Label 同时提供二者。


4. 侧边栏(高优先级)

侧边栏是 Mac 应用的主要导航区域。它们显示在前导边缘,可供用户持续访问顶级分区和内容库。

规则 4.1 — 位于前导边缘且可折叠

将侧边栏放在左侧(前导)边缘。允许用户通过工具栏按钮或键盘快捷键将其折叠。Apple 没有定义通用的侧边栏快捷键,请根据应用选择合适的快捷键(例如,Cmd+Ctrl+S 很常见,但不能保证在所有应用中都未被占用)。保留折叠状态。

// SwiftUI
NavigationSplitView(columnVisibility: $columnVisibility) {
    List(selection: $selection) {
        Section("Library") {
            Label("All Items", systemImage: "tray.full")
            Label("Favorites", systemImage: "star")
            Label("Recent", systemImage: "clock")
        }
        Section("Tags") {
            ForEach(tags) { tag in
                Label(tag.name, systemImage: "tag")
            }
        }
    }
    .navigationSplitViewColumnWidth(min: 180, ideal: 220, max: 320)
} detail: {
    DetailView(selection: selection)
}
.navigationSplitViewStyle(.prominentDetail)
规则 4.2 — 源列表样式

对于内容库导航,使用源列表样式(.listStyle(.sidebar))。源列表具有半透明背景,可通过活力效果显示其后的桌面或窗口。

// SwiftUI
List(selection: $selection) {
    ForEach(sections) { section in
        Section(section.name) {
            ForEach(section.items) { item in
                NavigationLink(value: item) {
                    Label(item.name, systemImage: item.icon)
                }
            }
        }
    }
}
.listStyle(.sidebar)
规则 4.3 — 使用大纲视图展示层级

当内容具有层级结构(例如文件夹树、项目结构)时,使用展开组或大纲视图,让用户展开和折叠各级内容。

// SwiftUI — Recursive outline
List(selection: $selection) {
    OutlineGroup(rootNodes, children: \.children) { node in
        Label(node.name, systemImage: node.icon)
    }
}
规则 4.4 — 拖动以重新排序

支持重新排序的侧边栏项目(书签、收藏项、自定义分区)必须支持拖动排序。实现 onMove 或 NSOutlineView 拖动代理。

// SwiftUI
ForEach(favorites) { item in
    Label(item.name, systemImage: item.icon)
}
.onMove { source, destination in
    favorites.move(fromOffsets: source, toOffset: destination)
}
规则 4.5 — 徽标计数

在侧边栏项目上显示徽标计数,用于表示未读数量、待处理项目或通知。使用 .badge() 修饰符。

// SwiftUI
Label("Inbox", systemImage: "tray")
    .badge(unreadCount)

5. 键盘(关键)

Mac 用户对键盘快捷键的依赖超过任何其他平台。缺少全面键盘支持的应用就不是合格的 Mac 应用。

规则 5.1 — 所有操作都要有 Cmd 快捷键

每个可用鼠标执行的操作都必须有对应的键盘操作。主要操作使用 Cmd+字母。次要操作使用 Cmd+Shift 或 Cmd+Option。第三级操作使用 Cmd+Ctrl。

键盘快捷键约定:

修饰键模式用途
Cmd+字母主要操作(新建、打开、保存等)
Cmd+Shift+字母主要操作的变体(另存为、查找上一个)
Cmd+Option+字母替代模式(粘贴并匹配样式)
Cmd+Ctrl+字母窗口/视图控制(全屏、侧边栏)
Ctrl+字母Emacs 风格的文本导航(可接受)
Fn+键系统功能(F11 显示桌面等)
规则 5.2 — 完整的键盘导航

支持使用 Tab 在控件之间移动。支持在列表、网格和表格中使用方向键。支持使用 Shift+Tab 反向导航。在 SwiftUI 中使用 focusable() 和 @FocusState。

// SwiftUI — Focus management
struct ContentView: View {
    @FocusState private var focusedField: Field?

    var body: some View {
        VStack {
            TextField("Name", text: $name)
                .focused($focusedField, equals: .name)
            TextField("Email", text: $email)
                .focused($focusedField, equals: .email)
        }
        .onSubmit { advanceFocus() }
    }
}
规则 5.3 — 按 Esc 取消或关闭

按 Esc 必须关闭弹出框、工作表和对话框,并取消正在进行的操作。在文本字段中,按 Esc 会恢复为之前的值。在模态对话框中,按 Esc 等同于点击“取消”。

// SwiftUI — Sheet with Esc support (automatic)
.sheet(isPresented: $showingSheet) {
    SheetView()  // Esc dismisses automatically
}

// AppKit — Custom responder
override func cancelOperation(_ sender: Any?) {
    dismiss(nil)
}
规则 5.4 — 按 Return 执行默认操作

在对话框和表单中,按 Return/Enter 会激活默认按钮(以蓝色突出显示)。默认按钮始终是最安全的主要操作。

// SwiftUI
Button("Save") { save() }
    .keyboardShortcut(.defaultAction)  // Enter key

Button("Cancel") { cancel() }
    .keyboardShortcut(.cancelAction)   // Esc key
规则 5.5 — 按 Delete 移除

Delete 键(Backspace)必须从列表、表格和集合中移除所选项目。按 Cmd+Delete 可执行更具破坏性的移除操作(移到废纸篓)。始终支持按 Cmd+Z 撤销删除。

规则 5.6 — 按空格键快速查看

当项目支持预览时,按空格键应调用“快速查看”。在 AppKit 中使用 QLPreviewPanel API,或在 SwiftUI 中使用 .quickLookPreview()。

// SwiftUI
List(selection: $selection) {
    ForEach(files) { file in
        FileRow(file: file)
    }
}
.quickLookPreview($quickLookItem, in: files)
规则 5.7 — 使用方向键导航

在列表和网格中,按向上/向下方向键移动选择项。按向左/向右方向键折叠/展开展开组或切换列。按 Cmd+向上键跳到开头,按 Cmd+向下键跳到末尾。


6. 指针和鼠标(高)

Mac 是以指针为主的操作平台。每个交互元素都必须响应悬停、点击、右键点击和拖动。

规则 6.1 — 悬停状态

所有交互元素都必须有明显的悬停状态。按钮应高亮显示,行应显示选择指示,链接应更改光标。SwiftUI 中使用 .onHover。

// SwiftUI — Hover effect
struct HoverableRow: View {
    @State private var isHovered = false

    var body: some View {
        HStack {
            Text(item.name)
            Spacer()
            if isHovered {
                Button("Edit") { edit() }
                    .buttonStyle(.borderless)
            }
        }
        .padding(8)
        .background(isHovered ? Color.primary.opacity(0.05) : .clear)
        .cornerRadius(6)
        .onHover { hovering in isHovered = hovering }
    }
}
规则 6.2 — 右键上下文菜单

每个交互元素都必须响应右键点击并显示上下文菜单。上下文菜单应包含与所点击项目最相关的操作。

规则 6.3 — 拖放

支持通过拖放操作内容:重新排序项目、在容器之间移动项目、从 Finder 导入文件,以及导出内容。

// SwiftUI — Drag and drop
ForEach(items) { item in
    ItemView(item: item)
        .draggable(item)
}
.dropDestination(for: Item.self) { items, location in
    handleDrop(items, at: location)
    return true
}
// Accepting file drops from Finder
.dropDestination(for: URL.self) { urls, location in
    importFiles(urls)
    return true
}
规则 6.4 — 滚动行为

同时支持触控板(平滑/惯性)和鼠标滚轮(分段)滚动。在内容边界处使用弹性/回弹滚动。在适当情况下支持水平滚动。

规则 6.5 — 光标变化

更改光标以指示可执行的操作:可点击元素使用指针,文本使用 I 形光标,绘图使用十字线,窗口/分隔条边缘使用调整大小手柄,可拖动内容使用抓取手形光标。

// AppKit — Custom cursor
override func resetCursorRects() {
    addCursorRect(bounds, cursor: .crosshair)
}
规则 6.6 — 多选

在列表、表格和网格中,支持按 Cmd+点击进行非连续选择,按 Shift+点击进行范围选择。这是 Mac 用户早已习惯的交互模式。

// SwiftUI — Tables with multi-selection
Table(items, selection: $selectedItems) {
    TableColumn("Name", value: \.name)
    TableColumn("Date", value: \.dateFormatted)
    TableColumn("Size", value: \.sizeFormatted)
}

7. 通知和提醒(中)

Mac 用户非常重视不被打扰。只有在确有必要时才打断他们。

规则 7.1 — 合理使用通知中心

仅针对应用外发生的事件或需要用户采取操作的事件发送通知。常规操作绝不发送通知。通知必须可供用户采取操作。

// UserNotifications
let content = UNMutableNotificationContent()
content.title = "Download Complete"
content.body = "project-assets.zip is ready"
content.categoryIdentifier = "DOWNLOAD"
content.sound = .default

let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
规则 7.2 — 提供关闭重复提醒的选项

对于重复出现的提醒,提供“不要再显示”复选框。尊重用户的选择并予以保存。

// AppKit — Alert with suppression
let alert = NSAlert()
alert.messageText = "Remove from library?"
alert.informativeText = "The file will be moved to the Trash."
alert.alertStyle = .warning
alert.addButton(withTitle: "Remove")
alert.addButton(withTitle: "Cancel")
alert.showsSuppressionButton = true
alert.suppressionButton?.title = "Do not ask again"

let response = alert.runModal()
if alert.suppressionButton?.state == .on {
    UserDefaults.standard.set(true, forKey: "suppressRemoveAlert")
}
规则 7.3 — 不要进行不必要的打扰

操作成功时绝不显示提醒。改用行内状态指示器、工具栏徽标或细微动画。仅在涉及破坏性或不可逆操作时使用模态提醒。

规则 7.4 — Dock 徽标

在 Dock 图标上显示通知数量徽标。用户处理通知后,应及时清除徽标。

// AppKit
NSApp.dockTile.badgeLabel = unreadCount > 0 ? "\(unreadCount)" : nil
规则 7.5 — 反馈应与认知成本相匹配

常规操作应通过行内状态、工具栏状态或细微动画确认已完成。只有当用户必须停下来评估后果并做出选择时,才使用模态提醒。


8. 系统集成(中)

Mac 应用身处丰富的生态系统中。深入集成能让应用更具原生体验。

规则 8.1 — Dock 图标和菜单

提供高质量的 1024x1024 应用图标。支持 Dock 右键菜单,提供快捷操作。在 Dock 菜单中显示最近使用的文稿。

// AppKit — Dock menu
override func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
    let menu = NSMenu()
    menu.addItem(withTitle: "New Window", action: #selector(newWindow(_:)), keyEquivalent: "")
    menu.addItem(withTitle: "New Document", action: #selector(newDocument(_:)), keyEquivalent: "")
    menu.addItem(.separator())
    for doc in recentDocuments.prefix(5) {
        menu.addItem(withTitle: doc.name, action: #selector(openRecent(_:)), keyEquivalent: "")
    }
    return menu
}
Rule 8.2 — 聚焦搜索集成

使用 CSSearchableItem 和 Core Spotlight 为应用内容建立索引,以便通过 Spotlight 搜索。用户希望使用 Cmd+Space 查找应用内容。

import CoreSpotlight

let attributeSet = CSSearchableItemAttributeSet(contentType: .text)
attributeSet.title = document.title
attributeSet.contentDescription = document.summary
attributeSet.thumbnailData = document.thumbnail?.pngData()

let item = CSSearchableItem(uniqueIdentifier: document.id, domainIdentifier: "documents", attributeSet: attributeSet)
CSSearchableIndex.default().indexSearchableItems([item])
Rule 8.3 — 支持快速查看

通过 Quick Look 预览扩展,为自定义文件类型提供快速查看预览。用户希望在 Finder 中按空格键预览任何文件。

Rule 8.4 — 共享扩展

实现“共享”菜单,让用户可以将应用中的内容分享到“信息”“邮件”“备忘录”等应用中。也要接收来自其他应用的共享内容。

// SwiftUI
ShareLink(item: document.url) {
    Label("Share", systemImage: "square.and.arrow.up")
}
Rule 8.5 — 服务菜单

注册“服务”菜单,以便接收来自其他应用的文本、URL 或文件。这是 Mac 特有的集成入口,资深用户会依赖它。

Rule 8.6 — 快捷指令和 AppleScript

通过提供 App Intents 支持“快捷指令”应用。对于高级自动化,可添加 AppleScript/JXA 脚本支持,使用 .sdef 脚本字典。

// App Intents for Shortcuts
struct CreateDocumentIntent: AppIntent {
    static var title: LocalizedStringResource = "Create Document"
    static var description = IntentDescription("Creates a new document with the given title.")

    @Parameter(title: "Title")
    var title: String

    func perform() async throws -> some IntentResult {
        let doc = DocumentManager.shared.create(title: title)
        return .result(value: doc.title)
    }
}

9. 视觉设计(高优先级)

Mac 应用的外观和体验应与平台融为一体。使用系统提供的材质、字体和颜色。

Rule 9.1 — 使用系统字体

使用 SF Pro(系统字体)和标准动态字体大小。代码使用 SF Mono。切勿硬编码字体大小;应使用语义样式。

// SwiftUI — Semantic font styles
Text("Title").font(.title)
Text("Headline").font(.headline)
Text("Body text").font(.body)
Text("Caption").font(.caption)
Text("let x = 42").font(.system(.body, design: .monospaced))
Rule 9.2 — 活力效果和材质

侧边栏和工具栏背景使用系统材质。活力效果可让桌面或底层内容透显,使应用融入 Mac 的视觉语言。

// SwiftUI
List { ... }
    .listStyle(.sidebar)  // Automatic vibrancy

// Custom vibrancy
ZStack {
    VisualEffectView(material: .sidebar, blendingMode: .behindWindow)
    Text("Sidebar Content")
}
// AppKit — Visual effect view
let visualEffect = NSVisualEffectView()
visualEffect.material = .sidebar
visualEffect.blendingMode = .behindWindow
visualEffect.state = .followsWindowActiveState
Rule 9.3 — 遵循系统强调色

在选中状态、强调内容和交互元素中使用系统强调色。标准控件切勿使用固定品牌色覆盖系统颜色。仅在适当情况下,对自定义视图使用 .accentColor 或 .tint。

// SwiftUI — Follows system accent automatically
Button("Action") { doSomething() }
    .buttonStyle(.borderedProminent)  // Uses system accent color

Toggle("Enable feature", isOn: $isEnabled)  // Toggle tint follows accent
Rule 9.4 — 支持深色模式

每个视图都必须支持浅色和深色外观。使用语义颜色(Color.primary、Color.secondary、.background),而非硬编码颜色。请在两种模式下进行测试。

// SwiftUI — Semantic colors
Text("Title").foregroundStyle(.primary)
Text("Subtitle").foregroundStyle(.secondary)

RoundedRectangle(cornerRadius: 8)
    .fill(Color(nsColor: .controlBackgroundColor))

// Asset catalog: define colors for Both Appearances
// Never use Color.white or Color.black for UI surfaces
Rule 9.5 — 半透明效果

遵循辅助功能设置中的“降低透明度”。降低透明度后,请将半透明材质替换为纯色背景。

// SwiftUI
@Environment(\.accessibilityReduceTransparency) var reduceTransparency

var body: some View {
    if reduceTransparency {
        Color(nsColor: .windowBackgroundColor)
    } else {
        VisualEffectView(material: .sidebar, blendingMode: .behindWindow)
    }
}
Rule 9.6 — 保持间距和布局一致

标准边距使用 20pt,相关控件之间的间距使用 8pt,各组之间的间距使用 20pt。将控件与网格对齐。使用 SwiftUI 内置的间距,或使用带有系统间距约束的 AppKit Auto Layout。


10. 弹出窗口(中优先级)

弹出窗口用于呈现锚定到某个控件的上下文内容。在 Mac 应用中,它们常用于选项面板、颜色选择器和上下文设置。

Rule 10.1 — 使用弹出窗口呈现临时的上下文相关内容

弹出窗口会附着在源视图上,点击外部或按下 Esc 即可将其关闭。将其用于适用于特定元素的设置或选项。不要将弹出窗口用于主要工作流或多步骤操作。

// SwiftUI
Button("Format...") { showingFormatPopover = true }
    .popover(isPresented: $showingFormatPopover, arrowEdge: .bottom) {
        FormatOptionsView()
            .frame(width: 280)
            .padding()
    }
Rule 10.2 — 按 Esc 关闭弹出窗口

用户按下 Esc 时,弹出窗口必须关闭。SwiftUI 会自动处理 .popover 的这一行为。对于 AppKit 的 NSPopover,将 behavior 设为 .transient 或 .semitransient 时,按 Esc 也会关闭弹出窗口。

Rule 10.3 — 根据内容调整弹出窗口大小

为弹出窗口内容设置合理的宽度。不要让弹出窗口宽于所需范围。除非列表本身较长(例如字体选择器),否则内容不应需要滚动。


11. 辅助功能(关键)

Mac 应用必须支持 VoiceOver、全键盘控制、切换控制及相关辅助技术。

Rule 11.1 — 为所有交互元素提供 VoiceOver 标签

每个按钮、控件和交互元素都必须有含义明确的辅助功能标签。仅含图标的工具栏项目和图像按钮必须提供标签。

正确:

Button(action: deleteSelected) {
    Image(systemName: "trash")
}
.accessibilityLabel("Delete selected items")

错误:

Button(action: deleteSelected) {
    Image(systemName: "trash")
}
// VoiceOver reads "trash" — ambiguous without context
规则 11.2 — 完整键盘访问

所有可通过鼠标执行的操作,也必须可通过键盘执行。按 Tab 键必须能在所有控件间移动焦点。方向键必须能在列表、表格和网格中导航。不得出现键盘陷阱。

// SwiftUI — Ensure all custom views are focusable
MyCustomControl()
    .focusable()
    .onKeyPress(.return) { handleActivation(); return .handled }
规则 11.3 — 遵循“减少动态效果”设置

用户启用“减少动态效果”后,应停用或替换装饰性动画。

@Environment(\.accessibilityReduceMotion) var reduceMotion

var body: some View {
    ContentView()
        .animation(reduceMotion ? nil : .spring(), value: isExpanded)
}
规则 11.4 — 遵循“减少透明度”设置

启用“减少透明度”后,应以纯色背景替换半透明材质(参见规则 9.5)。

规则 11.5 — 逻辑焦点顺序

VoiceOver 必须按照逻辑阅读顺序遍历元素(从左到右的界面中,顺序为从左上到右下)。当视觉布局与顺序不一致时,使用 .accessibilitySortPriority() 或 accessibilityElement(children:) 修正顺序。

规则 11.6 — 响应粗体文本设置

用户在系统设置中启用“粗体文本”后,自定义渲染的文本必须相应调整。SwiftUI 文本样式会自动处理此设置。对于 AppKit,请检查 NSWorkspace.shared.accessibilityDisplayShouldUseBoldText;对于 SwiftUI,可以使用 @Environment(\.legibilityWeight) 为自定义文本应用更粗的字重。

正确:

// SwiftUI — environment handles bold text automatically for standard styles
Text("Section Header")
    .font(.headline)

// SwiftUI — custom rendering responds to legibilityWeight
@Environment(\.legibilityWeight) var legibilityWeight

var body: some View {
    Text("Custom Label")
        .fontWeight(legibilityWeight == .bold ? .bold : .regular)
}

错误:

// Hardcoded weight ignores Bold Text preference
Text("Custom Label")
    .fontWeight(.regular) // Never adapts to Bold Text setting
规则 11.7 — 响应“增强对比度”设置

用户在系统设置中启用“增强对比度”后,自定义颜色必须提供对比度更高的变体。在 AppKit 中使用 NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast,或在 SwiftUI 中使用 @Environment(\.colorSchemeContrast) 检测并应用合适的值。

正确:

// SwiftUI
@Environment(\.colorSchemeContrast) var contrast

var borderColor: Color {
    contrast == .increased ? Color.primary : Color.secondary
}

// AppKit
let shouldIncrease = NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast
let borderColor: NSColor = shouldIncrease ? .labelColor : .separatorColor

错误:

// Static color ignores Increase Contrast setting
let borderColor = NSColor.separatorColor // Always low-contrast; ignores user preference

键盘快捷键速查表

导航
快捷键操作
Cmd+N新建窗口/文稿
Cmd+O打开
Cmd+W关闭窗口/标签页
Cmd+Q退出应用
Cmd+,设置/偏好设置
Cmd+Tab切换应用
Cmd+`在应用内切换窗口
Cmd+T新建标签页
编辑
快捷键操作
Cmd+Z撤销
Cmd+Shift+Z重做
Cmd+X / C / V剪切 / 复制 / 粘贴
Cmd+A全选
Cmd+D复制
Cmd+F查找
Cmd+G查找下一个
Cmd+Shift+G查找上一个
Cmd+E使用所选内容进行查找
视图
快捷键操作
Cmd+Ctrl+F切换全屏
Cmd+Ctrl+S切换侧边栏(由应用定义;并非通用的 HIG 标准)
Cmd++ / Cmd+-放大/缩小
Cmd+0实际大小

评估清单

发布 Mac 应用前,请确认:

菜单栏
  • 应用包含完整菜单栏和标准菜单
  • 所有操作都有键盘快捷键
  • 菜单项会动态更新(启用/停用、标题变化)
  • 所有交互元素都有上下文菜单
  • 应用菜单包含“关于”、“设置”、“隐藏”和“退出”
窗口
  • 窗口可自由调整大小,并设有合理的最小尺寸
  • 全屏和分屏浏览均可正常使用
  • 支持多个窗口(如适用)
  • 窗口位置和大小会在应用每次启动时保留
  • 交通灯按钮清晰可见且功能正常
  • 显示文稿标题和编辑状态(如应用以文稿为核心)
工具栏
  • 提供包含常用操作的工具栏
  • 用户可以自定义工具栏
  • 工具栏中提供搜索栏
侧边栏
  • 提供用于导航的侧边栏(如果应用包含多个部分)
  • 侧边栏可折叠
  • 使用带有活力效果的源列表样式
键盘
  • 支持完整键盘导航(Tab、方向键、Enter、Esc)
  • 所有破坏性操作均支持 Cmd+Z 撤销
  • 使用空格键查看快速预览
  • Delete 键可移除所选项目
  • 不得出现键盘陷阱(用户始终可以按 Tab 键移出)
指针
  • 交互元素具有悬停状态
  • 各处均提供右键上下文菜单
  • 支持通过拖放操作内容
  • 支持使用 Cmd+单击进行多选
  • 光标会根据情境适当变化
通知
  • 仅针对重要事件发送通知
  • 可为重复出现的提醒提供关闭选项
  • 日常操作不使用模态提醒
系统集成
  • 高品质 Dock 图标
  • 内容已编入 Spotlight 索引(如适用)
  • 分享菜单可正常使用
  • 支持 Shortcuts 的 App Intents
视觉设计
  • 在语义化尺寸下使用系统字体
  • 完全支持深色模式
  • 遵循系统强调色
  • 半透明效果遵循辅助功能设置
  • 间距统一,采用 8pt 网格
弹出框
  • 弹出框锚定到其来源元素,并用箭头指向该元素
  • 按 Esc 可关闭弹出框
  • 弹出框尺寸适应内容,无需不必要的滚动
辅助功能
  • 所有仅含图标的工具栏项目和图像按钮都有辅助功能标签
  • 所有可通过鼠标执行的操作也都可通过键盘访问(全键盘访问)
  • 启用“减少动态效果”后,停用装饰性动画
  • 启用“减少透明度”后,将半透明表面替换为纯色背景
  • VoiceOver 遍历顺序符合逻辑(从左上到右下)
  • 遵循“粗体文本”偏好设置(SwiftUI 会自动处理;AppKit 检查 accessibilityDisplayShouldUseBoldText)
  • 遵循“增强对比度”偏好设置(自定义颜色通过 colorSchemeContrast 或 accessibilityDisplayShouldIncreaseContrast 提供高对比度变体)

反模式

Mac 应用中不要做以下事情:

  1. 没有菜单栏 — 每个 Mac 应用都需要菜单栏。就是这样。没有菜单的 Mac 应用,就像没有方向盘的汽车。

  2. 汉堡菜单 — 切勿在 Mac 上使用汉堡菜单。菜单栏就是为此而设的。汉堡菜单会让人觉得这是一个敷衍移植的 iOS 应用。

  3. 底部标签栏 — Mac 应用使用侧边栏和工具栏,而不是 iOS 风格的标签栏。如果需要标签,请使用标签栏中的实际文稿标签(如 Safari 或 Finder)。

  4. 尺寸过大的触控目标 — Mac 控件应紧凑(高度为 22-28pt)。用户可以精确地使用指针。过大的按钮浪费空间,也显得格格不入。

  5. 悬浮操作按钮 — FAB 是 Material Design 的设计模式。在 Mac 上,应将主要操作放在工具栏、菜单栏中,或设为行内按钮。

  6. 每个操作都使用工作表 — 简单操作不要使用模态工作表。应使用弹出框、行内编辑或直接操作。工作表应留给多步工作流或重要决策。

  7. 自定义窗口装饰 — 不要用自定义实现替换标准标题栏、红黄绿按钮或窗口控件。用户期望这些元素在所有应用中的行为保持一致。

  8. 忽视键盘 — 如果高级用户执行常见操作时必须使用鼠标,你的键盘支持就不够完善。

  9. 仅支持单窗口 — 除非你的应用确实只有一种用途(计算器、计时器),否则应支持多个窗口。用户期望按 Cmd+N 新建窗口。

  10. 固定窗口尺寸 — 不可调整大小的窗口在 Mac 上显得有问题。用户使用的显示器从 13" 笔记本电脑到 32" 外接显示器不等,并且期望充分利用屏幕空间。

  11. 不支持 Cmd+Z 撤销 — 所有破坏性操作或修改操作都必须可撤销。用户会将 Cmd+Z 作为安全保障,形成肌肉记忆。

  12. 通知轰炸 — 发送过多通知的 Mac 应用会被撤销通知权限。只有确实需要关注的事件才发送通知。

  13. 忽视深色模式 — 在深色模式下显示异常的 Mac 应用会显得无人维护。务必测试浅色和深色外观。

  14. 硬编码颜色 — 使用语义化系统颜色,而不是硬编码的十六进制值。颜色应自动适配浅色/深色模式和辅助功能设置。

  15. 不支持拖放 — Mac 是一个支持拖放的平台。如果用户能看到内容,就会期望能将其拖到别处。

评论

支持 Markdown