How to add NSMenu Programmatically?
Asked Answered
L

3

10

I didn't use storyboard and xib, just used only code. I would like to add "Edit" Menu Programmatically. My Questions are

1) How to show "Edit" Menu / What codes need to input at comment Question 1)?

2) There are any actions provided from swift like copy & paste?

class TestManager: NSObject {

// ....
    override init() {
        let editMenuItems = [
            NSMenuItem(title: "Cut", action: nil(/* Question 2) */), keyEquivalent: ""),
            NSMenuItem(title: "Copy", action: nil, keyEquivalent: ""),
            NSMenuItem(title: "Paste", action: nil, keyEquivalent: ""),
        ]

        for editMenuItem in editMenuItems {
            self.editMenu.addItem(editMenuItem)
        }

        // Qustion 1) .. show "Edit" Menu
    }
}
Lightner answered 28/2, 2015 at 12:59 Comment(4)
Would you please state why you want to make an app withtout mainmenu if you actually need it?Lanark
The reason why I use custom code is 1) What I want to make is similar to github.com/devxoul/allkdic (Custom Code) 2) If you know how to code a user interface, then you know what happens under the hood, whereas the same is not necessarily true of NIBs and Storyboards.Lightner
That, honestly, does not make any sense at all. If you need a menu (and you state that you do need it) then use the MainMenu as being placed in the Info.plist.Lanark
I don't quite understand all the negatives. It's a perfectly reasonable question and I also want to know the answer also. I'm wanting to insert and append items. i.e. not static but dynamic. This is for a launcher application where apps are downloaded and launched from a dynamic menu.Rus
G
6

You don't show where self.editMenu comes from.

In any case, you need to obtain the mainMenu from the NSApplication instance and add a menu item to that which has your menu as its submenu. So, something like:

var editMenuItem = NSMenuItem()
editMenuItem.title = "Edit"
var mainMenu = NSMenu()
mainMenu.addItem(editMenuItem)
mainMenu.setSubmenu(self.editMenu, forItem:editMenuItem)
NSApplication.sharedApplication().mainMenu = mainMenu

I don't work in Swift, so there are probably some mistakes in there.

As to what action selector to use for Edit menu items, the easiest thing for you to do is to create a main menu NIB just to examine it. Look at the action selectors used for the menu items of the ready-made Edit menu. You'll find that the Copy item uses the copy: selector, for example. That can be represented in Swift as just a string, "copy:".

Guardrail answered 28/2, 2015 at 19:34 Comment(12)
Thanks for the answer. I don't have enough reputation. I will try to do this way and comment again. Thank you, Ken.Lightner
I did like this, gist.github.com/AstinCHOI/9791f4b0ab941a2be6da. but It didn't work. Thanks for your help.Lightner
You left out the mainMenu?.addItem(editMenuItem) statement.Guardrail
The current gist has it commented out. Also, check that mainMenu is not nil.Guardrail
As you expected, mainMenu is nil T.TLightner
Oh, OK. Do you have anything in the menu bar? I've changed my answer to show code for creating the main menu of the app. The Edit menu is added to that before it is set on the app.Guardrail
Sorry to response late. I've done with your code, but it didn't work. Thanks for answering my problem, Ken.Lightner
gist.github.com/AstinCHOI/9791f4b0ab941a2be6da. It doesn't workLightner
I see NSStatusItem in there. Is your app just for a status item? Have you set LSUIElement in the Info.plist? (Might be presented as "Application is agent (UIElement)" in the property list editor.) If so, then it can't have a menu bar. That's the purpose of LSUIElement, to prevent your app from having a menu bar or a Dock icon.Guardrail
That's right. My app just has a status item. The reason why I use NSMenu is to use "copy", "paste", "cut" and "selectAll" in the NSTextField. Thanks for helping this issue all the time. So, my new question is how to set LSUIElement or have an alternative way to use copy, paste, cut and select all without NSMenu? Thanks. T.TLightner
You can add a custom view anywhere within the window that overrides -performKeyEquivalent: to check the key event for standard edit keyboard shortcuts. If it matches, it sends the appropriate action to the responder chain. This question links to a third-party custom text field class which does that. As the question indicates, you only need to use that custom class for one text field in the window to handle the keyboard shortcuts for all controls in the window.Guardrail
Always Thanks for your answer. Once I get the reputation, I will do your answer up. I will try it as you mentioned. Thank you :)Lightner
M
4

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html

Automatic Menu Enabling

  • If the menu item’s target is not set (that is, if it is nil—typically if the menu item is connected to First Responder) and the NSMenu object is not a contextual menu, then NSMenu uses the responder chain (described in The Responder Chain) to determine the target. If there is no object in the responder chain that implements the item’s action, the item is disabled. If there is an object in the responder chain that implements the item’s action, NSMenu then checks to see if that object implements the validateMenuItem: or validateUserInterfaceItem: method. If it does not, then the menu item is enabled. If it does, then the enabled status of the menu item is determined by the return value of the method.

So, in order to achieve Automatic Menu Enabling for standard system menu items we have to specify correct action without setting target.

let menu = NSMenu(...)
menu.addItem(withTitle: "Quit \(applicationName)",
             action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
menu.addItem(withTitle: "Select All",
             action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")
menu.addItem(withTitle: "Copy",
             action: #selector(NSText.copy(_:)), keyEquivalent: "c")
Monogamist answered 30/11, 2017 at 16:15 Comment(0)
F
1

I found the answers here were generally in the right direction but something was missing, so I'll add my complete solution. In my case I wanted to add a "Debug" menu only for development.

func addDebugMenu() {
    let debugMenu = NSMenuItem(title: "Debug", action: nil, keyEquivalent: "")
    debugMenu.submenu = NSMenu(title: "Debug")
    debugMenu.submenu?.addItem(withTitle: "Load saved data", action: #selector(self.loadDataFromFile(_:)), keyEquivalent: "")
    NSApplication.shared.mainMenu?.addItem(debugMenu)
}

@objc func loadDataFromFile(_ sender: Any) {
    print("load it")
}

For the complete solution, call it like this for development-only:

#if DEBUG
addDebugMenu()
#endif

My environment is: Xcode 12.3, macOS 10.15.7, Swift 5.3.2

Finsen answered 12/1, 2022 at 19:48 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.