Calling a function through #selector with parameters - swift

I'm wanting to call a function and give it parameters using the #selector. However, I get the error:
"Argument of '#selector' does not refer to an '#objc' method, property, or initializer"
#objc func changeCrypto(crypto: String) {
//stuff
}
func constructMenu() {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Bitcoin", action: #selector(changeCrypto(crypto: "bitcoin")), keyEquivalent: "B"))
menu.addItem(NSMenuItem(title: "Ethereum", action: #selector(changeCrypto(crypto: "ethereum")), keyEquivalent: "E"))
menu.addItem(NSMenuItem(title: "Litecoin", action: #selector(changeCrypto(crypto: "litecoin")), keyEquivalent: "L"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit It", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
statusItem.menu = menu
}

I fixed it! I changed it to the following..
#objc func changeCrypto(_ sender: NSMenuItem) {
//Here I call the title of the Menu Item pressed
print(sender.title)
}
func constructMenu() {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Bitcoin", action: #selector(changeCrypto(_:)), keyEquivalent: "B"))
menu.addItem(NSMenuItem(title: "Ethereum", action: #selector(changeCrypto(_:)), keyEquivalent: "E"))
menu.addItem(NSMenuItem(title: "Litecoin", action: #selector(changeCrypto(_:)), keyEquivalent: "L"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit It", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
statusItem.menu = menu
}

Related

Swift NSMenu items are grey out

I have a menu with menu items. The problem is that my menu items are all greyed out or not enabled
public override init() {
super.init()
let menu = NSMenuItem(title: "Debug", action: nil, keyEquivalent: "")
menu.submenu = NSMenu(title: "Debug")
menu.submenu?.addItem(withTitle: "Load saved data", action: #selector(loadDataFromFile(_:)), keyEquivalent: "");
menu.submenu?.addItem(withTitle: "another item", action: #selector(loadDataFromFile(_:)), keyEquivalent: "")
menu.isEnabled = true
NSApplication.shared.mainMenu?.addItem(menu)
}
#objc func loadDataFromFile(_ sender: Any) {
print("load it")
}
To be able to call a custom selector in the current class you have to set the target of the menu item to self
let menu = NSMenuItem(title: "Debug", action: nil, keyEquivalent: "")
menu.submenu = NSMenu(title: "Debug")
let loadItem = NSMenuItem(title: "Load saved data", action: #selector(loadDataFromFile), keyEquivalent: "")
loadItem.target = self
let anotherItem = NSMenuItem(title: "another item", action: #selector(loadDataFromFile), keyEquivalent: "")
anotherItem.target = self
menu.submenu?.addItem(loadItem)
menu.submenu?.addItem(anotherItem)
menu.isEnabled = true

NSAlert appearing while NSMenu is open causes UI to freeze

As the title says, when my status bar menu is open and a NSAlert is triggered from another thread, the UI freezes.
Presumably this is because both things are running on the main thread. But since I'm dealing with an NSAlert and an NSMenu, don't I have to run these on the main thread?
NSAlert Code
func showWallpaperUpdateErrorAlert(messageText: String, informativeText: String) {
DispatchQueue.main.async {
NSApp.activate(ignoringOtherApps: true)
let updateErrorAlert = NSAlert()
updateErrorAlert.messageText = messageText
updateErrorAlert.informativeText = informativeText
updateErrorAlert.addButton(withTitle: "OK")
updateErrorAlert.runModal()
}
}
NSMenu Code
func createStatusBarMenu() {
// Status bar icon
guard let icon = NSImage(named: "iconFrame44")
else { NSLog("Error setting status bar icon image."); return }
icon.isTemplate = true
statusBarItem.image = icon
// Create Submenu items
let viewOnRedditMenuItem = NSMenuItem(title: "View on Reddit...", action: #selector(viewOnRedditAction), keyEquivalent: "")
viewOnRedditMenuItem.target = self
let saveThisImageMenuItem = NSMenuItem(title: "Save This Image...", action: #selector(saveThisImageAction), keyEquivalent: "")
saveThisImageMenuItem.target = self
// Add to title submenu
let titleSubmenu = NSMenu(title: "")
titleSubmenu.addItem(descriptionMenuItem)
titleSubmenu.addItem(NSMenuItem.separator())
titleSubmenu.addItem(viewOnRedditMenuItem)
titleSubmenu.addItem(saveThisImageMenuItem)
// Create main menu items
titleMenuItem = NSMenuItem(title: "No Wallpaperer Image", action: nil, keyEquivalent: "")
titleMenuItem.submenu = titleSubmenu
titleMenuItem.isEnabled = false
getNewWallpaperMenuItem = NSMenuItem(title: "Update Now", action: #selector(getNewWallpaperAction), keyEquivalent: "")
getNewWallpaperMenuItem.target = self
let preferencesMenuItem = NSMenuItem(title: "Preferences...", action: #selector(preferencesAction), keyEquivalent: "")
preferencesMenuItem.target = self
let quitMenuItem = NSMenuItem(title: "Quit Wallpaperer", action: #selector(quitAction), keyEquivalent: "")
quitMenuItem.target = self
// Add to main menu
let statusBarMenu = NSMenu(title: "")
statusBarMenu.addItem(titleMenuItem)
statusBarMenu.addItem(NSMenuItem.separator())
statusBarMenu.addItem(getNewWallpaperMenuItem)
statusBarMenu.addItem(NSMenuItem.separator())
statusBarMenu.addItem(preferencesMenuItem)
statusBarMenu.addItem(quitMenuItem)
statusBarItem.menu = statusBarMenu
statusBarMenu.delegate = self
}
In my case the solution was to dismiss the menu before showing the alert.
I had to access the menu from the NSStatusItem's menu property and call cancelTrackingWithoutAnimation() (regular cancelTracking() wasn't as smooth). I also had to do this outside the main thread, for whatever reason.
func showWallpaperUpdateErrorAlert(messageText: String, informativeText: String) {
statusBarItem.menu?.cancelTrackingWithoutAnimation() // This is new
DispatchQueue.main.async {
NSApp.activate(ignoringOtherApps: true)
let updateErrorAlert = NSAlert()
updateErrorAlert.messageText = messageText
updateErrorAlert.informativeText = informativeText
updateErrorAlert.addButton(withTitle: "OK")
updateErrorAlert.runModal()
}
}

List all mounted Volumes in a Menu Bar Submenu (programmatically only)

I'm currently working on a "menu bar only" project and I need to list all mounted Volumes in a submenu in the menubar app. I figured out how to print() all mounted volumes but I need help with the submenu (without .xib or .storyboard work)
This is my "listVolumes func"
func listVolumes(sender: NSMenuItem) {
let keys = [NSURLVolumeNameKey, NSURLVolumeIsRemovableKey, NSURLVolumeIsEjectableKey]
let paths = NSFileManager().mountedVolumeURLsIncludingResourceValuesForKeys(keys, options: [])
if let urls = paths {
for url in urls {
if let components = url.pathComponents
where components.count > 1
&& components[1] == "Volumes" {
print(url)
}
}
}
}
an below is my code for the "menu bar app"
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-2)
func applicationDidFinishLaunching(aNotification: NSNotification) {
if let button = statusItem.button {
button.image = NSImage(named: "StatusBarButtonImage")
}
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Volumes", action: Selector("volumes:"), keyEquivalent: ""))
menu.addItem(NSMenuItem.separatorItem())
menu.addItem(NSMenuItem(title: "Help", action: Selector("help:"), keyEquivalent: ""))
menu.addItem(NSMenuItem.separatorItem())
menu.addItem(NSMenuItem(title: "Quit", action: Selector("terminate:"), keyEquivalent: "q"))
statusItem.menu = menu
}
so my question is, how can I create a submenu that contains all the mounted drives (programmatically only)
You have to set a submenu to the volumes menu item for example
let volumesMenuItem = NSMenuItem(title: "Volumes", action: Selector("volumes:"), keyEquivalent: "")
menu.addItem(volumesMenuItem)
let volumesMenu = NSMenu(title: "Volumes")
volumesMenuItem.submenu = volumesMenu
I recommend to return an array of the names from your listVolumes() function, then you can add menu items to volumesMenu assuming volumes contains the names
for volumeName in volumes {
volumesMenu.addItem(NSMenuItem(title: volumeName, action: Selector("selectVolume"), keyEquivalent: ""))
}

Swift OSX Change color of NSMenuItem Title

How do I add a label to NSMenuItem that is disabled but not greyed out.
let addToComputerItem : NSMenuItem = NSMenuItem(title: "\(title)", action: nil, keyEquivalent: "")
computerInfoMenu.addItem(addToComputerItem)
addToComputerItem.enabled = false
Try this:
let menu = NSMenuItem(title: "Test", action: #selector(AppDelegate.test), keyEquivalent: "K")
menu.attributedTitle = NSAttributedString(string: "Testinggg", attributes: [NSFontAttributeName: NSFont.systemFontOfSize(20), NSForegroundColorAttributeName: NSColor.redColor()])
mainMenu?.itemAtIndex(0)?.submenu?.addItem(menu)//You can add whichever submenu

NSMenu selector in Swift

I'm at a loss to see why this doesn't work. The menu shows, but is grayed out if I leave autoenablesItems at the default, and the actions aren't called if I set it false.
class GameScene: SKScene {
// ...
func action1(sender: AnyObject) {
println("Urk, action 1")
}
func action2(sender: AnyObject) {
println("Urk, action 2")
}
func popUpMenu(#event: NSEvent) {
var theMenu = NSMenu(title: "Contextual menu")
theMenu.addItemWithTitle("Action 1", action: Selector("action1:"), keyEquivalent: "")
theMenu.addItemWithTitle("Action 2", action: Selector("action2:"), keyEquivalent: "")
//theMenu.autoenablesItems = false
NSMenu.popUpContextMenu(theMenu, withEvent:event, forView:self.view)
}
override func mouseDown(theEvent: NSEvent) {
self.popUpMenu(event: theEvent) // The menu shows
}
}
Update
As per #Chuck's answer, you will need to do the following:
func popUpMenu(#event: NSEvent) {
var theMenu = NSMenu(title: "Contextual menu")
theMenu.addItemWithTitle("Action 1", action: Selector("action1:"), keyEquivalent: "")
theMenu.addItemWithTitle("Action 2", action: Selector("action2:"), keyEquivalent: "")
for item: AnyObject in theMenu.itemArray {
if let menuItem = item as? NSMenuItem {
menuItem.target = self
}
}
NSMenu.popUpContextMenu(theMenu, withEvent:event, forView:self.view)
}
It sounds like your problem is that an NSMenuItem created with that method doesn't have a receiver, so it uses the responder chain, and this object is not in the responder chain. You can force it to see your object by setting the menu items' targets to self.