Access Menu Item to Disable/Enable - swift

I would like to implement NSMenuItemto trigger certain functionality (e.g. "Run Calculation"). How do I access the menu items in order to enable/disable the items based on the app logic? E.g. the "cut" function for text is only enable as menu item when test is selected. "Run Calculation" should only get enabled when certain criteria are given.
Thanks!

You probably have some view controller or window controller that implements runCalculation, like this:
class ViewController: NSViewController {
#IBAction func runCalculation(_ sender: Any?) {
print(1 + 1)
}
}
And you have connected the “Run Calculation” menu item's action to the runCalculation method of the controller.
To enable and disable the menu item, follow these steps:
Make sure the “Calculator” menu itself (of type NSMenu) has the “Auto Enables Items” property turned on in IB, or has autoenablesItems set to true in code.
Make your controller conform to the NSUserInterfaceValidations protocol:
extension ViewController: NSUserInterfaceValidations {
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
// See step 3...
return true
}
}
In validateUserInterfaceItem, check whether the item's action is runCalculation(_:). If so, return true if and only if you want to allow the user to run the calculation:
extension ViewController: NSUserInterfaceValidations {
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
switch item.action {
case #selector(runCalculation(_:))?:
// Put your real test here.
return !textField.stringValue.isEmpty
default: return true
}
}
}

Related

How to detect Option key down in Status bar app

I have a Swift Cocoa app that runs as an NSStatusItem, i.e., it presents a dropdown menu in Mac's top-right status bar. I would like to display extra "advanced" menu items when the user holds down the Option key while opening my status item menu. I can detect presses of the Option key, but my problem is that I cannot determine whether the key is down or up.
Here is the code I have so far in my AppDelegate.swift:
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.flagsChanged, handler: keyEvent);
}
func keyEvent(event: NSEvent) {
if let event = NSApp.currentEvent {
if event.modifierFlags.contains(.option) {
print("option key up/down event")
}
}
}
This works insofar as it prints the message when the Option key is either pressed down or released back up. The problem is that I don't know how to determine which state the Option key is in at any given time.
How can I adapt this code to know if the Option key is down or up?
EDIT: SO suggested that I edit my question to explain why it is not answered by Hide/Show menu item in application's main menu by pressing Option key. Briefly, the answers on that page, while probably workable, seemed either overly complex (adding timers, adding run loop observers, adding zero height custom views) or they were creating menu item alternates whereas I was trying to add additional menu items. Nevertheless, it is a good page and I recommend studying it if you also have this question.
Just ask for the current event and return a Bool, it's true when the key is pressed at the moment
func isOptionKeyPressed() -> Bool {
return NSEvent.modifierFlags.contains(.option)
}
addGlobalMonitorForEvents is not needed
I was able to find a working solution, inspired by (but not the same as) another answer here posted by vadian. This is my solution.
Eliminate the addGlobalMonitorForEvents and keyEvent code in my original question.
Create an an NSMenuDelegate like so:
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var extrasSeparator: NSMenuItem?
#IBOutlet weak var extrasSubmenu: NSMenuItem?
[...rest of AppDelegate...]
}
extension AppDelegate: NSMenuDelegate {
func menuWillOpen(_ menu: NSMenu) {
if NSEvent.modifierFlags.contains(.option) {
extrasSeparator?.isHidden = false
extrasSubmenu?.isHidden = false
} else {
extrasSeparator?.isHidden = true
extrasSubmenu?.isHidden = true
}
}
}
Caveat: this solution only works if the user presses and holds the Option key before clicking the NSStatusItem's icon. That's what was asked in the original question, but it may not be desirable in all cases.

Menu bar for NSDocument doesn't appear

