Create popup menu from right click event

I would like to create a popup menu using swift coding in a macOS app from a right click event using Xcode 11 or 12. Can't seem to find an example of this. I see plenty of IOS examples but none for macOS. Does anyone have an example of this you could share or could show me how this is done.

Also, I know how to add a menu to to the View Controller but this allows right click from anywhere in the tableView and I want the popup menu to show when you right click on a row in the table. If anybody is familiar with quicken for Mac I want the same function as it as when you right click on a row in an account register.

Here is the code for the right click event:

Code Block
  // right click action
    override func rightMouseDown(with theEvent: NSEvent) {
    let point = tableView.convert(theEvent.locationInWindow, from: nil)
        let row = tableView.row(at: point)
        selectedCustomer = custviewModel.customers[row].custNumber!
        print(selectedCustomer)
        // Add popup menu
        print("right click")
        print(row)
    }

I have ask this question on couple of other forums as well as searched for tutorials on this and have come up empty.
Answered by Claude31 in 629193022
I extract from an app, hope that will provide a good enough answer.
In this case, I create a popup menu, with values from absMin to absMax. or no value (any). Some values may be disabled. I did not edit in detail, so some parts are probably less useful for you.

Hope that helps anyway.

In rightMouseDown:

Code Block
        // Add popup menu
let theMenu = popupMenuForValue(defaultVal: defaultValue, absMin: 1, absMax: 10)
NSMenu.popUpContextMenu(theMenu, with: theEvent, for: self) // returns a selected value


The popup creation

