Search code examples
swiftcocoacontextmenuswift3nstoolbar

Display Only "Customize Toolbar..." in NSToolbar's Context Menu in Swift


enter image description here

I know this question has been asked many times but it seems no better solution for it.

Changing the allowsUserCustomization property doesn't help. It seems there is no API to customize the items in toolbar's context menu.

Finder app has no "Use Small Size" while Notes app has only "Customize Toolbar.."

I would like to know if there is any way to subclass or extend or do whatever to the NSToolbar to achieve the purpose?

Updated 1:

According to @Khundragpan and this post, problem 1 can be solved by:

    if let contextMenu = window?.contentView?.superview?.menu {
        for item in contextMenu.items {
            if item.title != "Customize Toolbar…" {
                contextMenu.removeItem(item)
            }
        }
    }

But I don't think it's the best way.

Update 2:

Another way to solve problem 1 (thanks to @1024jp to point out this file):

    if let contextMenu = window?.contentView?.superview?.menu {
        contextMenu.items.forEach({ (item) in
            if let action = item.action,
                NSStringFromSelector(action) != "runToolbarCustomizationPalette:" {
                contextMenu.removeItem(item)
            }
        })
    }

Update 3:

A ton of thanks to @1024jp for helping me. I'm able to remove those things with a few tips and tricks from him. Check the answer below.


Solution

  • After 3 days, I finally did it. Here is the result.

    enter image description here

    enter image description here

    Source Code in Swift 3

    You can implement and make your own class, but here I just want to keep everything in a file.

    This is the WindowController.swift file. You can set the custom class of your window controller and run. Again thanks to @1024jp for the tips.

    //
    //  WindowController.swift
    //  The Toolbar
    //
    //  Created by João Oliveira on 22/09/2016.
    //  Copyright © 2016 João Oliveira. All rights reserved.
    //
    
    import Cocoa
    
    class WindowController: NSWindowController {
    
        override func windowDidLoad() {
            super.windowDidLoad()
    
            guard let window = window else { return }
    
            window.delegate = self
    
            window.toolbar = NSToolbar(identifier: "RestrictedToolbar")
            window.toolbar?.allowsUserCustomization = true
            window.toolbar?.displayMode = .iconOnly
            window.toolbar?.delegate = self
    
            keepOnlyCustomizableMenu()
        }
    
        // PROBLEM 1: Solution
        func keepOnlyCustomizableMenu() {
            if let contextMenu = window?.contentView?.superview?.menu {
                contextMenu.items.forEach({ (item) in
                    if let action = item.action,
                        NSStringFromSelector(action) != "runToolbarCustomizationPalette:" {
                        contextMenu.removeItem(item)
                    }
                })
            }
        }
    }
    
    // MARK: Window Delegate
    // A ton of thanks to genius @1024jp
    extension MyWindowController: NSWindowDelegate {
    
        // PROBLEM 2: Solution
        func window(_ window: NSWindow, willPositionSheet sheet: NSWindow, using rect: NSRect) -> NSRect {
    
            if sheet.className == "NSToolbarConfigPanel" {
                removeSizeAndDisplayMode(in: sheet)
            }
    
            return rect
        }
    
        func removeSizeAndDisplayMode(in sheet: NSWindow) {
    
            guard let views = sheet.contentView?.subviews else { return }
    
            // Hide Small Size Option
            views.lazy
                .flatMap { $0 as? NSButton }
                .filter { button -> Bool in
                    guard let buttonTypeValue = button.cell?.value(forKey: "buttonType") as? UInt,
                        let buttonType = NSButtonType(rawValue: buttonTypeValue)
                        else { return false }
                    return buttonType == .switch
                }
                .first?.isHidden = true
    
            // Hide Display Mode Option
            views.lazy
                .filter { view -> Bool in
                    return view.subviews.count == 2
                }
                .first?.isHidden = true
    
            sheet.contentView?.needsDisplay = true
        }
    
    }
    
    // MARK: Toolbar Delegate
    extension MyWindowController: NSToolbarDelegate {
    
        func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [String] {
            return [
                NSToolbarFlexibleSpaceItemIdentifier,
                NSToolbarSpaceItemIdentifier,
                NSToolbarToggleSidebarItemIdentifier
            ]
        }
    
        func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [String] {
            return [NSToolbarToggleSidebarItemIdentifier]
        }
    
        func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: String, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
            return nil
        }
    
    }