mcpField
M
macos-design-guidelines

macos-design-guidelines

github.com/ehmoデザイン・UI6314.3k 回インストール32 週間前に更新
リポジトリ

macos-design-guidelines について

macOS ヒューマンインターフェイスガイドラインを、SwiftUI と AppKit の例を交えた実践的な設計ルールにまとめたスキルです。Mac アプリ開発者向けに、メニュー、ウィンドウ、ツールバー、サイドバー、キーボード操作を扱います。

主な機能

  1. 01メニューバーの構成と動作
  2. 02ウィンドウサイズとライフサイクルの指針
  3. 03ツールバーのカスタマイズパターン
  4. 04サイドバーナビゲーションの指針
  5. 05キーボードショートカットの規則

活用シーン

  1. 01Mac アプリのコマンドメニュー設計
  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 Human Interface Guidelines

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 — メニューを動的に更新する

メニュー項目には現在の状態を反映する必要があります。適用できない項目は無効にします。タイトルは状況に合わせて更新します(例:「Undo」だけでなく「Undo Typing」)。オン/オフの状態はチェックマークで切り替えます。

// 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 メニュー(左端にある、太字のアプリ名のメニュー)には、About、Preferences/Settings(Cmd+,)、Services サブメニュー、Hide App(Cmd+H)、Hide Others(Cmd+Option+H)、Show All、Quit(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 — 適切な最小サイズでサイズ変更を可能にする

すべてのメインウインドウは、自由にサイズ変更できる必要があります。UI を使いやすく保てる最小サイズを設定します。コンテンツを実際に拡大縮小できない場合(まれです)を除き、最大サイズは設定しないでください。

// 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 — フルスクリーンと Split View に対応する

適切なウインドウコレクションの動作を設定して、ネイティブのフルスクリーンに対応させます。緑色のウインドウボタンを押すと、フルスクリーンに切り替わるか、タイル表示の選択画面が表示される必要があります。

// 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. ツールバー(重要)

ツールバーは、メニューバーに次ぐ第2のコマンド領域です。頻繁に使う操作にすばやくアクセスできるようにし、カスタマイズ可能にしてください。

ルール 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+letter主な操作(新規、開く、保存など)
Cmd+Shift+letter主な操作の別パターン(別名で保存、前を検索)
Cmd+Option+letter別のモード(スタイルを合わせてペースト)
Cmd+Ctrl+letterウィンドウ/ビューの操作(フルスクリーン、サイドバー)
Ctrl+letterEmacs 風のテキスト移動(許容されます)
Fn+keyシステム機能(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 — Space キーでクイックルック

項目のプレビューに対応している場合は、Space キーでクイックルックを起動できるようにしてください。AppKit では QLPreviewPanel API を、SwiftUI では .quickLookPreview() を使用します。

// SwiftUI
List(selection: $selection) {
    ForEach(files) { file in
        FileRow(file: file)
    }
}
.quickLookPreview($quickLookItem, in: files)
ルール 5.7 — 矢印キーで移動

リストやグリッドでは、上/下矢印キーで選択項目を移動します。左/右矢印キーでは、開示グループを折りたたむ/展開するか、列間を移動します。Cmd+Up で先頭へ、Cmd+Down で末尾へ移動します。


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
}
ルール 8.2 — Spotlight との統合

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])
ルール 8.3 — Quick Look のサポート

Quick Look Preview Extension を使って、カスタムファイルタイプのプレビューを提供します。ユーザーは Finder でどのファイルも Space キーでプレビューできることを期待しています。

ルール 8.4 — 共有機能拡張

ユーザーがアプリ内のコンテンツを Messages、Mail、Notes などに共有できるよう、共有メニューを実装します。また、ほかのアプリから共有されたコンテンツも受け取れるようにします。

// SwiftUI
ShareLink(item: document.url) {
    Label("Share", systemImage: "square.and.arrow.up")
}
ルール 8.5 — サービスメニュー

ほかのアプリからテキスト、URL、ファイルを受け取れるよう、サービスメニューに登録します。これは Mac 独自の連携ポイントであり、パワーユーザーに活用されています。

ルール 8.6 — ショートカットと AppleScript

App Intents を提供して、ショートカットアプリをサポートします。高度な自動化に対応するには、.sdef スクリプティング辞書を使って AppleScript/JXA のスクリプティングサポートを追加します。

// 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 アプリは、プラットフォームにふさわしい外観と使い心地にする必要があります。システム提供のマテリアル、フォント、カラーを使用します。

ルール 9.1 — システムフォントを使用する

標準の Dynamic Type サイズで、システムフォントの 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))
ルール 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
ルール 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
ルール 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
ルール 9.5 — 透明度

アクセシビリティ設定の「透明度を下げる」を尊重します。透明度を下げる設定が有効な場合は、半透明のマテリアルを不透明な背景に置き換えます。

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

var body: some View {
    if reduceTransparency {
        Color(nsColor: .windowBackgroundColor)
    } else {
        VisualEffectView(material: .sidebar, blendingMode: .behindWindow)
    }
}
ルール 9.6 — 一貫した間隔とレイアウト

標準のマージンには 20pt、関連するコントロール間の間隔には 8pt、グループ間の間隔には 20pt を使用します。コントロールをグリッドに揃えます。SwiftUI の組み込みの間隔、またはシステム間隔の制約を使用した AppKit の Auto Layout を使用します。


10. ポップオーバー(中)

ポップオーバーは、コントロールに紐付いたコンテキスト情報を表示します。Mac アプリでは、オプションパネル、カラーピッカー、コンテキスト設定によく使われます。

ルール 10.1 — 一時的なコンテキスト依存コンテンツにはポップオーバーを使用する

