macos-design-guidelines

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

macOS Human Interface Guidelines

macOS 人机界面指南

Mac apps serve power users who expect deep keyboard control, persistent menu bars, resizable multi-window layouts, and tight system integration. These guidelines codify Apple's HIG into actionable rules with SwiftUI and AppKit examples.

Mac应用面向的是对键盘深度控制、持久化菜单栏、可调整大小的多窗口布局以及紧密系统集成有需求的高级用户。本指南将Apple的HIG(人机界面指南)整理为可执行的规则,并附带SwiftUI和AppKit示例。

1. Menu Bar (CRITICAL)

1. 菜单栏(关键要求)

Every Mac app must have a menu bar. It is the primary discovery mechanism for commands. Users who cannot find a feature will look in the menu bar before anywhere else.
每个Mac应用都必须有菜单栏,它是用户查找命令的主要途径。用户如果找不到某个功能,首先会查看菜单栏。

Rule 1.1 — Provide Standard Menus

规则1.1 — 提供标准菜单

Every app must include at minimum: App, File, Edit, View, Window, Help. Omit File only if the app is not document-based. Add app-specific menus between Edit and View or between View and Window.
swift
// 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])
            }
        }
    }
}
swift
// 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())
每个应用至少必须包含:应用文件编辑视图窗口帮助菜单。如果应用不是基于文档的,可以省略“文件”菜单。在“编辑”和“视图”之间,或“视图”和“窗口”之间添加应用专属菜单。
swift
// SwiftUI — 标准菜单结构
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            // 向现有标准菜单添加内容
            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])
            }
        }
    }
}
swift
// AppKit — 以编程方式构建菜单
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())

Rule 1.2 — Keyboard Shortcuts for All Menu Items

规则1.2 — 为所有菜单项设置键盘快捷键

Every menu item that performs an action must have a keyboard shortcut. Use standard shortcuts for standard actions (Cmd+C, Cmd+V, Cmd+Z, etc.). Custom shortcuts should use Cmd plus a letter. Reserve Cmd+Shift, Cmd+Option, and Cmd+Ctrl combos for secondary actions.
Standard Shortcut Reference:
ActionShortcut
NewCmd+N
OpenCmd+O
CloseCmd+W
SaveCmd+S
Save AsCmd+Shift+S
PrintCmd+P
UndoCmd+Z
RedoCmd+Shift+Z
CutCmd+X
CopyCmd+C
PasteCmd+V
Select AllCmd+A
FindCmd+F
Find NextCmd+G
Preferences/SettingsCmd+,
Hide AppCmd+H
QuitCmd+Q
MinimizeCmd+M
FullscreenCmd+Ctrl+F
每个执行操作的菜单项都必须有键盘快捷键。标准操作使用标准快捷键(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

Rule 1.3 — Dynamic Menu Updates

规则1.3 — 动态更新菜单

Menu items must reflect current state. Disable items that are not applicable. Update titles to match context (e.g., "Undo Typing" not just "Undo"). Toggle checkmarks for on/off states.
swift
// SwiftUI — Dynamic menu state
CommandGroup(replacing: .toolbar) {
    Button(showingSidebar ? "Hide Sidebar" : "Show Sidebar") {
        showingSidebar.toggle()
    }
    .keyboardShortcut("S", modifiers: [.command, .control])
}
swift
// 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)
}
菜单项必须反映当前状态。禁用不适用的菜单项。根据上下文更新标题(例如,显示“撤销输入”而非仅“撤销”)。为开关状态添加复选标记。
swift
// SwiftUI — 动态菜单状态
CommandGroup(replacing: .toolbar) {
    Button(showingSidebar ? "Hide Sidebar" : "Show Sidebar") {
        showingSidebar.toggle()
    }
    .keyboardShortcut("S", modifiers: [.command, .control])
}
swift
// AppKit — 验证菜单项
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)
}

Rule 1.4 — Contextual Menus

规则1.4 — 上下文菜单

Provide right-click context menus on all interactive elements. Context menus should contain the most relevant subset of menu bar actions for the clicked element, plus element-specific actions.
swift
// SwiftUI
Text(item.name)
    .contextMenu {
        Button("Rename...") { rename(item) }
        Button("Duplicate") { duplicate(item) }
        Divider()
        Button("Delete", role: .destructive) { delete(item) }
    }
为所有交互元素提供右键上下文菜单。上下文菜单应包含与点击元素最相关的菜单栏操作子集,以及元素专属操作。
swift
// SwiftUI
Text(item.name)
    .contextMenu {
        Button("Rename...") { rename(item) }
        Button("Duplicate") { duplicate(item) }
        Divider()
        Button("Delete", role: .destructive) { delete(item) }
    }

Rule 1.5 — App Menu Structure

规则1.5 — 应用菜单结构

The App menu (leftmost, bold app name) must contain: About, Preferences/Settings (Cmd+,), Services submenu, Hide App (Cmd+H), Hide Others (Cmd+Option+H), Show All, Quit (Cmd+Q). Never rename or remove these standard items.
swift
// SwiftUI — Settings scene
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
        Settings { SettingsView() }  // Automatically wired to Cmd+,
    }
}

应用菜单(最左侧的粗体应用名称)必须包含:关于、偏好设置/设置(Cmd+,)、服务子菜单、隐藏应用(Cmd+H)、隐藏其他应用(Cmd+Option+H)、显示全部、退出(Cmd+Q)。请勿重命名或移除这些标准项。
swift
// SwiftUI — 设置场景
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
        Settings { SettingsView() }  // 自动关联到Cmd+,
    }
}

