15 Replies
      Latest reply on Dec 11, 2019 2:11 PM by Claude31
      Mogens Level 1 Level 1 (0 points)

        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

        • Re: How do I set a menu check mark at runtime?
          OOPer Level 8 Level 8 (5,555 points)

          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?

          • Re: How do I set a menu check mark at runtime?
            Claude31 Level 8 Level 8 (7,895 points)
            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
              • Re: How do I set a menu check mark at runtime?
                Mogens Level 1 Level 1 (0 points)

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

                 

                Please, see my response to OOPer.

                  • Re: How do I set a menu check mark at runtime?
                    Claude31 Level 8 Level 8 (7,895 points)

                    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.

                      • Re: How do I set a menu check mark at runtime?
                        Mogens Level 1 Level 1 (0 points)

                        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

                            }

                          • Re: How do I set a menu check mark at runtime?
                            Claude31 Level 8 Level 8 (7,895 points)

                            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.

                              • Re: How do I set a menu check mark at runtime?
                                Mogens Level 1 Level 1 (0 points)

                                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.

                                  • Re: How do I set a menu check mark at runtime?
                                    Claude31 Level 8 Level 8 (7,895 points)

                                    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.

                                      • Re: How do I set a menu check mark at runtime?
                                        Mogens Level 1 Level 1 (0 points)

                                        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.

                                          • Re: How do I set a menu check mark at runtime?
                                            Claude31 Level 8 Level 8 (7,895 points)

                                            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 
                                              • Re: How do I set a menu check mark at runtime?
                                                Claude31 Level 8 Level 8 (7,895 points)

                                                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