ポップオーバーは元となるビューに紐付き、外側をクリックするか Esc キーを押すと閉じます。特定の要素に適用される設定やオプションに使用します。主要なワークフローや複数ステップの操作には使用しないでください。

// SwiftUI
Button("Format...") { showingFormatPopover = true }
    .popover(isPresented: $showingFormatPopover, arrowEdge: .bottom) {
        FormatOptionsView()
            .frame(width: 280)
            .padding()
    }
ルール 10.2 — Esc キーでポップオーバーを閉じる

ユーザーが Esc キーを押したとき、ポップオーバーが閉じる必要があります。SwiftUI の .popover では自動的に処理されます。AppKit の NSPopover も、behavior が .transient または .semitransient に設定されていれば、Esc キーで閉じます。

ルール 10.3 — コンテンツに合わせてポップオーバーのサイズを設定する

ポップオーバー内のコンテンツに適した幅を設定します。必要以上に幅を広くしないでください。リストが本質的に長い場合(フォントピッカーなど)を除き、コンテンツをスクロールする必要がないようにします。


11. アクセシビリティ(最重要)

Mac アプリは、VoiceOver、フルキーボードアクセス、スイッチコントロールなどの支援技術をサポートする必要があります。

ルール 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 が要素を論理的な読み上げ順序で(LTR の場合は左上から右下へ)移動できるようにします。表示レイアウトと順序が異なる場合は、.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 アプリをリリースする前に、以下を確認してください。

メニューバー
  • 標準メニューを備えた完全なメニューバーがある
  • すべてのアクションにキーボードショートカットがある
  • メニュー項目が動的に更新される(有効/無効、タイトルの変更)
  • すべてのインタラクティブ要素にコンテキストメニューがある
  • アプリメニューに「このアプリについて」、「設定」、「隠す」、「終了」がある
ウィンドウ
  • ウィンドウのサイズを自由に変更でき、最小サイズも適切に設定されている
  • フルスクリーンと Split View が動作する
  • 必要に応じて、複数のウィンドウをサポートしている
  • ウィンドウの位置とサイズが、起動後も保持される
  • 信号機ボタンが表示され、機能する
  • 書類のタイトルと編集状態が表示される(書類ベースのアプリの場合)
ツールバー
  • よく使うアクションを含むツールバーがある
  • ツールバーをユーザーがカスタマイズできる
  • ツールバーに検索フィールドがある
サイドバー
  • アプリに複数のセクションがある場合、ナビゲーション用のサイドバーがある
  • サイドバーを折りたためる
  • ビジュアルエフェクトを備えたソースリストスタイルである
キーボード
  • キーボードで完全に移動できる(Tab、矢印、Enter、Esc)
  • 破壊的なアクションをすべて Cmd+Z で取り消せる
  • クイックルックのプレビューにスペースキーを使える
  • Delete キーで選択項目を削除できる
  • キーボードトラップがなく、常に Tab キーで外に移動できる
ポインター
  • インタラクティブ要素にホバー時の状態がある
  • 右クリックでどこでもコンテキストメニューを表示できる
  • コンテンツの操作にドラッグ&ドロップを使える
  • Cmd+Click で複数選択できる
  • 状況に応じてカーソルが適切に変化する
通知
  • 重要なイベントにのみ通知を表示する
  • 繰り返し表示されるアラートを抑制するオプションがある
  • 定常的な操作にモーダルアラートを表示しない
システムとの統合
  • 高品質なDockアイコン
  • Spotlightにコンテンツがインデックスされる(該当する場合)
  • 共有メニューが機能する
  • ショートカット用の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はマテリアルデザインのパターンです。Macでは、主要なアクションをツールバーやメニューバーに配置するか、インラインボタンとして表示します。

  6. あらゆる操作にシートを使う — 簡単な操作にモーダルシートを使わないでください。ポップオーバー、インライン編集、または直接操作を使います。シートは複数ステップのワークフローや重要な判断のために使います。

  7. 独自のウィンドウ枠 — 標準のタイトルバー、信号機ボタン、ウィンドウコントロールを独自の実装に置き換えないでください。ユーザーは、すべてのアプリで一貫して動作することを期待しています。

  8. キーボードを無視する — パワーユーザーがよく使う操作を行うためにマウスを使わなければならないなら、キーボード対応が不十分です。

  9. 単一ウィンドウのみ — アプリの用途が本当に単一機能(電卓、タイマーなど)に限られる場合を除き、複数ウィンドウをサポートしてください。ユーザーはCmd+Nで新しいウィンドウを開けることを期待しています。

  10. ウィンドウサイズが固定されている — サイズを変更できないウィンドウは、Macでは不自然に感じられます。ユーザーのディスプレイは13インチのノートPCから32インチの外部ディスプレイまでさまざまで、そのスペースを活用できることを期待しています。

  11. Cmd+Zで取り消せない — 破壊的な操作や変更を伴う操作は、すべて取り消せるようにする必要があります。ユーザーは、いざというときの安全策としてCmd+Zを使う習慣を身につけています。

  12. 通知の乱発 — 通知を過剰に送るMacアプリは、通知の許可を取り消されます。本当に注意が必要なイベントのみ通知してください。

  13. ダークモードを無視する — ダークモードで見た目が崩れるMacアプリは、放置されているように見えます。必ずライトとダークの両方の外観をテストしてください。

  14. 色をハードコードする — ハードコードされた16進数の値ではなく、意味に応じたシステムカラーを使ってください。色はライト/ダークモードやアクセシビリティ設定に自動的に適応する必要があります。

  15. ドラッグ&ドロップに対応しない — Macはドラッグ&ドロップを前提とするプラットフォームです。ユーザーがコンテンツを見られるなら、どこかへドラッグできることを期待しています。

コメント