2. Windows (CRITICAL)

2. 窗口(关键要求)

Mac users expect full control over window size, position, and lifecycle. An app that fights window management feels fundamentally broken on the Mac.
Mac用户期望完全控制窗口的大小、位置和生命周期。抗拒窗口管理的应用在Mac上会显得完全不合格。

Rule 2.1 — Resizable with Sensible Minimums

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

All main windows must be freely resizable. Set a minimum size that keeps the UI usable. Never set a maximum size unless the content truly cannot scale (rare).
swift
// SwiftUI
WindowGroup {
    ContentView()
        .frame(minWidth: 600, minHeight: 400)
}
.defaultSize(width: 900, height: 600)
swift
// AppKit
window.minSize = NSSize(width: 600, height: 400)
window.setContentSize(NSSize(width: 900, height: 600))
所有主窗口必须可自由调整大小。设置一个能保持UI可用性的最小尺寸。除非内容确实无法缩放(这种情况很少见),否则不要设置最大尺寸。
swift
// SwiftUI
WindowGroup {
    ContentView()
        .frame(minWidth: 600, minHeight: 400)
}
.defaultSize(width: 900, height: 600)
swift
// AppKit
window.minSize = NSSize(width: 600, height: 400)
window.setContentSize(NSSize(width: 900, height: 600))

Rule 2.2 — Support Fullscreen and Split View

规则2.2 — 支持全屏和分屏视图

Opt into native fullscreen by setting the appropriate window collection behavior. The green traffic-light button must either enter fullscreen or show the tile picker.
swift
// AppKit
window.collectionBehavior.insert(.fullScreenPrimary)
SwiftUI windows get fullscreen support automatically.
通过设置适当的窗口集合行为来启用原生全屏功能。绿色交通灯按钮必须能够进入全屏或显示分屏选择器。
swift
// AppKit
window.collectionBehavior.insert(.fullScreenPrimary)
SwiftUI窗口自动获得全屏支持。

Rule 2.3 — Multiple Windows

规则2.3 — 多窗口支持

Unless your app is a single-purpose utility, support multiple windows. Document-based apps must allow multiple documents open simultaneously. Use
WindowGroup
or
DocumentGroup
in SwiftUI.
swift
// SwiftUI — Document-based app
@main
struct TextEditorApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: TextDocument()) { file in
            TextEditorView(document: file.$document)
        }
    }
}
除非你的应用是单一用途的工具,否则请支持多窗口。基于文档的应用必须允许同时打开多个文档。在SwiftUI中使用
WindowGroup
DocumentGroup
swift
// SwiftUI — 基于文档的应用
@main
struct TextEditorApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: TextDocument()) { file in
            TextEditorView(document: file.$document)
        }
    }
}

Rule 2.4 — Title Bar Shows Document Info

规则2.4 — 标题栏显示文档信息

For document-based apps, the title bar must show the document name. Support proxy icon dragging. Show edited state (dot in close button). Support title bar renaming on click.
swift
// AppKit
window.representedURL = document.fileURL
window.title = document.displayName
window.isDocumentEdited = document.hasUnsavedChanges
swift
// SwiftUI — NavigationSplitView titles
NavigationSplitView {
    SidebarView()
} detail: {
    DetailView()
        .navigationTitle(document.name)
}
对于基于文档的应用,标题栏必须显示文档名称。支持代理图标拖拽。显示编辑状态(关闭按钮中的圆点)。支持点击标题栏重命名。
swift
// AppKit
window.representedURL = document.fileURL
window.title = document.displayName
window.isDocumentEdited = document.hasUnsavedChanges
swift
// SwiftUI — NavigationSplitView 标题
NavigationSplitView {
    SidebarView()
} detail: {
    DetailView()
        .navigationTitle(document.name)
}

Rule 2.5 — Remember Window State

规则2.5 — 记住窗口状态

Persist window position, size, and state across launches. Use
NSWindow.setFrameAutosaveName
or SwiftUI's built-in state restoration.
swift
// AppKit
window.setFrameAutosaveName("MainWindow")

// SwiftUI — Automatic with WindowGroup
WindowGroup(id: "main") {
    ContentView()
}
.defaultPosition(.center)
在应用重启时保留窗口的位置、大小和状态。使用
NSWindow.setFrameAutosaveName
或SwiftUI的内置状态恢复功能。
swift
// AppKit
window.setFrameAutosaveName("MainWindow")

// SwiftUI — 通过WindowGroup自动实现
WindowGroup(id: "main") {
    ContentView()
}
.defaultPosition(.center)

Rule 2.6 — Traffic Light Buttons

规则2.6 — 交通灯按钮

Never hide or reposition the close (red), minimize (yellow), or zoom (green) buttons. They must remain in the top-left corner. If using a custom title bar, the buttons must still be visible and functional.
swift
// AppKit — Custom title bar that preserves traffic lights
window.titlebarAppearsTransparent = true
window.styleMask.insert(.fullSizeContentView)
// Traffic lights remain functional and visible

请勿隐藏或重新定位关闭(红色)、最小化(黄色)或缩放(绿色)按钮。它们必须保持在左上角。如果使用自定义标题栏,按钮仍必须可见且可用。
swift
// AppKit — 保留交通灯的自定义标题栏
window.titlebarAppearsTransparent = true
window.styleMask.insert(.fullSizeContentView)
// 交通灯保持可用且可见

3. Toolbars (HIGH)

3. 工具栏(重要要求)

