macos-app-structure について
Mac アプリを構築またはレビューする開発者向けの、SwiftUI macOS アプリ構成スキルです。シーン選択、ウィンドウ、ナビゲーション、プロジェクト構成、プラットフォーム固有の注意点を扱います。
主な機能
- 01macOS のシーンとウィンドウのパターン
- 02メニューバーアプリの構成
- 03サイドバーナビゲーションの指針
- 04プロジェクト構成の規約
- 05Info.plist と権限設定の参照
- 06よくある誤りと修正方法
活用シーン
- 01SwiftUI macOS アプリ構成の計画
- 02メニューバーや複数ウィンドウの追加
- 03macOS のナビゲーションと設定シーンの確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ makgunay/ claude-swift-skills --skill macos-app-structureインストール後、Claude Code で/macos-app-structureを入力して読み込まれたか確認します。
詳細
macOS アプリの構造
重要な制約
- ❌ iOS 専用のシーン(ルートシーンとしての
TabView)は使用しないでください → ✅WindowGroup、Window、またはNavigationSplitViewを使用してください - ❌
UIApplicationDelegateは使用しないでください → ✅ AppKit のフックにはNSApplicationDelegateAdaptorを使用してください - ❌ 環境設定用の
Settingsシーンを忘れないでください → ✅ macOS アプリにはSettingsシーンを用意してください - ❌ 単一ウィンドウだと決めつけないでください → ✅ macOS アプリは複数のウィンドウを持てるため、それを前提に設計してください
- ❌ iOS のナビゲーションパターンは使用しないでください → ✅ macOS では
NavigationSplitView(サイドバー+詳細)を使用してください
標準的な macOS アプリ
import SwiftUI
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
.defaultSize(width: 900, height: 600)
Settings {
SettingsView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// AppKit lifecycle hooks
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true // Quit when last window closes
}
}
メニューバーアプリ
@main
struct MenuBarApp: App {
var body: some Scene {
MenuBarExtra("My App", systemImage: "command") {
MenuBarView()
}
.menuBarExtraStyle(.window) // Full window popover (not just menu items)
Settings {
SettingsView()
}
}
}
Dock アイコンを非表示にするには、Info.plist に追加します:
<key>LSUIElement</key>
<true/>
名前付きの複数ウィンドウ
@main
struct MultiWindowApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
Window("Inspector", id: "inspector") {
InspectorView()
}
.defaultSize(width: 300, height: 400)
.defaultPosition(.trailing)
Settings {
SettingsView()
}
}
}
// Open a named window from code
@Environment(\.openWindow) private var openWindow
Button("Open Inspector") { openWindow(id: "inspector") }
サイドバー付きコンテンツビュー
struct ContentView: View {
@State private var selection: SidebarItem? = .library
var body: some View {
NavigationSplitView {
List(selection: $selection) {
Section("Library") {
Label("All Items", systemImage: "square.grid.2x2")
.tag(SidebarItem.library)
Label("Favorites", systemImage: "heart")
.tag(SidebarItem.favorites)
}
}
.navigationSplitViewColumnWidth(min: 180, ideal: 220)
} detail: {
switch selection {
case .library: LibraryView()
case .favorites: FavoritesView()
case nil: ContentUnavailableView("Select an item", systemImage: "sidebar.left")
}
}
.navigationTitle("My App")
}
}
プロジェクト構造の規約
MyApp/
├── MyApp.swift # @main App struct
├── AppDelegate.swift # NSApplicationDelegateAdaptor (if needed)
├── Models/ # SwiftData @Model classes
├── Views/
│ ├── ContentView.swift # Main navigation structure
│ ├── Components/ # Reusable view components
│ └── Settings/ # Settings/Preferences views
├── ViewModels/ # @Observable view models
├── Services/ # Business logic, networking, persistence
├── Utilities/ # Extensions, helpers
├── Resources/
│ ├── Assets.xcassets
│ └── Localizable.xcstrings
├── Info.plist
└── MyApp.entitlements
主要な Info.plist 項目(macOS)
<key>LSUIElement</key>
<key>NSAccessibilityUsageDescription</key>
<key>NSAppleEventsUsageDescription</key>
主要なエンタイトルメント
<key>com.apple.security.app-sandbox</key><true/>
<key>com.apple.security.network.client</key><true/>
<key>com.apple.security.files.user-selected.read-write</key><true/>
<key>com.apple.developer.icloud-container-identifiers</key>
よくある間違いと修正方法
| 間違い | 修正方法 |
|---|---|
Settings シーンがない | Settings { SettingsView() } を追加します。macOS では必要です |
| 最後のウィンドウを閉じてもアプリが終了しない | applicationShouldTerminateAfterLastWindowClosed を実装します |
| メニューバーアプリで Dock アイコンが表示される | Info.plist に LSUIElement = true を追加します |
| macOS でウィンドウが小さすぎる | WindowGroup に .defaultSize(width:height:) を追加します |
メインのナビゲーションに TabView を使用している | macOS ではサイドバー付きの NavigationSplitView を使用します |
参考資料
コメント
Markdown 対応