Code Block
@objc func setMaxi(_ sender: NSMenuItem) {
selectedMaxFromMenu = sender.tag
}
func popupMenuForValue(defaultVal: Int, absMin: Int, absMax: Int) -> NSMenu {
let theMenu = NSMenu(title: "Maxi") // Not used in fact
theMenu.autoenablesItems = false // allows to deactivate some items
let v0 = NSAttributedString(string: NSLocalizedString("Any", comment: "popupMenuForValue"), attributes: [NSAttributedStringKey.foregroundColor:NSColor.red])
let item0 = NSMenuItem(title: "v0", action: #selector(setMaxi(_:)), keyEquivalent: "")
item0.attributedTitle = v0
item0.tag = 0
theMenu.addItem(item0)
let itemSeparator = NSMenuItem.separator()
theMenu.addItem(itemSeparator)
if absMax == 0 { return theMenu }
for i in 1...absMax {
let basicString = String(i)
var itemString : NSAttributedString
if i == defaultVal {
itemString = NSAttributedString(string: basicString, attributes: [NSAttributedStringKey.underlineStyle:NSUnderlineStyle.styleSingle.rawValue, NSAttributedStringKey.font: NSFont.boldSystemFont(ofSize: 12)])
} else {
itemString = NSAttributedString(string: basicString, attributes: nil)
}
let item = NSMenuItem(title: String(i), action: #selector(setMaxi(_:)), keyEquivalent: "")
item.attributedTitle = itemString
item.tag = i
if i < absMin {
item.isEnabled = false
}
theMenu.addItem(item)
}
return theMenu
}


Accepted Answer
I extract from an app, hope that will provide a good enough answer.
In this case, I create a popup menu, with values from absMin to absMax. or no value (any). Some values may be disabled. I did not edit in detail, so some parts are probably less useful for you.

Hope that helps anyway.

In rightMouseDown:

Code Block
        // Add popup menu
let theMenu = popupMenuForValue(defaultVal: defaultValue, absMin: 1, absMax: 10)
NSMenu.popUpContextMenu(theMenu, with: theEvent, for: self) // returns a selected value


The popup creation

Code Block
@objc func setMaxi(_ sender: NSMenuItem) {
selectedMaxFromMenu = sender.tag
}
func popupMenuForValue(defaultVal: Int, absMin: Int, absMax: Int) -> NSMenu {
let theMenu = NSMenu(title: "Maxi") // Not used in fact
theMenu.autoenablesItems = false // allows to deactivate some items
let v0 = NSAttributedString(string: NSLocalizedString("Any", comment: "popupMenuForValue"), attributes: [NSAttributedStringKey.foregroundColor:NSColor.red])
let item0 = NSMenuItem(title: "v0", action: #selector(setMaxi(_:)), keyEquivalent: "")
item0.attributedTitle = v0
item0.tag = 0
theMenu.addItem(item0)
let itemSeparator = NSMenuItem.separator()
theMenu.addItem(itemSeparator)
if absMax == 0 { return theMenu }
for i in 1...absMax {
let basicString = String(i)
var itemString : NSAttributedString
if i == defaultVal {
itemString = NSAttributedString(string: basicString, attributes: [NSAttributedStringKey.underlineStyle:NSUnderlineStyle.styleSingle.rawValue, NSAttributedStringKey.font: NSFont.boldSystemFont(ofSize: 12)])
} else {
itemString = NSAttributedString(string: basicString, attributes: nil)
}
let item = NSMenuItem(title: String(i), action: #selector(setMaxi(_:)), keyEquivalent: "")
item.attributedTitle = itemString
item.tag = i
if i < absMin {
item.isEnabled = false
}
theMenu.addItem(item)
}
return theMenu
}


Thanks Claude.


Did you make it work ?
Claude,

Thanks for checking.. working on it. I got a couple of errors I trying to resolve with your code. I wanted to get that working first. However, I have not been able to work on it much yesterday and today...I live in Louisiana and I have Hurricane Laura power issues.

I will post the errors I am getting when I get power back.
For this code inside the view controller inside rightmousedown

Code Block
 let theMenu = popupMenuForValue(absMin: 1, absMax: 10)
  NSMenu.popUpContextMenu(theMenu, with: theEvent, for: Self) 

I get this error "Cannot convert value of type Self.type to expected argument NSView.

Then for this code
Code Block
 @objc func setMaxi(_ sender: NSMenuItem) {
          selectedMaxFromMenu = sender.tag
        }

I get this error "Cannot find selectedMaxFromMenu in scope" .. I think this is due to the first error I am getting.

Also, I found a tutorial on context menus at appcoda website "macOS Programming: Using Menus and the Toolbar
" that is also helpful, but in this example I don't see how the context menu is connected to the right mouse button?

please advise
Hope you are all safe from hurricane and did not suffer damages.


Code Block
let theMenu = popupMenuForValue(absMin: 1, absMax: 10)
NSMenu.popUpContextMenu(theMenu, with: theEvent, for: Self)

I get this error "Cannot convert value of type Self.type to expected argument NSView.
My code says self, not Self.


For the second,

Code Block
 @objc func setMaxi(_ sender: NSMenuItem) {          selectedMaxFromMenu = sender.tag        }

I get this error "Cannot find selectedMaxFromMenu in scope" .. I think this is due to the first error I am getting.

You need to declare a var in the class:
Code Block
    fileprivate var selectedMaxFromMenu = 0   


It should work with those changes (it works so in my app).
Caulde,

No damage problem here the storm went far enough west but not so on the Texas Louisiana borders...

My bad on the error Self which should be self.. I copied wrong text when I posted. Originally I copied and pasted your code. I was playing around with that code (self). The error I got is as follows:
cannot convert value of type 'CustViewController' to expected NSView

So, I think I have my right mouse func is in the wrong class. I have it in the view controller class. Or I need more code inside the controller class.

How do I fix this?

Thanks for your help.

In my set up:
  • I have a window

  • inside this window, an NSView (with a type : GrilleView), in which I detect the click

  • rightMouseDown is in GrilleView.Swift

Can you map your own setup on this ?
I have the following:
  • A window with a custom view controller -  CustViewController: NSViewController

  • Inside this I have the right mouse detect.




In this case, NSViewController has an NSView.

Try:

Code Block
NSMenu.popUpContextMenu(theMenu, with: theEvent, for: self.view)

Success. Thanks Claude for all the help. Although you gave me the answer I did learn from it.


Great. Good continuation. Don’t forget to close the thread on the correct answer.
Create popup menu from right click event
 
 
Q