Toolbars are the secondary command surface after the menu bar. They provide quick access to frequent actions and should be customizable.
工具栏是菜单栏之后的次要命令界面。它为常用操作提供快速访问,并且应该支持自定义。

Rule 3.1 — Unified Title Bar and Toolbar

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

Use the unified title bar + toolbar style for a modern appearance. The toolbar sits in the title bar area, saving vertical space.
swift
// SwiftUI
WindowGroup {
    ContentView()
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(action: compose) {
                    Label("Compose", systemImage: "square.and.pencil")
                }
            }
        }
}
.windowToolbarStyle(.unified)
swift
// AppKit
window.titleVisibility = .hidden
window.toolbarStyle = .unified
使用统一标题栏+工具栏样式以获得现代外观。工具栏位于标题栏区域,节省垂直空间。
swift
// SwiftUI
WindowGroup {
    ContentView()
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(action: compose) {
                    Label("Compose", systemImage: "square.and.pencil")
                }
            }
        }
}
.windowToolbarStyle(.unified)
swift
// AppKit
window.titleVisibility = .hidden
window.toolbarStyle = .unified

Rule 3.2 — User-Customizable Toolbars

规则3.2 — 用户可自定义的工具栏

Allow users to add, remove, and rearrange toolbar items. Provide a default set and a superset of available items.
swift
// 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)
允许用户添加、移除和重新排列工具栏项。提供默认项集和可用项的超集。
swift
// SwiftUI — 可自定义工具栏
.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)

Rule 3.3 — Segmented Controls for View Switching

规则3.3 — 使用分段控件切换视图

Use a segmented control or picker in the toolbar for switching between content views (e.g., List/Grid/Column). This is a toolbar pattern, not a tab bar.
swift
// 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)
}
在工具栏中使用分段控件或选择器来切换内容视图(例如,列表/网格/列)。这是工具栏模式,而非标签栏。
swift
// 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)
}

Rule 3.4 — Search Field in Toolbar

规则3.4 — 工具栏中的搜索字段

Place the search field in the trailing area of the toolbar. Use
.searchable()
in SwiftUI for standard search behavior with suggestions and tokens.
swift
// SwiftUI
NavigationSplitView {
    SidebarView()
} detail: {
    ContentListView()
        .searchable(text: $searchText, placement: .toolbar, prompt: "Search items")
        .searchSuggestions {
            ForEach(suggestions) { suggestion in
                Text(suggestion.title).searchCompletion(suggestion.title)
            }
        }
}
将搜索字段放置在工具栏的尾部区域。在SwiftUI中使用
.searchable()
以获得带有建议和标记的标准搜索行为。
swift
// SwiftUI
NavigationSplitView {
    SidebarView()
} detail: {
    ContentListView()
        .searchable(text: $searchText, placement: .toolbar, prompt: "Search items")
        .searchSuggestions {
            ForEach(suggestions) { suggestion in
                Text(suggestion.title).searchCompletion(suggestion.title)
            }
        }
}

Rule 3.5 — Toolbar Labels and Icons

规则3.5 — 工具栏标签和图标

Toolbar items should have both an icon (SF Symbol) and a text label. In compact mode, show icons only. Prefer labeled icons for discoverability. Use
Label
to supply both.

工具栏项应同时包含图标(SF Symbol)和文本标签。在紧凑模式下,仅显示图标。为了便于发现,优先使用带标签的图标。使用
Label
来同时提供两者。

4. Sidebars (HIGH)

4. 侧边栏(重要要求)

Sidebars are the primary navigation surface for Mac apps. They appear on the leading edge and provide persistent access to top-level sections and content libraries.
侧边栏是Mac应用的主要导航界面。它位于左侧边缘,提供对顶级区域和内容库的持久访问。

Rule 4.1 — Leading Edge, Collapsible

规则4.1 — 左侧边缘,可折叠

Place the sidebar on the left (leading) edge. Make it collapsible via the toolbar button or Cmd+Ctrl+S. Persist collapsed state.
swift
// 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)
将侧边栏放置在左侧(前导)边缘。允许通过工具栏按钮或Cmd+Ctrl+S折叠。保留折叠状态。
swift
// 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)

Rule 4.2 — Source List Style

规则4.2 — 源列表样式

Use the source list style (
.listStyle(.sidebar)
) for content-library navigation. Source lists have a translucent background that shows the desktop or window behind them with vibrancy effects.
swift
// 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)
使用源列表样式(
.listStyle(.sidebar)
)进行内容库导航。源列表具有半透明背景,可通过活力效果显示背后的桌面或窗口。
swift
// 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)

Rule 4.3 — Outline Views for Hierarchies

规则4.3 — 用于层级结构的大纲视图

When content is hierarchical (e.g., folder trees, project structures), use disclosure groups or outline views to let users expand and collapse levels.
swift
// SwiftUI — Recursive outline
List(selection: $selection) {
    OutlineGroup(rootNodes, children: \.children) { node in
        Label(node.name, systemImage: node.icon)
    }
}
当内容具有层级结构(例如,文件夹树、项目结构)时,使用展开组或大纲视图让用户展开和折叠层级。
swift
// SwiftUI — 递归大纲
List(selection: $selection) {
    OutlineGroup(rootNodes, children: \.children) { node in
        Label(node.name, systemImage: node.icon)
    }
}

Rule 4.4 — Drag to Reorder

规则4.4 — 拖拽重排

