[SwiftUI] commands Modifier @SwiftUI(2024)

SwiftUI2021

   
⌛️ 3 min.

いつの間にかいろいろ 増えているようなので、.commands 関連を再確認してみます。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS14.7
  • Xcode 16.2 Beta
  • iOS 18.1 Beta
  • Swift 5.9

.commands 系 Modifier

SwiftUI には、.commands という Modifier が用意されています。

この Modifier は、View ではなく、Scene に付与して使用します。


参考
commands(content:)Apple Developer Documentation

機能的には、Scene に command を付与します。

具体的には、アプリにメニューを追加できます。

macOS であれば、画面上部にある top-level menu に追加できます。iPadOS であれば、キーボードショートカットで使用できるコマンドを追加できます。

iPadOS 上では、キーボードの⌘キーを押下し続けると、一覧が表示されるようになっています。
iPad のキーボードショートカットについて@Apple

登場当初(macOS11) では、.commands しかなかったのですが、iOS16/macOS13 以降向けにいくつか 追加されていました。

以降で、改めて確認してみます。

.commands

メニューを追加する基本的な Modifier は、commands です。


参考
commands(content:)Apple Developer Documentation

引数に渡す content には、@CommandsBuilder 指定されています。これは、ViewBuilder の Commands 版です。

つまり、以下のように複数の Commands を渡すことができます。

import SwiftUI

@main
struct MenuBarMenu2024App: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands(content: {
            CommandMenu("MyMenu1", content: {
                Button(action: {}, label: { Text("Hello world") })
            })
            CommandMenu("MyMenu2", content: {
                Button(action: {}, label: { Text("Konnichiwa Sekai") })
            })
        })
    }
}

上記は、1つの .commands で 複数の CommandMenu を渡していて、以下のようなメニューになります。

commandsBasic

メニューバーに、top-level menu として “MyMenu1”, “MyMenu2″ と追加されていて、MyMenu1 のメニューには、”Hello world” というメニュー項目が追加されているのが見えます。

CommandMenu

CommandMenu 等については、以下の記事で説明しています。

SwiftUI2021 [SwiftUI][macOS] SwiftUI でのメニューの扱い方

.commandsRemoved

新しく iOS16/ macOS13 で追加されています。


参考
commandsRemoved()Apple Developer Documentation

この Scene Modifier を使用すると付与した Scene の付与するメニューを削除します。

何言ってるか意味不明だと思います。以下が具体的な機能です。

Scene は、Window だったり WindowGroup だったりしますが、macOS であれば ウィンドウ (もしくはタブ)を定義します。

このような Scene をアプリに定義すると、Window メニューに scene それぞれに該当するメニューが付与されています。
このメニューを削除します。


import SwiftUI

@main
struct MenuBarMenu2024App: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        Window("MyWindow", id: "MyWindowID", content: {
            Text("MyWindow here")
        })
    }
}
WindowMenu

Window メニュー配下に、”MyWindow” メニューが追加されているのがわかります。Window として追加した Scene をこのメニューから開くことができます。

作成しているアプリによっては、このウィンドウはアプリ内部からプログラム的にのみ表示したい時があります。正直、これまではそのようなときに、困っていました。このメニューを消してくれるのが commandsRemoved です。

import SwiftUI

@main
struct MenuBarMenu2024App: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        Window("MyWindow", id: "MyWindowID", content: {
            Text("MyWindow here")
        })
        .commandsRemoved()
    }
}

以下のように、Window メニューに表示されなくなります。

WindowMenuRemoved

これは、シングルウィンドウを定義する Window での振る舞いですが、WindowGroup に付与すると、”File” – “NewWindow” 等のメニューが削除されます。(ですので、閉じてしまうと新しいウィンドウを開く手段がなくなります。)

複数の WindowGroup を持つアプリでは、特定の WindowGroup のみユーザーから開くことができるようにしたい等の実装が可能になるということです。

.commandsReplaced

commandsRemoved と同時期に追加された Modifier として、commandsReplaced があります。


参考
commandsReplaced(content:)Apple Developer Documentation

この Modifier は、付与された Scene の追加する command を replace することができます。

以下では、WindowGroup の “New Document” メニューを置き換えています。

import SwiftUI

@main
struct MenuBarMenu2024App: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commandsReplaced(content: {
            CommandGroup(replacing: .newItem, addition: {
                Button("MyNewDocument") {}
            })
        })
        Window("MyWindow", id: "MyWindowID", content: {
            Text("MyWindow here")
        })
    }
}
MyNewDocument

考察

当初は、commandsReplace と使用せずとも CommandGroup に replaced 指定することで、メニューを置き換えられる仕様でした。

ですが、macOS では、アクティブなウィンドウに応じてメニューが切り替えられるため、CommandGroup で置き換えてしまうと不便なケースや うまく置き換えられなかったケースがありました。(当時この制限で、InterfaceBuilder に戻ってなんとかした記憶があります。)

現在の仕様では、あくまで 付与した Scene が追加するメニューについての置き換え指定なので、何が置き換えられるかについては、より明確になっているようです。

ただ、あくまで scene が追加するメニューが対象なので、一般的に存在するメニューを置き換える場合、複数の scene に異なる指定をしたときの動作が仕様からは読み取れず、試行錯誤が必要でした。

おまけ:CommandGroupPlacement

CommandGroupPlacement というのは、CommandGroup で replace 指定するときに指定できるメニューのことです。


参考
CommandGroupPlacementApple Developer Documentation

こちらも少しづつ増えているようです。

まとめ

SwiftUI(2024)の commands 関連を確認しました。

SwiftUI(2024)の commands 関連の確認
  • .commands で メニュー追加は従来通り
  • .commandsRemoved で 特定 scene により追加されるメニューを削除することが可能に
  • .commandsReplaced で 特定 scene により追加されるメニューを置き換えすることが可能に
  • .commandsRemoved/.commandsReplaced では scene により追加されるメニューの理解が必要(ドキュメントなし)
  • .commandsReplaced で複数 scene に異なる指定がされるときの動作は要確認(ドキュメントなし)

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMatery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です