Right click on UIButton for UIKitForMac (catalyst) - swift

Is it possible to easily enable right click on a Catalyst app within UIKitForMac?
Currently the following code works perfectly on left click, but nothing is called on right click:
button.addTarget(self, action: #selector(doSomething), for: .touchUpInside)
// Is called for left click but not for right click
#objc func doSomething(sender:UIButton, event:UIEvent) -> Bool {

https://developer.apple.com/design/human-interface-guidelines/ios/controls/context-menus/
This menu works by right clicking on Mac Catalyst
1. Add Iteraction
let interaction = UIContextMenuInteraction(delegate: self)
yourButton.addInteraction(interaction)
2. Add extension UIContextMenuIteractionDelegate
extension AccountViewVC: UIContextMenuInteractionDelegate {
public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { suggestedActions in
let delete = UIAction(title: "Delete", image: UIImage(systemName: "arrow")) { action in
}
let children = [delete]
return UIMenu(title: "Main Menu", children: children)
})
}}

Related

How to implement a NSButton like the Finder button

Please refer to the screenshot, I want to implement a NSButton like the buttons on Finder window. It seems that the button style is similar with a NSButton recessed. I have tried the recessed button but it is a little different from the Finder button, even the one which have a drop down menu. I have no idea how to make a drop down menu with recessed button.
If I choose to make a bevel style borderless NSButton with a special image, I cannot set a highlighted hover background whose size is larger than the image inside. The hover area is always the same with the image.
So any body known how to implement this. Thanks
Use NSMenuToolbarItem instead and check the inline comment in the sample source:
Assign this custom class MainWindowController to the initial NSWindowController controller:
class MainWindowController: NSWindowController {
// `NSMenuToolbarItem` Items
var dropDownMenu: NSMenu = {
var menu = NSMenu(title: "DropDown")
let item1 = NSMenuItem(title: "Item 1", action: nil, keyEquivalent: "")
let item2 = NSMenuItem(title: "Item 1", action: nil, keyEquivalent: "")
let item3 = NSMenuItem(title: "Item 3", action: nil, keyEquivalent: "")
menu.items = [item1, item2, item3]
return menu
}()
override func windowDidLoad() {
super.windowDidLoad()
configureToolbar()
}
private func configureToolbar() {
if let safeWindow = self.window {
let toolbar = NSToolbar(identifier: "mainWindowToolbar")
toolbar.delegate = self
toolbar.displayMode = .default
safeWindow.toolbarStyle = .automatic
safeWindow.toolbar = toolbar
safeWindow.toolbar?.validateVisibleItems()
}
}
}
extension MainWindowController: NSToolbarDelegate {
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
let toolbarItem = NSMenuToolbarItem(itemIdentifier: itemIdentifier)
toolbarItem.showsIndicator = true // To display DropDown Indicator
toolbarItem.menu = self.dropDownMenu
toolbarItem.isBordered = true // Show ToolBar Item Background on Mouse Hover
toolbarItem.image = NSImage(systemSymbolName: "ellipsis.circle", accessibilityDescription: "")
return toolbarItem
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [NSToolbarItem.Identifier("DropDownAction")]
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [NSToolbarItem.Identifier("DropDownAction")]
}
}
Output:

Presenting Context menu on button tap in swift

In iOS context menus can be shown by a long press or a tap. Currently the below code shows a context menu on a long press, how do I present the menu on a tap?
let interaction = UIContextMenuInteraction(delegate: self)
tagBtn.addInteraction(interaction)
func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint)
-> UIContextMenuConfiguration? {
let favorite = UIAction(title: "Favorite",
image: UIImage(systemName: "heart.fill")) { _ in
// Perform action
}
let share = UIAction(title: "Share",
image: UIImage(systemName: "square.and.arrow.up.fill")) { action in
// Perform action
}
let delete = UIAction(title: "Delete",
image: UIImage(systemName: "trash.fill"),
attributes: [.destructive]) { action in
// Perform action
}
return UIContextMenuConfiguration(identifier: nil,
previewProvider: nil) { _ in
UIMenu(title: "Actions", children: [favorite, share, delete])
}
}
UIContextMenuInteraction is intended only for contextual (long-press) menus.
If you want the primary action of the button to display a menu, you can just create a UIMenu and assign it directly to the button.menu property, then set button.showsMenuAsPrimaryAction = true, like this:
let favorite = UIAction(title: "Favorite",
image: UIImage(systemName: "heart.fill")) { _ in
// Perform action
}
...
let button = UIButton()
button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(title: "", children: [favorite, ...])

