8

I have an NSToolbarItem with an NSButton as its view and an NSMenuItem in the main menu. Both have the same action, which is sent to the first responder, not to a particular target. That method is ultimately implemented in a subclass of NSSplitViewController, somewhere in the view hierarchy of the window’s content view. I want to validate both items, but have that specific split-view controller take care of the validation, because it relies on some conditions local to that controller.

I overrode validateToolbarItem(_:) and validateMenuItem(_:) in that split-view controller. For the menu item, this is working as expected. The method is called and the validation happens. validateToolbarItem(_:) is never called, however.

According to Apple’s documentation, NSToolbar does not send validateToolbarItem(_:) to view-based toolbar items. To test this, I have substituted the toolbar item with an image toolbar item and there it works as expected.

Based on this, I have come across several solutions, but they aren’t quite what I want.

  • Subclass NSToolbarItem and override validate(). However, no guidance is given as to how I end up getting the controller’s validateToolbarItem(_:) to call.

  • Subclass NSToolbar and override validateVisibleToolbarItems(), then send messages to the first responder. Here I am running into the problem, that I cannot send a message to the split-view controller, because it is outside of the toolbar’s responder chain.

  • Subclass NSToolbar as above, but implement validateToolbarItem(_:) in a controller that is within the responder chain, such as the NSWindowController. This would work, but then I have to add additional code to handle what is not necessary for the menu item.

Is there an elegant solution for this that works as well like it does for the image toolbar item and the menu item?

Eitot
  • 186
  • 1
  • 11

1 Answers1

15

I wrote the following code in my NSToolbarItem subclass for buttons. With this toolbarItem subclass, you can use normal validateUserInterfaceItem() or validateToolbarItem() to validate toolbar items that contain an NSControl.

override func validate() {

    // validate content view
    if
        let control = self.view as? NSControl,
        let action = self.action,
        let validator = NSApp.target(forAction: action, to: self.target, from: self) as AnyObject?
    {
        switch validator {
        case let validator as NSUserInterfaceValidations:
            control.isEnabled = validator.validateUserInterfaceItem(self)
        default:
            control.isEnabled = validator.validateToolbarItem(self)
        }

    } else {
        super.validate()
    }
}
1024jp
  • 2,058
  • 1
  • 16
  • 25
  • Fantastic, just the elegant solution I was looking for. Thanks a lot! – Eitot Feb 27 '17 at 14:28
  • Beautiful! Thanks for sharing this. Would you mind explaining two things: 1) why is this even needed — why doesn't Cocoa already do this? and b) how often should I expect this to be called? In my quick implementation of your code it seems to be called very unpredictably, and rather often. – jeff-h May 14 '17 at 06:59
  • @jeff-h 1) The inner view of a NSToolbarItem can be more than a NSControl. So Cocoa doesn't know what it is and therefore how to validate it. 2) It's a combination of event trigers and a timer. You can see the detail of the validation timing in the reference: https://developer.apple.com/reference/appkit/nstoolbar/1516947-validatevisibleitems – 1024jp May 15 '17 at 23:02
  • I also ran into an NSToolbar(Item)-validation warphole, as the items kept refusing to validate. Then I found-out that in fact they did validate, but they didn't properly redraw, until something forced a redraw of the entire window. Then, after waisting lots of time, I finally figured-out what the problem was: the validation-methods more often than not would be called from threads other than the main thread, and enabled/disabling toolbaritems from these threads would cause the – arri Mar 12 '19 at 16:24
  • 1
    ... inconsistent behaviour. This was easily fixed by dispatching code that altered UI-state in a block on the main dispatch-queue/main-thread. – arri Mar 12 '19 at 16:34