Skip to content

usagimaru/MainMenuTemplate

Repository files navigation

MainMenuTemplate

日本語

A Swift package for building macOS menu bar entirely in code — no XIB or storyboard required.

Requirements

  • macOS 15+
  • Swift 6

Installation

Add the package via Swift Package Manager:

.package(url: "...", from: "1.0.0")

Then add MainMenuTemplate to your target's dependencies.

Usage

Setup

Normally a Cocoa app loads its menu bar from MainMenu.xib (or storyboard). Follow these steps to build the menu entirely in code:

  1. Remove MainMenu.xib from the project
  2. Delete the NSMainNibFile key from Info.plist (build setting: INFOPLIST_KEY_NSMainNibFile)
  3. Mark your AppDelegate with @main and implement static func main(), calling runAppDelegate(_:) inside it — this creates the NSApplication instance, sets the delegate, and starts the run loop
  4. Implement @objc func setupMainMenu()runAppDelegate(_:) automatically calls it at willFinishLaunching timing via dynamic dispatch
import Cocoa
import MainMenuTemplate

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    static func main() {
        runAppDelegate()
    }

    @objc func setupMainMenu() {
        MainMenuBuilder.buildAndApply({[
            MainMenuBuilder.applicationMenuTemplate(),
            MainMenuBuilder.fileMenuTemplate(),
            MainMenuBuilder.editMenuTemplate(),
            MainMenuBuilder.viewMenuTemplate(),
            MainMenuBuilder.windowMenuTemplate(),
            MainMenuBuilder.helpMenuTemplate(),
        ]}, localizingWith: .main, tableName: "MainMenu")
    }
}

Default menu

Build and apply the standard menu bar (Application / File / Edit / View / Window / Help) in one call:

@objc func setupMainMenu() {
    MainMenuBuilder.apply(MainMenuBuilder.defaultMainMenuTemplate())
}

apply(_:) assigns the menu to NSApp.mainMenu, registers system menus (Services, Window, Help), and applies localization.

Custom composition

Use buildAndApply(_:) to compose and apply a menu in a single step:

@objc func setupMainMenu() {
    MainMenuBuilder.buildAndApply({
        [
            MainMenuBuilder.applicationMenuTemplate(),
            MainMenuBuilder.fileMenuTemplate(),
            MainMenuBuilder.editMenuTemplate(),
            MainMenuBuilder.viewMenuTemplate(),
            MainMenuBuilder.buildMenuItem(title: "Custom", {
                [
                    .init(title: "Custom Item 1", action: nil),
                    .init(title: "Custom Item 2", action: nil),
                ]
            }),
            MainMenuBuilder.windowMenuTemplate(),
            MainMenuBuilder.helpMenuTemplate(),
        ]
    }, localizingWith: .main, tableName: "MainMenu")
}

You can also use build(_:) and apply(_:) separately:

let menu = MainMenuBuilder.build {
    [
        MainMenuBuilder.applicationMenuTemplate(),
        MainMenuBuilder.fileMenuTemplate(),
        MainMenuBuilder.editMenuTemplate(),
        MainMenuBuilder.viewMenuTemplate(),
        MainMenuBuilder.windowMenuTemplate(),
        MainMenuBuilder.helpMenuTemplate(),
    ]
}
MainMenuBuilder.apply(menu, localizingWith: .main, tableName: "MainMenu")

Custom menu items

Use buildMenuItem(title:icon:_:) to create custom top-level menus:

MainMenuBuilder.buildMenuItem(title: "Tools", {
    [
        .init(title: "Run Script", action: #selector(runScript), keyEquivalent: "r"),
        .init(title: "Open Console", action: #selector(openConsole)),
    ]
})

You can also insert items into an existing template using MenuItemBuilder and insertItems(_:afterSelector:):

let fileMenuItem = MainMenuBuilder.fileMenuTemplate()
fileMenuItem.submenu?.insertItems(.init({
    [
        .separator(),
        .init(title: "Custom Action", action: #selector(customAction(_:))),
        .separator(),
    ]
}), afterSelector: #selector(NSWindow.performClose(_:)))

Localization

The package includes English and Japanese localizations via Resources/MainMenu.xcstrings.

To provide app-specific overrides, pass your bundle and table name:

MainMenuBuilder.apply(menu, localizingWith: .main, tableName: "MainMenu")

The localizationMode parameter controls how the user bundle is applied:

Mode Behavior
.override (default) User bundle takes priority; keys not found fall back to the package's built-in translations
.replace User bundle is used exclusively; keys not found display the original English key as-is
// Patch mode — supply only what differs from the built-in translations (default)
MainMenuBuilder.apply(menu, localizingWith: .main, localizationMode: .override)

// Replace mode — the user bundle is the sole source of translations
MainMenuBuilder.apply(menu, localizingWith: .main, localizationMode: .replace)

Standard Templates

Method Menu
applicationMenuTemplate() App menu (About, Settings, Services, Quit, …)
fileMenuTemplate() File (New, Open, Save, Print, …)
editMenuTemplate() Edit (Undo, Cut/Copy/Paste, Find, …)
viewMenuTemplate() View (Toolbar, Sidebar, Full Screen)
windowMenuTemplate() Window (Minimize, Zoom, Bring All to Front)
helpMenuTemplate() Help
formatMenu() Format — Font, Text (for text editing apps)
findMenu() Find promoted to a top-level menu
buildMenuItem(title:icon:_:) Custom menu with arbitrary items

License

See LICENSE for details.

About

A package for building macOS app main menu programmatically with easy standard localization support.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages