How do I set a menu check mark at runtime?

How do I set tick/check marks at a menu item at runtime?


Main menu -> Submenu -> Item


My best shot is this code, which compiles OK:


let mainMenu = NSApplication.shared.mainMenu!

let subMenu = mainMenu.item(withTitle: "Main menu")?.menu

let menuItem = subMenu?.item(withTitle: "Submenu")?.submenu

menuItem?.item(withTitle: "Item")?.isEnabled = true


Any help for a novice is appreciated.


I have the following, which toggles check marks at mouse clicks:


sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on

Replies

The line you have shown would work well if `sender` is an `NSMenuItem`, even when any other events than mouse clicks.


Mouse click is definately a runtime event and I cannot see what you think difficult.


You may need to clarify more things, on what sort of event do you want to toggle the check marks?

sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on


is correct to toggle the checkmark


Where and when do you call it ?

You could do it in the IBAction associated with the menu item.


Looking at th code:

        let mainMenu = NSApplication.shared.mainMenu!
        let subMenu = mainMenu.item(withTitle: "Main menu")?.menu
        let menuItem = subMenu?.item(withTitle: "Submenu")?.submenu
        menuItem?.item(withTitle: "Item")?.isEnabled = true


line 1, you get the mainMenu defined in Storyboard (the menu bar)

line 2, you get the menu that has the "Main Menu" title: it is an NSMenu, you call it subMenu (may be you could find a more explicit name)

line 3, you get the item "SubMenu" of the NSMenu submenu, and then load its submenu in menuItem

Does it have a submenu defined ? Otherwise menuItem is nil

line 4: you enable item "Item" in the submenu


The sequence is more complex:

Main menu (Menu bar) -> Main menu (NSMenuItem) -> NSMenuItem: Submenu -> its subMenu menuItem -> Item named "Item" in this submenu


It would be easier to read with more explicit names, such as (adapt to your app) ; even the title "Main menu" is confusing

        let mainMenu = NSApplication.shared.mainMenu!
        let firstMenu = mainMenu.item(withTitle: "First menu")?.menu
        let myItemSubmenu = firstMenu?.item(withTitle: "My Item")?.submenu // will be nil if there is not submenu (hierarchical)
        myItemSubmenu?.item(withTitle: "Item")?.isEnabled = true

when you set the state, who is sender ? myItemSubmenu or myItemSubmenu?.item(withTitle: "Item")

We do not see where you defined the menu action. Did you define in IB by connecting to an IBAction func actionForItem(_ sender: NSMenuItem) ?

It is inside this actionForItem that you have to toggle.


Note: you could write a more compact form:

sender.state = sender.state == on ? .off : .on

I use


sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on


to let the user (un)check a menuitem.


This state is saved in a file so the (un)checked menuitem can be restored when the user restarts the app.


I hope this makes sense?

Thank you for your reply, but I still can't get it to work.


Please, see my response to OOPer.

It is very welcome and helpful if you answer my question directly.


on what sort of event do you want to toggle the check marks?

I hope thiis example clarifies things:


I have a user provided list of words, which the user classifies


Word class

Noun group

Subject

Object

Pronoun

Personal

Indefinite


I.e. Subject, Object, Personal, Indefinite may be (un)checked by the user

and the word is stored along with the checked item's name(s) (sender.title)

which I wish to use to restore the checkmark(s) for a given word.

That does not answer the questions that were asked:


on what event (we do not say what user wants to do, but what is iOS event) to you call toggle ?

- on a menu selection ?

- on button tap ?

- other ?


where did you define the menu action.

- Did you define in IB by connecting to an IBAction func actionForItem(_ sender: NSMenuItem) ?

- elsewhere in code


The best would be to show the complete code for the controller, will be more useful than trying to paraphrase what you think the code is doing.

I apologize for being so vague, but I hope the following helps:


I forgot to mention I'm trying to make trial code for macOS.



@IBOutlet weak var textResult: NSTextField! // Textbox for test purpose


@IBAction func actionForItem(_ sender: NSMenuItem) {


sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on

textResult.stringValue = "Clicked on = " + sender.title // For test purpose


// The following is hardcoded for test purpose


let mainMenu = NSApplication.shared.mainMenu!

let firstMenu = mainMenu.item(withTitle: "Word class")?.menu

let myItemSubmenu = firstMenu?.item(withTitle: "Noun group")?.submenu // will be nil if there is not submenu (hierarchical)

myItemSubmenu?.item(withTitle: "Subject")?.isEnabled = true

}

It's better, but does not answer everything.


How do you connect the menu action.

- in IB by connecting to actionForItem(_ sender: NSMenuItem) ?

- elsewhere in code


You enable the menuItem in the IBAction:

myItemSubmenu?.item(withTitle: "Subject")?.isEnabled = true


What was its state before ?

If it was disabled, actionForItem will not be called on menu selection.

I connect the menu action in IB by connecting to actionForItem(_ sender: NSMenuItem)

and not elsewhere in code.


You enable the menuItem in the IBAction:

myItemSubmenu?.item(withTitle: "Subject")?.isEnabled = true


The state before was Enabled (not greyed out) and without check mark.


I would expect the code to put a check mark next to the item.

Need to check if you did the right connections.