Sidebar items that can be reordered (bookmarks, favorites, custom sections) must support drag-to-reorder. Implement
onMove
or
NSOutlineView
drag delegates.
swift
// SwiftUI
ForEach(favorites) { item in
    Label(item.name, systemImage: item.icon)
}
.onMove { source, destination in
    favorites.move(fromOffsets: source, toOffset: destination)
}
可重排的侧边栏项(书签、收藏、自定义区域)必须支持拖拽重排。实现
onMove
NSOutlineView
拖拽委托。
swift
// SwiftUI
ForEach(favorites) { item in
    Label(item.name, systemImage: item.icon)
}
.onMove { source, destination in
    favorites.move(fromOffsets: source, toOffset: destination)
}

Rule 4.5 — Badge Counts

规则4.5 — 徽章计数

Show badge counts on sidebar items for unread counts, pending items, or notifications. Use the
.badge()
modifier.
swift
// SwiftUI
Label("Inbox", systemImage: "tray")
    .badge(unreadCount)

在侧边栏项上显示未读计数、待处理项或通知的徽章。使用
.badge()
修饰符。
swift
// SwiftUI
Label("Inbox", systemImage: "tray")
    .badge(unreadCount)

5. Keyboard (CRITICAL)

5. 键盘(关键要求)

Mac users rely on keyboard shortcuts more than any other platform. An app without comprehensive keyboard support is a broken Mac app.
Mac用户比其他平台的用户更依赖键盘快捷键。没有全面键盘支持的应用在Mac上是不合格的。

Rule 5.1 — Cmd Shortcuts for Everything

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

Every action reachable by mouse must have a keyboard equivalent. Primary actions use Cmd+letter. Secondary actions use Cmd+Shift or Cmd+Option. Tertiary actions use Cmd+Ctrl.
Keyboard Shortcut Conventions:
Modifier PatternUsage
Cmd+letterPrimary actions (New, Open, Save, etc.)
Cmd+Shift+letterVariant of primary (Save As, Find Previous)
Cmd+Option+letterAlternative mode (Paste and Match Style)
Cmd+Ctrl+letterWindow/view controls (Fullscreen, Sidebar)
Ctrl+letterEmacs-style text navigation (acceptable)
Fn+keySystem functions (F11 Show Desktop, etc.)
每个可通过鼠标执行的操作都必须有键盘等效操作。主要操作使用Cmd+字母组合。次要操作使用Cmd+Shift或Cmd+Option组合。三级操作使用Cmd+Ctrl组合。
键盘快捷键约定:
修饰符模式用途
Cmd+letter主要操作(新建、打开、保存等)
Cmd+Shift+letter主要操作的变体(另存为、查找上一个)
Cmd+Option+letter替代模式(粘贴并匹配样式)
Cmd+Ctrl+letter窗口/视图控制(全屏、侧边栏)
Ctrl+letterEmacs风格的文本导航(可接受)
Fn+key系统功能(F11 显示桌面等)

Rule 5.2 — Full Keyboard Navigation

规则5.2 — 全键盘导航

Support Tab to move between controls. Support arrow keys within lists, grids, and tables. Support Shift+Tab for reverse navigation. Use
focusable()
and
@FocusState
in SwiftUI.
swift
// 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() }
    }
}
支持使用Tab键在控件之间移动。支持在列表、网格和表格中使用箭头键。支持Shift+Tab反向导航。在SwiftUI中使用
focusable()
@FocusState
swift
// SwiftUI — 焦点管理
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() }
    }
}

Rule 5.3 — Escape to Cancel or Close

规则5.3 — 使用Escape取消或关闭

Esc must dismiss popovers, sheets, dialogs, and cancel in-progress operations. In text fields, Esc reverts to the previous value. In modal dialogs, Esc is equivalent to clicking Cancel.
swift
// SwiftUI — Sheet with Esc support (automatic)
.sheet(isPresented: $showingSheet) {
    SheetView()  // Esc dismisses automatically
}

// AppKit — Custom responder
override func cancelOperation(_ sender: Any?) {
    dismiss(nil)
}
Esc键必须能够关闭弹出框、表单页、对话框,并取消正在进行的操作。在文本字段中,Esc键恢复为之前的值。在模态对话框中,Esc键等效于点击取消按钮。
swift
// SwiftUI — 支持Esc的表单页(自动实现)
.sheet(isPresented: $showingSheet) {
    SheetView()  // Esc键可自动关闭
}

// AppKit — 自定义响应者
override func cancelOperation(_ sender: Any?) {
    dismiss(nil)
}

Rule 5.4 — Return for Default Action

规则5.4 — 使用Return执行默认操作

In dialogs and forms, Return/Enter activates the default button (visually emphasized in blue). The default button is always the safest primary action.
swift
// SwiftUI
Button("Save") { save() }
    .keyboardShortcut(.defaultAction)  // Enter key

Button("Cancel") { cancel() }
    .keyboardShortcut(.cancelAction)   // Esc key
在对话框和表单中,Return/Enter键激活默认按钮(视觉上以蓝色突出显示)。默认按钮始终是最安全的主要操作。
swift
// SwiftUI
Button("Save") { save() }
    .keyboardShortcut(.defaultAction)  // Enter键

Button("Cancel") { cancel() }
    .keyboardShortcut(.cancelAction)   // Esc键

Rule 5.5 — Delete for Removal

规则5.5 — 使用Delete删除

The Delete key (Backspace) must remove selected items in lists, tables, and collections. Cmd+Delete for more destructive removal (move to Trash). Always support Cmd+Z to undo deletion.
Delete键(退格键)必须能够删除列表、表格和集合中选中的项。Cmd+Delete用于更具破坏性的删除(移至废纸篓)。始终支持Cmd+Z撤销删除操作。

