macOS coding beyond the books: basic menu control

Last Sunday, I moaned at the documentation void in coding for macOS (and iOS, for that matter). Here is a good example of a very basic problem which isn’t, as far as I can see, addressed in any of Apple’s documentation, nor in most (if not all) books about coding for macOS.

Problem

DelightEd has two sets of menu commands which rely on Mojave’s appearances, but the app runs on Sierra and later. When running on Sierra, I wanted to remove those items from their respective menus, so that the user doesn’t even see them.

The functions which are called by those commands are already disabled when running on Sierra or High Sierra, by running conditional code such as
@IBAction func goAppDark(_ any: Any) {
if #available(OSX 10.14, *) {
NSApp.appearance = NSAppearance(named: .darkAqua)
// and so on
} }

But that won’t remove those items from, in this case, the app menu. Apple provides easy access to the other menu which I wanted to change, the Windows menu. It was straightforward to discover that I could remove an item from that menu using code of the form
if let theMenu = NSApplication.shared.windowsMenu {
if let theMenuItem = theMenu.item(withTitle: "Dark") {
theMenu.removeItem(theMenuItem)
} }

but there is no equivalent of NSApplication.shared.windowsMenu for the main app menu, so I didn’t know how to find NSMenuItems within it.

Documentation

There’s a great deal of confusion here between menu bar, menu, and menu item. Even Apple’s archived guide doesn’t explain properly the hierarchical structure of objects in an app’s menu. So while it seems simple to remove a menu item, finding it in the first place is not detailed:

menuprob01

With a bit of luck, you may discover how to find your app’s main menu bar, which is
NSApplication.shared.mainMenu

This turns out to be an NSMenu object, but you want NSMenuItems within the app menu within that. That’s where it all gets confusing. One clue might be the NSMenuItem property submenu. This is all the documentation that you now get for that:

menuprob02

Missing information

What the docs don’t make clear is that NSApplication.shared.mainMenu is the menu of menus laid out horizontally across the menu bar. To locate a menu item within one of its submenus, such as the app menu, you first have to obtain the NSMenuItem representing the app menu, then look at its submenu property, which is the NSMenu which contains the NSMenuItem that you want. This is shown in the diagram below.

menuprob03

If you try to find the NSMenuItem for the command within the NSMenu for the whole menu bar, it doesn’t exist, and fails.

Solution

So, we first get the NSMenu for the whole menu bar
if let theMenu = NSApplication.shared.mainMenu {
then we obtain the NSMenuItem for the app menu within that
if let theMenu2 = theMenu.item(withTitle: "DelightEd") {
then we obtain the NSMenu for the app menu as a submenu
if let theMenu3 = theMenu2.submenu {
and from that obtain the specific NSMenuItem which we want to remove
if let theMenuItem = theMenu3.item(withTitle: "Dark") {
and finally remove that
theMenu3.removeItem(theMenuItem)

I haven’t seen this problem explained or solved anywhere else, so my solution may not be the most efficient or simplest by any means. If you know of a better answer, please don’t hesitate to inform me in a comment.

When WIMP environments first came out, documentation of basic menu operations like this was considered to be fundamental. Is it so well understood even by novice coders that it doesn’t merit detailing explicitly any more?