To see if the menu action is called, add a print:


    @IBAction func actionForItem(_ sender: NSMenuItem) {

        sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
        print("actionForItem", sender.title, sender.state)
        textResult.stringValue = "Clicked on = " + sender.title  // For test purpose
      
        //   The following is hardcoded for test purpose

        let mainMenu = NSApplication.shared.mainMenu!
        let firstMenu = mainMenu.item(withTitle: "Word class")?.menu
        let myItemSubmenu = firstMenu?.item(withTitle: "Noun group")?.submenu // will be nil if there is not submenu (hierarchical)
        myItemSubmenu?.item(withTitle: "Subject")?.isEnabled = true
    }



And tell what log you get when you select the menu item "Subject"


Check also what image is defined for the menu item "Subject" in IB Attributs inspector for the on state field.

Is this what you are looking for:


2019-12-08 22:33:32.117410+0100 Edgar[2669:436738] [Nib Loading] Failed to connect (object) outlet from (Edgar.AppDelegate) to (NSMenuItem): missing setter or instance variable

2019-12-08 22:33:32.162887+0100 Edgar[2669:436738] Metal API Validation Enabled


actionForItem Subject NSControlStateValue(rawValue: 1)


The following is beyond my knowledge of XCode:


Check also what image is defined for the menu item "Subject" in IB Attributs inspector for the on state field.

on what sort of event do you want to toggle the check marks?


Please answer to my question directly. Or, if you have some words you do not understand, please tell me.

So, your code did not run, but you had this crash report ? Right ?


2019-12-08 22:33:32.117410+0100 Edgar[2669:436738] [Nib Loading] Failed to connect (object) outlet from (Edgar.AppDelegate) to (NSMenuItem): missing setter or instance variable


That's not a crash, but some nemuItem won't be loaded.

You may have missed a connection somewhere.


Let's check on a systematic way (please answer precisely to each question):

- You created Word class menu in IB. Right ?

If not, where did you create ?

It yes (which I assume):

- You created a menu in the menu bar (will be named "Word class" later) by inserting an NSMenuItem in the menu bar. Right ?

- in this menu, you inserted a menu (dragging menu object on the newly created MenuItem. Right ?

- you gave this Menu the title Word class in IB (attributes inspector of the Menu, not the menu item). Right ?

Note take care that the spelling, incl lower and upper case be exactly what you put in code

- You inserted 2 sub menu items in this menu and named them "Noun group" and "Pronoun". They appear with a right pointing arrow. Right ?

- You may have removed the other 3 items without arrow.

- You click on Noun group ; you see a submenu item, named item1. You select and rename "Subject"

- You drag a menu item below Subject and name it "Object". Right ?

- You added submenus to Pronoun in the same way.


Did you declare IBOutlets for some of those elements ? Such as Subject or Object ?

How do they show in code: please copy the IBOutlet declarations.


Have you defined a xib for some of the menus ? If so, detail which


Did you connect some of this item to an IBAction ?

If yes, which action ?

Please show the code for the action



You get a print:

actionForItem Subject NSControlStateValue(rawValue: 1)

So, you have correctly connected the Subject menuItem to its IBAction


So probably, myItemSubmenu is nil.

Please add other prints:


    @IBAction func actionForItem(_ sender: NSMenuItem) {

        sender.state = sender.state == .on ? .off : .on
        print("actionForItem", sender.title, sender.state)
        textResult.stringValue = "Clicked on = " + sender.title  // For test purpose

        //   The following is hardcoded for test purpose

        let mainMenu = NSApplication.shared.mainMenu!
        let firstMenu = mainMenu.item(withTitle: "Word class")?.menu
        print("firstMenu", firstMenu)

        let myItemSubmenu = firstMenu?.item(withTitle: "Noun group")?.submenu // will be nil if there is not submenu (hierarchical)
        print("myItemSubmenu", myItemSubmenu)

       myItemSubmenu?.item(withTitle: "Subject")?.isEnabled = true
        print("myItemSubmenu?.item ", myItemSubmenu?.item(withTitle: "Subject"))

    }


THE PROBLEM: it is the way you access to menu elements.


Instead of:


         let mainMenu = NSApplication.shared.mainMenu!
      
        let firstMenu = mainMenu.item(withTitle: "Word class")?.menu
      
        let myItemSubmenu = firstMenu?.item(withTitle: "Noun group")?.submenu // will be nil if there is not submenu (hierarchical)
        myItemSubmenu?.item(withTitle: "Subject")?.isEnabled = true

you should write (I have added a 2 at the end of names to make it clear about the changes):


         let mainMenu = NSApplication.shared.mainMenu!

        let firstMenu2 = mainMenu.items.filter(){ $0.submenu!.title == "Word class" }[0]
       
        let myItemSubmenu2 = firstMenu2.submenu?.items.filter(){ $0.submenu!.title == "Noun group" }[0].submenu
        myItemSubmenu2?.item(withTitle: "Subject")?.isEnabled = true
        myItemSubmenu2?.autoenablesItems = false // Even if no action defined, can be enabled

        // To set the state:
        let myItemSubject2 = myItemSubmenu2?.items.filter(){ $0.title == "Subject" }[0]
        myItemSubject2?.state = .on          // This switches the menu On

Does it work now ? If yes, don't forget to close the thread.


Another way to set the state property:


- create an IBOutlet for the menuItems for which you want to change the state ("Subject", "Object"…): subjectMenu, objectMenu, …


- then you can call anywhere you need:

subjectMenu?.state = .on