Rule 5.6 — Space for Quick Look

规则5.6 — 使用Space快速查看

When items support previewing, Space bar should invoke Quick Look. Use the
QLPreviewPanel
API in AppKit or
.quickLookPreview()
in SwiftUI.
swift
// SwiftUI
List(selection: $selection) {
    ForEach(files) { file in
        FileRow(file: file)
    }
}
.quickLookPreview($quickLookItem, in: files)
当项支持预览时,空格键应调用快速查看。在AppKit中使用
QLPreviewPanel
API,或在SwiftUI中使用
.quickLookPreview()
swift
// SwiftUI
List(selection: $selection) {
    ForEach(files) { file in
        FileRow(file: file)
    }
}
.quickLookPreview($quickLookItem, in: files)

Rule 5.7 — Arrow Key Navigation

规则5.7 — 箭头键导航

In lists and grids, Up/Down arrow keys move selection. Left/Right collapse/expand disclosure groups or navigate columns. Cmd+Up goes to the beginning, Cmd+Down goes to the end.

在列表和网格中,向上/向下箭头键移动选择。向左/向右箭头键折叠/展开展开组或导航列。Cmd+Up键跳至开头,Cmd+Down键跳至结尾。

6. Pointer and Mouse (HIGH)

6. 指针和鼠标(重要要求)

Mac is a pointer-driven platform. Every interactive element must respond to hover, click, right-click, and drag.
Mac是一个基于指针的平台。每个交互元素都必须响应悬停、点击、右键点击和拖拽。

Rule 6.1 — Hover States

规则6.1 — 悬停状态

All interactive elements must have a visible hover state. Buttons highlight, rows show a selection indicator, links change cursor. Use
.onHover
in SwiftUI.
swift
// 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 }
    }
}
所有交互元素必须有可见的悬停状态。按钮高亮显示,行显示选择指示器,链接更改光标。在SwiftUI中使用
.onHover
swift
// SwiftUI — 悬停效果
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 }
    }
}

Rule 6.2 — Right-Click Context Menus

规则6.2 — 右键上下文菜单

Every interactive element must respond to right-click with a contextual menu. The context menu should contain the most relevant actions for the clicked item.
每个交互元素必须响应右键点击并显示上下文菜单。上下文菜单应包含与点击项最相关的操作。

Rule 6.3 — Drag and Drop

规则6.3 — 拖拽和放置

Support drag and drop for content manipulation: reordering items, moving between containers, importing files from Finder, and exporting content.
swift
// 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
}
swift
// Accepting file drops from Finder
.dropDestination(for: URL.self) { urls, location in
    importFiles(urls)
    return true
}
支持拖拽和放置以进行内容操作:重排项、在容器之间移动、从Finder导入文件以及导出内容。
swift
// SwiftUI — 拖拽和放置
ForEach(items) { item in
    ItemView(item: item)
        .draggable(item)
}
.dropDestination(for: Item.self) { items, location in
    handleDrop(items, at: location)
    return true
}
swift
// 接受来自Finder的文件拖放
.dropDestination(for: URL.self) { urls, location in
    importFiles(urls)
    return true
}

Rule 6.4 — Scroll Behavior

规则6.4 — 滚动行为

Support both trackpad (smooth/inertial) and mouse wheel (discrete) scrolling. Use elastic/bounce scrolling at content boundaries. Support horizontal scrolling where appropriate.
同时支持触控板(平滑/惯性)和鼠标滚轮(离散)滚动。在内容边界使用弹性/弹跳滚动。在适当的地方支持水平滚动。

Rule 6.5 — Cursor Changes

规则6.5 — 光标变化

Change the cursor to indicate affordances: pointer for clickable elements, I-beam for text, crosshair for drawing, resize handles at window/splitter edges, grab hand for draggable content.
swift
// AppKit — Custom cursor
override func resetCursorRects() {
    addCursorRect(bounds, cursor: .crosshair)
}
更改光标以指示功能:可点击元素使用指针光标,文本区域使用I型光标,绘图使用十字光标,窗口/拆分器边缘使用调整大小手柄,可拖拽内容使用抓取手光标。
swift
// AppKit — 自定义光标
override func resetCursorRects() {
    addCursorRect(bounds, cursor: .crosshair)
}

Rule 6.6 — Multi-Selection

规则6.6 — 多选

Support Cmd+Click for non-contiguous selection and Shift+Click for range selection in lists, tables, and grids. This is a deeply ingrained Mac interaction pattern.
swift
// SwiftUI — Tables with multi-selection
Table(items, selection: $selectedItems) {
    TableColumn("Name", value: \.name)
    TableColumn("Date", value: \.dateFormatted)
    TableColumn("Size", value: \.sizeFormatted)
}

在列表、表格和网格中支持Cmd+Click非连续选择和Shift+Click范围选择。这是Mac中根深蒂固的交互模式。
swift
// SwiftUI — 支持多选的表格
Table(items, selection: $selectedItems) {
    TableColumn("Name", value: \.name)
    TableColumn("Date", value: \.dateFormatted)
    TableColumn("Size", value: \.sizeFormatted)
}

7. Notifications and Alerts (MEDIUM)

7. 通知和提醒(中等要求)

Mac users are protective of their attention. Only interrupt when truly necessary.
Mac用户非常珍惜自己的注意力。仅在真正必要时才打断用户。

Rule 7.1 — Use Notification Center Appropriately

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

Send notifications only for events that happen outside the app or require user action. Never notify for routine operations. Notifications must be actionable.
swift
// 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)
仅在应用外发生事件或需要用户操作时发送通知。不要为常规操作发送通知。通知必须是可操作的。
swift
// 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)