I transplanted a storyboard to another project that uses xibs (yes, the deployment target for the app is 10.9). This storyboard is connected to a NSDocument subclass (available only on 10.10+) which seems to work very good as expected... but the only problem is the main menu that only appear when the window's document goes behind other windows (such Finder ones) and then I put it back in front.
My question is: how can I ensure the main menu get connected to my document?
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
Swift.print("validateMenuItem")
return super.validateMenuItem(menuItem)
}
override func makeWindowControllers() {
let wc = DocumentWC.loadFromNib()
self.addWindowController(wc!)
}
Not sure what causing the problem (the project it's huge), and probably you can call this a patch instead of a fix:
override func viewDidAppear() {
super.viewDidAppear()
if !self.menufixed {
self.menufixed = true // just to call it once
let win = self.view.window
win?.resignMain()
win?.becomeMain()
win?.orderFrontRegardless()
win?.resignKey()
win?.becomeKey()
win?.orderFrontRegardless()
NSApp.activate(ignoringOtherApps: true)
}
}
P.S. added to the view controller when the view and the window appear.

Keyboard focus on NSCollectionView when app launches

I'm trying to have keyboard focus on my NSCollectionView when my Mac App launches or when I switch tabs. I've tried making it the firstResponder, and it says that it is when I test, but I have to click inside the collection view before I can use the arrow keys to navigate around the collection view items.
#IBOutlet weak var collectionView: NSCollectionView!
override func viewDidAppear() {
self.collectionView.item(at: 0)?.isSelected = true
self.collectionView.becomeFirstResponder()
}
I also tried putting it in viewDidAppear override, but no dice.
Anybody have the same issue? How did you get around it?
In my experience, NSCollectionView doesn't let you navigate with arrow keys unless an item is selected already. So I did it by subclassing NSCollectionView.becomeFirstResponder() and manually selecting the first available item when nothing is selected.
class MyCollectionView: NSCollectionView {
override func becomeFirstResponder() -> Bool {
if selectionIndexPaths.count == 0 {
for section in 0..<numberOfSections {
if numberOfItems(inSection: section) > 0 {
selectionIndexPaths = [IndexPath(item: 0, section: section)]
break
}
}
}
return super.becomeFirstResponder()
}
}
I did this:
self.collectionView.reloadData()
if let selectionIndexPath = self.selectionIndexPath {
self.collectionView.selectItems(at: [selectionIndexPath],
scrollPosition: .centeredVertically)
self.view.window?.makeFirstResponder(self.collectionView)
}
NSCollectionView gets focus in this way. (tested on Mac OS 10.15.4).
I reloaded data when got response from the back end.

Disable/enable NSMenu item

I've created a menu bar app, a NSMenu object using the Interface Builder (following this tutorial). The menu has two items:
Start Commando
Stop Commando
How can I disable/enable the menu items when they're clicked? I've set disabled "Auto Enables Items" and I can manually enable/disable the items in the Attributes inspector, but how can I achieve the same thing when their functions are called?
When "Start Commando" is clicked I want the item to disable and "Stop Commando" to enable. And the other way around when "Stop Commando" is clicked.
Swift provides with setEnabled property that can be used on NSMenuItem you are trying to enable or disable.
You can do the following :
#IBOutlet weak var startMenuItem: NSMenuItem!
startMenuItem.isEnabled = false or true
You can try below code :
let menu = NSMenu();
menu.autoenablesItems = false
As others say, there is a isEnabled property for NSMenuItems. One also needs to uncheck Auto Enables Items for that menu or sub-menu in the Attributes Inspector in Xcode, or through code, to allow the setting to take effect.
To get it to change on selection, in the IBAction called for the menu item, likely in your NSWindowController, do something like this:
#IBAction private func myMenuAction(sender: NSMenuItem) {
sender.isEnabled = false
}
You will not be able to then select the menu item afterwards. I assume you re-enable it else where as so:
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
appDelegate.myMenuItem.isEnabled = true
}
Code untested.
Declare a BOOL value for instance
BOOL isActive
if(isActive)
{
//show menu
}
else
{
//hide your menu
}
also make BOOL true when your view dismiss

Menubar with Storyboard - validateMenuItem not get called

I'm trying to setup a menubar Application using storyboard but my validateMenuItem method not get called.
I will try to explain what i did.
First i dragged a Menu Item in my Application Scene. Then one Object for my MenuController. Created a MenuController (MenuController.swift) and filled it with code. Back in my storyboard I set my Menu delegate to MenuController and MenuController Outlet to Menu. (I'm not totally sure whether i have set the delegates correctly.)
When i start the app, the menu icon appears and the first item title is set to test. But when i'm clicking the icon the validateMenuItem method not get called.
MenuController.swift
import Cocoa
class MenuController: NSObject {
var statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
#IBOutlet weak var statusMenu: NSMenu!
#IBOutlet weak var item1: NSMenuItem!
override func awakeFromNib() {
print("awakeFromNib")
self.item1.title = "Test"
let icon = NSImage(named: "menubarIcon")
statusItem.image = icon
statusItem.menu = statusMenu
}
override func validateMenuItem(menuItem: NSMenuItem) -> Bool {
print("validateMenuItem")
return true
}
}
Storyboard Menu Delegates
(source: picr.de)
Storyboard MenuController Delegates
(source: picr.de)
Has anybody an idea?
Greets from Austria!
The menu/UI validation mechanism does not query the menu's delegate but uses the item's target to determine the enabled state instead.
If the target is not explicitly set, it walks the responder chain.
To get basic validation, you have to make sure that the following things are setup:
"Auto Enables Items" is checked in Interface Builder (on by default)
You control-dragged the menu's action to the first responder
The menu's action is implemented in a class that is part of the responder chain (e.g. a view controller that manages a set of actions for your UI)
A basic implementation of validateUserInterfaceItem: could look like the following for an item with an action selector called test:
func validateUserInterfaceItem(anItem: NSValidatedUserInterfaceItem) -> Bool
{
if anItem.action() == Selector("test:") {
print("validating item \(anItem)")
return true
}
return true
}