3

Given that I have a menuBar app with 3 items on a subMenu:

let delaySubMenu = NSMenu()

delaySubMenu.addItem(NSMenuItem(title: "5", action: #selector( setReminder(_:)), keyEquivalent: ""))
delaySubMenu.addItem(NSMenuItem(title: "10", action: #selector(setReminder(_:)), keyEquivalent: ""))
delaySubMenu.addItem(NSMenuItem(title: "15", action: #selector(setReminder(_:)), keyEquivalent: ""))

How do I detect which of my delaySubMenu items has been selected without making a unique setReminder function for each?

Thanks

ian
  • 11,605
  • 25
  • 69
  • 96

1 Answers1

4

The action selector will receive the sender object just like it would if you were using Interface Builder. So your setReminder(_:) selector could have the signature:

func setReminder(_ sender: Any) {
// Coerce sender to NSMenuItem and use it to make your decisions
}

or:

func setReminder(_ sender: NSMenuItem) {
// Don't do any coercion work you don't need to do…
}

You could also set the tag property of the NSMenuItem to your delay values. The tag property is an Int type so a good match for your values.

As you are creating multiple entries you could use a for in loop to traverse an array or dictionary, creating a new NSMenuItem for each entry. So we could change your original code to something like this example where I use a dictionary:

let delaySubMenu = NSMenu()
let delays = ["5 Minutes" : 5, "10 Minutes" : 10, "15 Minutes" : 15] // This is a dictionary of String:Int

for (titleKey, value) in delays {
    let menuItem = NSMenuItem(title: titleKey, action: #selector(setReminder(_:)), keyEquivalent: nil)
    menuItem.tag = value
    delaySubMenu.addItem(menuItem)
}

func setReminder(_ sender: NSMenuItem) {
    let delayValue = sender.tag // delayValue is a Int by inference from tag

    // Do something with your delay value
}

disclaimer: this is just cut and paste in the browser so it may needs some tweaking to actually work.

Craig
  • 9,335
  • 2
  • 34
  • 38
  • 1
    What is there about the NSMenuItem that will identify it more reliably than the tag? Surely you’re not suggesting he use the title?? – matt Aug 23 '18 at 01:57
  • No… I was meaning not directly storing the value in the tag… but using it to store an identifier… I guess that's not as clear as I should make it - I'll update – Craig Aug 23 '18 at 02:00
  • Thanks , I was able to use the second example 'let someVar = sender.title' to get what I need but you might want to update with a full example, I couldn't find much info on `coercion` and had to sort of guess at things. If I was using `sender: Any` would I `coerce` it with something like `let someVar = sender.title as NSMenuItem`? – ian Aug 23 '18 at 16:42
  • I'm sorry but I think using the `title` to identify which menu item this is a terrible idea. It's just poor practice. Use the `tag`, or subclass and give it a property. Tags do _not_ need to "unique across the app" so the whole way this answer is put is misleading. – matt Aug 23 '18 at 17:47
  • @ian and @matt - updated with example of creating `NSMenuItem` and using the `tag` to store the value… which is much safer than coercing the `title` from a `String` to an `Int` – Craig Aug 23 '18 at 22:43
  • @Craig thanks, @matt why is `let menuItem = sender.title` bad practice? How would I do it using a `subclass`? – ian Aug 24 '18 at 02:11