Rule 7.2 — Alerts with Suppression Option

规则7.2 — 带有抑制选项的提醒

For recurring alerts, provide a "Do not show this again" checkbox. Respect the user's choice and persist it.
swift
// 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")
}
对于重复出现的提醒,提供“不再显示此提醒”复选框。尊重用户的选择并保留该设置。
swift
// AppKit — 带有抑制选项的提醒
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")
}

Rule 7.3 — Don't Interrupt Unnecessarily

规则7.3 — 不要不必要地打断用户

Never show alerts for successful operations. Use inline status indicators, toolbar badges, or subtle animations instead. Reserve modal alerts for destructive or irreversible actions.
不要为成功操作显示提醒。改用内联状态指示器、工具栏徽章或微妙的动画。仅在执行破坏性或不可逆操作时使用模态提醒。

Rule 7.4 — Dock Badge

规则7.4 — Dock徽章

Show a badge on the Dock icon for notification counts. Clear it promptly when the user addresses the notifications.
swift
// AppKit
NSApp.dockTile.badgeLabel = unreadCount > 0 ? "\(unreadCount)" : nil

在Dock图标上显示通知计数的徽章。当用户处理完通知后立即清除徽章。
swift
// AppKit
NSApp.dockTile.badgeLabel = unreadCount > 0 ? "\(unreadCount)" : nil

8. System Integration (MEDIUM)

8. 系统集成(中等要求)

Mac apps exist in a rich ecosystem. Deep integration makes an app feel native.
Mac应用存在于一个丰富的生态系统中。深度集成让应用感觉更原生。

Rule 8.1 — Dock Icon and Menus

规则8.1 — Dock图标和菜单

Provide a high-quality 1024x1024 app icon. Support Dock right-click menus for quick actions. Show recent documents in the Dock menu.
swift
// 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
}
提供高质量的1024x1024应用图标。支持Dock右键菜单以执行快速操作。在Dock菜单中显示最近的文档。
swift
// AppKit — Dock菜单
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 — Spotlight Integration

规则8.2 — Spotlight集成

Index app content for Spotlight search using
CSSearchableItem
and Core Spotlight. Users expect to find app content via Cmd+Space.
swift
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])
使用
CSSearchableItem
和Core Spotlight为Spotlight搜索索引应用内容。用户期望通过Cmd+Space找到应用内容。
swift
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 Support

规则8.3 — 快速查看支持

Provide Quick Look previews for custom file types via a Quick Look Preview Extension. Users expect Space to preview any file in Finder.
通过快速查看预览扩展为自定义文件类型提供快速查看预览。用户期望在Finder中使用空格键预览任何文件。

Rule 8.4 — Share Extensions

规则8.4 — 分享扩展

Implement the Share menu so users can share content from your app to Messages, Mail, Notes, etc. Also accept shared content from other apps.
swift
// SwiftUI
ShareLink(item: document.url) {
    Label("Share", systemImage: "square.and.arrow.up")
}
实现分享菜单,让用户可以将应用中的内容分享到信息、邮件、备忘录等应用。同时接受来自其他应用的共享内容。
swift
// SwiftUI
ShareLink(item: document.url) {
    Label("Share", systemImage: "square.and.arrow.up")
}

Rule 8.5 — Services Menu

规则8.5 — 服务菜单

Register for the Services menu to receive text, URLs, or files from other apps. This is a uniquely Mac integration point that power users rely on.
注册服务菜单以接收来自其他应用的文本、URL或文件。这是高级用户依赖的独特Mac集成点。

Rule 8.6 — Shortcuts and AppleScript

规则8.6 — 快捷指令和AppleScript

Support the Shortcuts app by providing App Intents. For advanced automation, add AppleScript/JXA scripting support via an
.sdef
scripting dictionary.
swift
// 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)
    }
}

通过提供App Intents支持快捷指令应用。对于高级自动化,通过
.sdef
脚本字典添加AppleScript/JXA脚本支持。
swift
// 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. Visual Design (HIGH)

9. 视觉设计(重要要求)

Mac apps should look and feel like they belong on the platform. Use system-provided materials, fonts, and colors.
Mac应用应该看起来和感觉上属于该平台。使用系统提供的材质、字体和颜色。

Rule 9.1 — Use System Fonts

规则9.1 — 使用系统字体

Use SF Pro (the system font) at standard dynamic type sizes. Use SF Mono for code. Never hardcode font sizes; use semantic styles.
swift
// 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))
使用SF Pro(系统字体)并设置标准动态类型大小。代码使用SF Mono。不要硬编码字体大小;使用语义样式。
swift
// SwiftUI — 语义字体样式
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 — Vibrancy and Materials

规则9.2 — 活力效果和材质

Use system materials for sidebar and toolbar backgrounds. Vibrancy lets the desktop or underlying content show through, anchoring the app to the Mac visual language.
swift
// SwiftUI
List { ... }
    .listStyle(.sidebar)  // Automatic vibrancy

// Custom vibrancy
ZStack {
    VisualEffectView(material: .sidebar, blendingMode: .behindWindow)
    Text("Sidebar Content")
}
swift
// AppKit — Visual effect view
let visualEffect = NSVisualEffectView()
visualEffect.material = .sidebar
visualEffect.blendingMode = .behindWindow
visualEffect.state = .followsWindowActiveState
为侧边栏和工具栏背景使用系统材质。活力效果让桌面或底层内容能够透显出来,将应用与Mac视觉语言紧密结合。
swift
// SwiftUI
List { ... }
    .listStyle(.sidebar)  // 自动应用活力效果

