[SwiftUI][macOS] SwiftUI アプリの macOS メニュー変更方法

SwiftUI2021

SwiftUI を使ったアプリの macOS でのメニューバー項目変更方法です

環境&対象

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

  • macOS Monterery beta 5
  • Xcode 13 beta5

macOS のメニューバー

macOS アプリには、ウィンドウ上部のツールバーにメニューが表示されます。

SwiftUI のデフォルトで用意されるメニューには、アプリによっては 不要なものがあり消したくなることがあります。

"New Window" メニュー

アプリケーション管理の 新しいウィンドウを作成して表示するメニューです。

シングルウィンドウで動作するアプリでは、不要ですので、削除したい時も多いと思います。

以下のようにすることで、削除することができました。


@main
struct SwiftUImacOSApp: App {
    var body: some Scene {
        WindowGroup {
            AppRootView()
        }
        .commands {
            // (1)
            CommandGroup(replacing: .newItem) {
            }
        }
    }
}
コード解説
  1. "New Window" は、.newitem に対応しているので、空のコマンドと置き換えることでメニューがなくなります

おまけ:ウィンドウを閉じたらアプリケーションを終了させる

シングルウィンドウアプリであれば、ウィンドウを閉じたら終了させるのが適切な動作であることもあります。

以下のようなコードを AppDelegate に追加することで、ウィンドウを閉じるとアプリケーションが終了するようになります。


    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }

Tab 関連メニュー

View メニューの中に、Tab 関連メニューとして "Show Tab Bar", "Show All Tabs" があります。

ドキュメントベースのアプリでしたら、Tab を使うことで、複数のドキュメントを1つのウィンドウ内に表示させることもできるようになります。

ですが、"New Window" メニューと同様に、シングルウィンドウで動作するアプリでは、削除したくなるメニューです。

以下のようにすることで、削除することができました。


@main
struct SwiftUImacOSApp: App {
    init() {
        // (1)
        NSWindow.allowsAutomaticWindowTabbing = false
    }

    var body: some Scene {
        WindowGroup {
            AppRootView()
        }
        .commands {
            CommandGroup(replacing: .newItem) {
            }
        }
    }
}
コード解説
  1. NSWindow.allowsAutomaticWindowTabbing に false を設定することで表示されなくなります。
    AppDelegate を使用しているときは、同様のコードを applicationWillFinishLaunching に記述することで同様の動作になります。

"Enter Full Screen" メニュー

アプリのウィンドウを全画面に切り替えるメニューです。

小さいウィンドウしか想定していないユーティリティ系のアプリでは不要になるメニューです。

AppDelegate の applicationWillFinishLaunching で以下のコードを実行することで、削除することができました。


    func applicationWillFinishLaunching(_ notification: Notification) {
        // (1)
        UserDefaults.standard.set(true, forKey: "NSFullScreenMenuItemEverywhere")
    }
コード解説
  1. 実際には、NSApplicationDidFinishLaunchingNotification 以前の箇所で実行すれば OK です。

その他" メニュー

その他メニューの変更は、現時点(2021.Oct) では、難しいようです。

例えば、Tab 関連のメニューと "Enter Full Screen" メニューを削除しても、空になった "View" メニューは存在し続けます。

NSApp.mainMenu を直接編集することで このメニューを”一時的”に削除することはできます。

例えば 以下のようなコードを AppDelegate#applicationDidFinishLaunching に記述することで、該当メニューがなくなります。


  // remove menus
  if let mainMenu = NSApp.mainMenu {
      DispatchQueue.main.async {
          if let viewMenu = mainMenu.items.first(where: { $0.title == "View" }) {
              mainMenu.removeItem(viewMenu)
          }
      }
  }
注意
上記のメニュー編集は、該当メニューが削除されたように見えるのですが、画面更新のタイミング(?) で削除されたメニューが復活してしまいます。

# 更新タイミングは不明ですが、アプリのビュー更新のタイミングと一致しているように見えます。

ということで、メニューを自由自在にカスタマイズするには、MainMenu.xib を作って行うしか無いようです。

まとめ:SwiftUI アプリの macOS メニュー変更方法

SwiftUI アプリの macOS メニュー変更方法
  • CommandGroup(replacing: ) で消すのが SwiftUI 流
  • CommandGroup で置き換え/消去 できるメニューは限られている
  • Tab 関連メニューは、NSWindow.allowsAutomaticWindowTabbing = false で表示されなくなる
  • 上記以外にも AppKit で行っていた調整方法で メニュー制御することはできるが、メニューや設定対象の生成タイミングに注意が必要
  • "Enter Full Screen" は、UserDefaults に NSFullScreenMenuItemEverywhere キーで設定することで非表示にできる
  • 上記以外のメニューについて SwiftUI 流に変更することはできない(ように見える)

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

コメントを残す

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