user14347980
user14347980

Reputation: 11

How to get rid of automatically added menu items in MacOS?

In the Edit section of the top menu bar in MacOS, it keeps on adding Start Dictation... and Emoji & Symbols, even though it's not there in Xcode. Here's a screenshot with the last two, unwanted items circled in red. I can't delete them, because they don't exist in the Xcode UI builder, but they get added automatically somehow. How can I get rid of them?

I am not allowed to programatically delete them at application launch. I need to prevent them in the first place.

I tried to replace the current edit menu with a freshly created one, deleting the old one, and transferring the items, but with no prevail. I also tried to rename it to another name, also with no prevail.

My code is in Swift, so the ObjC answers can't help me, and on top of that I'm not allowed to manually delete them after the program starts programmatically.

Thanks ahead of time!

Upvotes: 1

Views: 890

Answers (1)

Chip Jarred
Chip Jarred

Reputation: 2785

I posted an answer that uses Swift with selectors to the question that Willeke linked to. I won't repeat that solution here, but at the end I mentioned an alternative approach that I will say more about here.

First I'll say this solution is a bit heavy-weight, but it's also robust against any future automatic menus Apple might choose to add. It's best suited for programmatically creating your menus. You can use it with Storyboards, but it's more of a pain.

The idea is to prevent the unwanted menus from being added in the first place without relying on undocumented UserDefaults settings.

To accomplish this, you need to take control of the process of adding menu items. That happens in NSMenu, so the plan would be to subclass it, overriding the various addItem/insertItem methods to check the tag property of any NSMenuItem being added. If the tag doesn't match some value you define for your app, simply refuse to add the item.

Unfortunately NSMenu doesn't doesn't call addItem(_:) when you call addItem(withTitle:action:keyEquivalent), nor for the insertItem methods, so you have to override all of them not just two.

It's also helpful to do some debug printing, especially if you are using Storyboards, because it's easy to miss tagging menu items.

class TaggedItemMenu: NSMenu
{
    static let wantedTag = 42 // or whatever value
    
    // Helper for creating properly tagged menu items
    private func makeTaggedItem(
        withTitle string: String,
        action selector: Selector?,
        keyEquivalent charCode: String) -> NSMenuItem
    {
        let newItem = NSMenuItem(
            title: string,
            action: selector,
            keyEquivalent: charCode
        )
        newItem.tag = Self.wantedTag
        return newItem
    }
    
    // If you use Storyboards, you have to individually set all the tags, so
    // its helpful to log untagged add/inserts so you can check they're not one
    // of your menu items you missed setting the tag for.
    private func logUntaggedAddInsert(
        _ item: @autoclosure () -> NSMenuItem,
        function: StaticString = #function)
    {
        #if DEBUG
        print("Call to \(function) for untagged NSMenuItem named \"\(item().title)\"")
        #endif
    }

    // MARK: Methods for your app to use
    // -------------------------------------
    public override func addItem(_ newItem: NSMenuItem)
    {
        guard newItem.tag == Self.wantedTag else
        {
            logUntaggedAddInsert(newItem)
            return
        }
        super.addItem(newItem)
    }
    
    // Replacement for addItem(withTitle:action:keyEquivalent)
    public func addTaggedItem(
        withTitle string: String,
        action selector: Selector?,
        keyEquivalent charCode: String) -> NSMenuItem
    {
        let newItem = makeTaggedItem(
            withTitle: string,
            action: selector,
            keyEquivalent: charCode
        )
        super.addItem(newItem)
        return newItem
    }
    
    public override func insertItem(_ newItem: NSMenuItem, at index: Int)
    {
        guard newItem.tag == Self.wantedTag else
        {
            logUntaggedAddInsert(newItem)
            return
        }
        super.insertItem(newItem, at: index)
    }

    // Replacement for insertItem(withTitle:action:keyEquivalent:at)
    public func insertTaggedItem(
        withTitle string: String,
        action selector: Selector?,
        keyEquivalent charCode: String,
        at index: Int) -> NSMenuItem
    {
        let newItem = makeTaggedItem(
            withTitle: string,
            action: selector,
            keyEquivalent: charCode
        )
        
        super.insertItem(newItem, at: index)
        return newItem
    }

    // MARK: Do NOT use these methods in your app
    // These will be used when macOS automatically inserts menus items.
    // -------------------------------------
    public override func addItem(
        withTitle string: String,
        action selector: Selector?,
        keyEquivalent charCode: String) -> NSMenuItem
    {
        let newItem = NSMenuItem(
            title: string,
            action: selector,
            keyEquivalent: charCode
        )
        logUntaggedAddInsert(newItem)
        return newItem
    }
    
    public override func insertItem(
        withTitle string: String,
        action selector: Selector?,
        keyEquivalent charCode: String,
        at index: Int) -> NSMenuItem
    {
        let newItem = NSMenuItem(
            title: string,
            action: selector,
            keyEquivalent: charCode
        )
        logUntaggedAddInsert(newItem)
        return newItem
    }
}

If programmatically creating menus, you only create TaggedItemMenus instead of NSMenu, and ensure you only ever create menus items with the addTaggedItem and insertTaggedItem. This way the automatically added menus never get into your menus in the first place, and then you don't need to worry about removing them later.

Also keep in mind that when you add submenus, those are basically a menu wrapped in a menu item. The menu part of the submenu needs to be a TaggedItemMenu too, and the NSMenuItem it's wrapped inside of needs to be tagged or it won't be added. Whether or not you need to tag your menu items, it's useful to add a addSubmenu and insertSubmenu to NSMenu via extension (or in this case to TaggedItemMenu), to do that wrapping for you.

If you're using Storyboards, you have to make sure to change each menu's and submenu's class to TaggedItemMenu in its "Identity Inspector",

Setting Menu Class Name

and individually set the tag for each menu item in its "Attribute Inspector"

Setting Menu Item tag property

That doesn't seem too bad until you starting counting up all the items in menus and submenus, all just to get rid of a couple of items Apple decided to inject into your program. Of course, if you add new menu items later, you'll need to make sure you set their tags. That's why the logging comes in handy.

If you can't live with the automatically added menu items, I'd recommend either moving away from Storyboards, or take one of the approaches to remove the items after the fact, and just accept that it could break in some future macOS version.

Upvotes: 2

Related Questions