// 自定义活力效果
ZStack {
    VisualEffectView(material: .sidebar, blendingMode: .behindWindow)
    Text("Sidebar Content")
}
swift
// AppKit — 视觉效果视图
let visualEffect = NSVisualEffectView()
visualEffect.material = .sidebar
visualEffect.blendingMode = .behindWindow
visualEffect.state = .followsWindowActiveState

Rule 9.3 — Respect System Accent Color

规则9.3 — 尊重系统强调色

Use the system accent color for selection, emphasis, and interactive elements. Never override it with a fixed brand color for standard controls. Use
.accentColor
or
.tint
only on custom views when appropriate.
swift
// SwiftUI — Follows system accent automatically
Button("Action") { doSomething() }
    .buttonStyle(.borderedProminent)  // Uses system accent color

Toggle("Enable feature", isOn: $isEnabled)  // Toggle tint follows accent
使用系统强调色进行选择、强调和交互元素。不要为标准控件使用固定品牌颜色覆盖它。仅在合适时对自定义视图使用
.accentColor
.tint
swift
// SwiftUI — 自动遵循系统强调色
Button("Action") { doSomething() }
    .buttonStyle(.borderedProminent)  // 使用系统强调色

Toggle("Enable feature", isOn: $isEnabled)  // 开关色调遵循强调色

Rule 9.4 — Support Dark Mode

规则9.4 — 支持深色模式

Every view must support both Light and Dark appearances. Use semantic colors (
Color.primary
,
Color.secondary
,
.background
) rather than hardcoded colors. Test in both modes.
swift
// 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
每个视图必须同时支持浅色和深色外观。使用语义颜色(
Color.primary
Color.secondary
.background
)而非硬编码颜色。在两种模式下进行测试。
swift
// SwiftUI — 语义颜色
Text("Title").foregroundStyle(.primary)
Text("Subtitle").foregroundStyle(.secondary)

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

// 资源目录:为两种外观定义颜色
// 不要为UI表面使用Color.white或Color.black

Rule 9.5 — Translucency

规则9.5 — 半透明

Respect the "Reduce transparency" accessibility setting. When transparency is reduced, replace translucent materials with solid backgrounds.
swift
// SwiftUI
@Environment(\.accessibilityReduceTransparency) var reduceTransparency

var body: some View {
    if reduceTransparency {
        Color(nsColor: .windowBackgroundColor)
    } else {
        VisualEffectView(material: .sidebar, blendingMode: .behindWindow)
    }
}
尊重“降低透明度”辅助功能设置。当透明度降低时,将半透明材质替换为实色背景。
swift
// SwiftUI
@Environment(\.accessibilityReduceTransparency) var reduceTransparency

var body: some View {
    if reduceTransparency {
        Color(nsColor: .windowBackgroundColor)
    } else {
        VisualEffectView(material: .sidebar, blendingMode: .behindWindow)
    }
}

Rule 9.6 — Consistent Spacing and Layout

规则9.6 — 一致的间距和布局

Use 20pt standard margins, 8pt spacing between related controls, 20pt spacing between groups. Align controls to a grid. Use SwiftUI's built-in spacing or AppKit's Auto Layout with system spacing constraints.

使用20pt标准边距,相关控件之间使用8pt间距,组之间使用20pt间距。将控件对齐到网格。使用SwiftUI的内置间距或AppKit的自动布局并设置系统间距约束。

Keyboard Shortcut Quick Reference

键盘快捷键快速参考

Navigation

导航

ShortcutAction
Cmd+NNew window/document
Cmd+OOpen
Cmd+WClose window/tab
Cmd+QQuit app
Cmd+,Settings/Preferences
Cmd+TabSwitch apps
Cmd+`Switch windows within app
Cmd+TNew tab
快捷键操作
Cmd+N新建窗口/文档
Cmd+O打开
Cmd+W关闭窗口/标签页
Cmd+Q退出应用
Cmd+,偏好设置/设置
Cmd+Tab切换应用
Cmd+`在应用内切换窗口
Cmd+T新建标签页

Editing

编辑

ShortcutAction
Cmd+ZUndo
Cmd+Shift+ZRedo
Cmd+X / C / VCut / Copy / Paste
Cmd+ASelect All
Cmd+DDuplicate
Cmd+FFind
Cmd+GFind Next
Cmd+Shift+GFind Previous
Cmd+EUse Selection for Find
快捷键操作
Cmd+Z撤销
Cmd+Shift+Z重做
Cmd+X / C / V剪切 / 复制 / 粘贴
Cmd+A全选
Cmd+D复制
Cmd+F查找
Cmd+G查找下一个
Cmd+Shift+G查找上一个
Cmd+E使用所选内容进行查找

View

视图

ShortcutAction
Cmd+Ctrl+FToggle fullscreen
Cmd+Ctrl+SToggle sidebar
Cmd+0Show/hide toolbar
Cmd++ / Cmd+-Zoom in/out
Cmd+0Actual size

快捷键操作
Cmd+Ctrl+F切换全屏
Cmd+Ctrl+S切换侧边栏
Cmd+0显示/隐藏工具栏
Cmd++ / Cmd+-放大/缩小
Cmd+0实际大小

Evaluation Checklist

评估检查表

Before shipping a Mac app, verify:
在发布Mac应用之前,请验证:

Menu Bar

