关于 macos-design-guidelines
此 Skill 将 macOS 人机界面指南转化为可操作的设计规则,并提供 SwiftUI 和 AppKit 示例,面向 Mac 应用开发者,涵盖菜单、窗口、工具栏、侧边栏和键盘交互。
主要功能
- 01菜单栏结构与行为规范
- 02窗口尺寸与生命周期指南
- 03工具栏自定义模式
- 04侧边栏导航指南
- 05键盘快捷键约定
适用场景
- 01设计 Mac 应用的命令菜单
- 02规划可调整大小的多窗口界面
- 03为应用操作添加键盘控制
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ehmo/ platform-design-skills --skill macos-design-guidelines安装后在 Claude Code 中输入/macos-design-guidelines确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdmetadata.jsonrules/_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 应用中不要做以下事情:
-
没有菜单栏 — 每个 Mac 应用都需要菜单栏。就是这样。没有菜单的 Mac 应用,就像没有方向盘的汽车。
-
汉堡菜单 — 切勿在 Mac 上使用汉堡菜单。菜单栏就是为此而设的。汉堡菜单会让人觉得这是一个敷衍移植的 iOS 应用。
-
底部标签栏 — Mac 应用使用侧边栏和工具栏,而不是 iOS 风格的标签栏。如果需要标签,请使用标签栏中的实际文稿标签(如 Safari 或 Finder)。
-
尺寸过大的触控目标 — Mac 控件应紧凑(高度为 22-28pt)。用户可以精确地使用指针。过大的按钮浪费空间,也显得格格不入。
-
悬浮操作按钮 — FAB 是 Material Design 的设计模式。在 Mac 上,应将主要操作放在工具栏、菜单栏中,或设为行内按钮。
-
每个操作都使用工作表 — 简单操作不要使用模态工作表。应使用弹出框、行内编辑或直接操作。工作表应留给多步工作流或重要决策。
-
自定义窗口装饰 — 不要用自定义实现替换标准标题栏、红黄绿按钮或窗口控件。用户期望这些元素在所有应用中的行为保持一致。
-
忽视键盘 — 如果高级用户执行常见操作时必须使用鼠标,你的键盘支持就不够完善。
-
仅支持单窗口 — 除非你的应用确实只有一种用途(计算器、计时器),否则应支持多个窗口。用户期望按 Cmd+N 新建窗口。
-
固定窗口尺寸 — 不可调整大小的窗口在 Mac 上显得有问题。用户使用的显示器从 13" 笔记本电脑到 32" 外接显示器不等,并且期望充分利用屏幕空间。
-
不支持 Cmd+Z 撤销 — 所有破坏性操作或修改操作都必须可撤销。用户会将 Cmd+Z 作为安全保障,形成肌肉记忆。
-
通知轰炸 — 发送过多通知的 Mac 应用会被撤销通知权限。只有确实需要关注的事件才发送通知。
-
忽视深色模式 — 在深色模式下显示异常的 Mac 应用会显得无人维护。务必测试浅色和深色外观。
-
硬编码颜色 — 使用语义化系统颜色,而不是硬编码的十六进制值。颜色应自动适配浅色/深色模式和辅助功能设置。
-
不支持拖放 — Mac 是一个支持拖放的平台。如果用户能看到内容,就会期望能将其拖到别处。