Actions assigned to NSMenuItem dont seem to work

Heres whats going on:
I am attempting to build a Mac Status Bar App completely programmatically. Everything seems to be working fine, that is the menu shows up in the Mac status bar, the dropdown menu is displaying how it should. But when I click on the menu items, nothing happens. I even changed the target function to just doing the basic task of printing to the terminal, and nothing.
About the code:
The issue lies somewhere around here I think:
menu.addItem(NSMenuItem(title: val, action: #selector(toggleService), keyEquivalent: ""))
That code should fire off the > toggleService function. But it doesn't do anything. Could the issue be due to the fact that I am only inheriting from the NSObject class?
The Code
// StatusBar.swift
import Cocoa
class StatusBar: NSObject {
var menuButton = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
var menu = NSMenu()
var service = Service()
override init() {
super.init()
menuButton.button?.image = NSImage(named: NSImage.Name("icon"))
menuButton.menu = menu
menu.autoenablesItems = false
for (_, val) in service.list {
menu.addItem(NSMenuItem(title: val, action: #selector(toggleService), keyEquivalent: ""))
}
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quit), keyEquivalent: ""))
}
#objc func toggleService(sender: NSMenuItem) {
print ("Say Something.. anything??")
}
#objc func quit(sender: NSMenuItem) {
print ("Say Something.. anything??")
}
}
menuItem.target = self
You need to set the target to 'self'. NSMenuItems have two basic requirements. An action, and a target,
Action
menuItem.action: #selector(YOURFUNCTION)
Target
menuItem.target = self
So to get your menu items working, replace the for loop (within your init call) with this new one:
for (_, val) in service.list {
let menuItem = menu.addItem(NSMenuItem(title: val, action: #selector(toggleService), keyEquivalent: ""))
menuItem.target = self
}

swift mac osx NSButton is not responding until a long press

I am having a weird issue with a button. So I have a NSViewController with many subviews in it. When I click a button, a new NSView with click gestures and buttons is added on top. But I can't press any of them, they don't respond unless a click for 2 seconds and then release. I've tried disabling the gestures of the holder but it didn't work. Any suggestions?
Well, some of the rest of us do. In my case, it's for buttons on a view in a sheet, so "many subviews" isn't likely it. My view controller for the sheet is about 100 lines. Still debugging...
At present the VC is as follows. The snp.makeConstraints calls are for SnapKit (from GitHub)
#objc
class ThreadEditSheetViewController: NSViewController {
/// The container for the graphics view
#IBOutlet var sheetView: NSView!
/// The information packet initialized by the invoking view controller
var info: ThreadEditInfo!
/// API
override func viewDidLoad() {
super.viewDidLoad()
}
/// API
override func viewWillAppear() {
guard let gvc = (try? self.bundleLoader(id: "GraphicsViewController")) as? GraphicsViewController else {
fatalUserAlert(error: AppError.UIConstructionFailure, message: "Can't find GraphicsViewController for ThreadEditSheetViewController")}
let gv = gvc.view
self.view.addSubview(gv)
// Spaces in title text move it left to avoid visual overlap with scroll bar. Don't know how to do it with
// constraints given the scrolling view
let done = makeButton(gvc: gvc, title: "done ", action: #selector(doneEditing(_:)))
done.snp.makeConstraints{ (make) in
make.top.equalTo(gv).offset(-5)
make.right.equalTo(gv).offset(-5)
}
let cancel = makeButton(gvc: gvc, title: "cancel", action: #selector(cancelEditing(_:)))
cancel.snp.makeConstraints{ (make) in
make.top.equalTo(gv).offset(-5)
make.left.equalTo(gv).offset(5)
}
self.view.becomeFirstResponder()
super.viewWillAppear()
return
}
func makeButton(gvc: NSViewController, title: String, action: Selector) -> NSButton {
let button = NSButton(title: title, target: self, action: action)
let gv = gvc.view
gv.addSubview(button)
button.backgroundColor = .clear
button.setButtonType(.momentaryChange)
button.isTransparent = true
return button
}
#objc
func doneEditing(_ sender: Any) {
self.dismissViewController(self)
}
#objc
func cancelEditing(_ sender: Any) {
self.dismissViewController(self)
}
}

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.