DEV Community

Peter Witham
Peter Witham

Posted on

Changing macOS menu item states with Swift

This post originally appeared on my CompileSwift site.

Before I begin, I hope there is a more straightforward solution, but this works. I say that because my macOS developer skills are not as good as my iOS ones. So if you know a better way, please comment and help a developer out, thanks.

The Problem

I have a macOS application with two menu items that I use to switch between different views. There is a checkmark next to the current view on the menu, and I need to change that state when the view switches and vice versa.

My Solution

I thought this would be a lot easier than it turned out to be; to put it another way, it took a lot of experimentation and research, far more than I expected.

Assign the NSMenu to an @IBOutlet

In _AppDelegate.swift_I created an IBOutlet and then connected the menu on the storyboard to it

@IBOutlet weak var mainMenu: NSMenu!
Enter fullscreen mode Exit fullscreen mode

OK, that gave me a way to talk with the menu from across my application. Given how much everyone talks about not putting things in AppDelegate I found this strange, but I’m going along with it since it worked.

Now the real fun begins within the viewController

OK, now it was time to create the functions that get called when you click on the NSMenuItem

So for this, I need two, and I hooked them up the usual way using a storyboard by CTRL+Click or right-click and drag from the menu item to the function.

Here’s the content of those functions

    // MARK: Menu Items
    @IBAction func showSettingsWindow(_ sender: NSMenuItem) {
        tabContainer.selectTabViewItem(at: 1)
        sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
    }

    @IBAction func showPostCreatorWindow(_ sender: NSMenuItem) {
        tabContainer.selectTabViewItem(at: 0)
        sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
    }
Enter fullscreen mode Exit fullscreen mode

So what’s going on here?

Well, the first line tabContainer.selectTabViewItem(at: 1) switches tabs in a view, not part of what we need to be concerned about for the menu.

Next up, since the first thing we want to do is toggle the checkmark for the item we clicked, we can take advantage of using the sender. We toggle the state with a ternary, so off is now on and vice versa.

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

That’s the simple part.

Now we need to tell the other menu item to turn off, this is where I do not feel comfortable with my solution, but I’m going to offer a couple of different ways, all use the @IBOutlet in the AppDelegate.

Starting with the scenario where I click on the ‘Settings’ option, I need to turn off the ‘Post Creator’ menu option.

The first way is to access the menu item using the index of both the top-level menu item and the sub-item.

// 1 = File menu
// 0 = Post Creator item
mainMenu.items[1].submenu?.items[0].state = NSControl.StateValue.off

Enter fullscreen mode Exit fullscreen mode

The second way is to use a tag that I have assigned to the ‘Post Creator’ menu item.

mainMenu.item(withTitle: "File")?.submenu?.item(withTitle: "Post Creator")?.state = NSControl.StateValue.off
Enter fullscreen mode Exit fullscreen mode

A third way is to assign a tag to the menu items and use the tag to change the item state.

// 100 = Post Creator
mainMenu.items[1].submenu?.item(withTag: 100)?.state = NSControl.StateValue.off
Enter fullscreen mode Exit fullscreen mode

The technique is the same for when I switch from the ‘Settings’ view back to ‘Post Creator’. So the complete code for both actions would be this.

    // MARK: Menu Items
    @IBAction func showSettingsWindow(_ sender: NSMenuItem) {
        tabContainer.selectTabViewItem(at: 1)
        sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
        mainMenu.items[1].submenu?.item(withTag: 100)?.state = NSControl.StateValue.off
    }

    @IBAction func showPostCreatorWindow(_ sender: NSMenuItem) {
        tabContainer.selectTabViewItem(at: 0)
        sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
        mainMenu.items[1].submenu?.item(withTag: 101)?.state = NSControl.StateValue.off
    }
Enter fullscreen mode Exit fullscreen mode

The Wrap

So there it is, three different ways to access a macOS menu item. As mentioned previously, I am open to better ways to do this. Or is this how we do it, and it is just that awkward?

Top comments (0)