菜单栏

  • App has a complete menu bar with standard menus
  • All actions have keyboard shortcuts
  • Menu items dynamically update (enable/disable, title changes)
  • Context menus on all interactive elements
  • App menu has About, Settings, Hide, Quit
  • 应用有包含标准菜单的完整菜单栏
  • 所有操作都有键盘快捷键
  • 菜单项动态更新(启用/禁用、标题更改)
  • 所有交互元素都有上下文菜单
  • 应用菜单包含关于、设置、隐藏、退出

Windows

窗口

  • Windows are freely resizable with sensible minimums
  • Fullscreen and Split View work
  • Multiple windows supported (if appropriate)
  • Window position and size persist across launches
  • Traffic light buttons visible and functional
  • Document title and edited state shown (if document-based)
  • 窗口可自由调整大小并设置合理的最小值
  • 全屏和分屏视图正常工作
  • 支持多窗口(如适用)
  • 窗口位置和大小在重启后保留
  • 交通灯按钮可见且可用
  • 显示文档标题和编辑状态(如为基于文档的应用)

Toolbars

工具栏

  • Toolbar present with common actions
  • Toolbar is user-customizable
  • Search field available in toolbar
  • 工具栏包含常用操作
  • 工具栏支持用户自定义
  • 工具栏中提供搜索字段

Sidebars

侧边栏

  • Sidebar for navigation (if app has multiple sections)
  • Sidebar is collapsible
  • Source list style with vibrancy
  • 提供用于导航的侧边栏(如应用有多个区域)
  • 侧边栏可折叠
  • 使用带活力效果的源列表样式

Keyboard

键盘

  • Full keyboard navigation (Tab, arrows, Enter, Esc)
  • Cmd+Z undo for all destructive actions
  • Space for Quick Look previews
  • Delete key removes selected items
  • No keyboard traps (user can always Tab out)
  • 支持全键盘导航(Tab、箭头键、Enter、Esc)
  • 所有破坏性操作都支持Cmd+Z撤销
  • 使用空格键进行快速查看预览
  • Delete键可删除选中项
  • 没有键盘陷阱(用户始终可以使用Tab键退出)

Pointer

指针

  • Hover states on interactive elements
  • Right-click context menus everywhere
  • Drag and drop for content manipulation
  • Cmd+Click for multi-selection
  • Appropriate cursor changes
  • 交互元素有悬停状态
  • 所有地方都支持右键上下文菜单
  • 支持内容操作的拖拽和放置
  • 支持Cmd+Click多选
  • 光标有适当的变化

Notifications

通知

  • Notifications only for important events
  • Alerts have suppression option for recurring ones
  • No modal alerts for routine operations
  • 仅为重要事件发送通知
  • 重复提醒有抑制选项
  • 不为常规操作使用模态提醒

System Integration

系统集成

  • High-quality Dock icon
  • Content indexed in Spotlight (if applicable)
  • Share menu works
  • App Intents for Shortcuts
  • 高质量Dock图标
  • 内容已索引到Spotlight(如适用)
  • 分享菜单正常工作
  • 为快捷指令提供App Intents

Visual Design

视觉设计

  • System fonts at semantic sizes
  • Dark Mode fully supported
  • System accent color respected
  • Translucency respects accessibility setting
  • Consistent spacing on 8pt grid

  • 使用语义大小的系统字体
  • 完全支持深色模式
  • 尊重系统强调色
  • 半透明遵循辅助功能设置
  • 基于8pt网格的一致间距

Anti-Patterns

反模式

Do not do these things in a Mac app:
  1. No menu bar — Every Mac app needs a menu bar. Period. A Mac app without menus is like a car without a steering wheel.
  2. Hamburger menus — Never use a hamburger menu on Mac. The menu bar exists for this purpose. Hamburger menus signal a lazy iOS port.
  3. Tab bars at the bottom — Mac apps use sidebars and toolbars, not iOS-style tab bars. If you need tabs, use actual document tabs in the tab bar (like Safari or Finder).
  4. Large touch-sized targets — Mac controls should be compact (22-28pt height). Users have precise pointer input. Giant buttons waste space and look out of place.
  5. Floating action buttons — FABs are a Material Design pattern. On Mac, place primary actions in the toolbar, menu bar, or as inline buttons.
  6. Sheet for every action — Don't use modal sheets for simple operations. Use popovers, inline editing, or direct manipulation. Sheets should be reserved for multi-step workflows or important decisions.
  7. Custom window chrome — Don't replace the standard title bar, traffic lights, or window controls with custom implementations. Users expect these to work consistently across all apps.
  8. Ignoring keyboard — If a power user must reach for the mouse to perform common actions, your keyboard support is insufficient.
  9. Single-window only — Unless your app is genuinely single-purpose (calculator, timer), support multiple windows. Users expect to Cmd+N for new windows.
  10. Fixed window size — Non-resizable windows feel broken on Mac. Users have displays ranging from 13" laptops to 32" externals and expect to use that space.
  11. No Cmd+Z undo — Every destructive or modifying action must be undoable. Users build muscle memory around Cmd+Z as their safety net.
  12. Notification spam — Mac apps that send excessive notifications get their permissions revoked. Only notify for events that genuinely need attention.
  13. Ignoring Dark Mode — A Mac app that looks wrong in Dark Mode appears abandoned. Always test both appearances.
  14. Hardcoded colors — Use semantic system colors, not hardcoded hex values. Your colors should adapt to Light/Dark mode and accessibility settings automatically.
  15. No drag and drop — Mac is a drag-and-drop platform. If users can see content, they expect to drag it somewhere.
在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是一个拖拽和放置平台。如果用户能看到内容,他们期望能够将其拖拽到其他地方。