NSStatusItem menu Action not firing - swift

The NSStatusItem menu shows correctly but when I click on menu the action not fire.
Since the app is compatible with Mac Catalyst, I created framework then passing Framework to main app in order to show the menu of NSStatusItem, it work correctly but I have the issue for action of the menu that doesn't work.
Here is my code:
#objc class AppKitController: NSObject {
var statusBarItem: StatusBarItemControler!
override init() {
super.init()
print("[AppKitController] Loaded successfully")
self.statusBarItem = StatusBarItemControler()
self.statusBarItem.updateTitle()
self.statusBarItem.updateMenu()
}
}
class StatusBarItemControler {
let item: NSStatusItem
init() {
self.item = NSStatusBar.system.statusItem(
withLength: NSStatusItem.variableLength
)
let statusBarMenu = NSMenu(title: "APPMenu")
self.item.menu = statusBarMenu
}
func updateTitle() {
let title = "AppMenu"
print("Update title")
DispatchQueue.main.async {
if let button = self.item.button {
button.title = "\(title)"
button.target = self
}
}
}
func updateMenu() {
if let statusBarMenu = self.item.menu {
statusBarMenu.autoenablesItems = false
statusBarMenu.removeAllItems()
statusBarMenu.addItem(NSMenuItem.separator())
statusBarMenu.addItem(NSMenuItem.separator())
self.createPreferencesSection()
}
}
func createPreferencesSection() {
self.item.menu!.addItem(
withTitle: "Open",
action: #selector(openPrefecencesWindow),
keyEquivalent: ",")
self.item.menu!.addItem(
withTitle: "Quit",
action: #selector(quit),
keyEquivalent: "q")
}
#objc func openPrefecencesWindow(_: NSStatusBarButton?) {
print("Open preferences window")
}
#objc func quit(_: NSStatusBarButton?) {
print("Open preferences window")
}
}

Thank you #Alexander, I have found the solution and it works.
class AppKitController: NSObject,NSApplicationDelegate,NSWindowDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
override init() {
super.init()
NSApplication.shared.delegate = self
NSApplication.shared.mainWindow?.delegate = self
statusItem.button?.title = "Your_App_Name"
statusItem.menu = createMenu()
print("[AppKitController] Loaded successfully")
}
func createMenu() -> NSMenu{
let menu = NSMenu()
let openMenuItem = menu.addItem(withTitle: "Open", action: #selector(openMenu), keyEquivalent: "")
openMenuItem.target = self
return menu
}
#objc func openMenu(_ sender:Any?){
print("Open menu called")
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
print("Window should close")
return false
}
}

Related

Using UIEditMenuInteraction with UITextView

How can we use UIEditMenuInteraction with UITextView to customize menu and add more buttons?
Before iOS 16 I was using:
UIMenuController.shared.menuItems = [menuItem1, menuItem2, menuItem3]
Try this sample source:
class ViewController: UIViewController {
#IBOutlet weak var txtView: UITextView!
var editMenuInteraction: UIEditMenuInteraction?
override func viewDidLoad() {
super.viewDidLoad()
setupEditMenuInteraction()
}
private func setupEditMenuInteraction() {
// Addding Menu Interaction to TextView
editMenuInteraction = UIEditMenuInteraction(delegate: self)
txtView.addInteraction(editMenuInteraction!)
// Addding Long Press Gesture
let longPressGestureRecognizer =
UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
txtView.addGestureRecognizer(longPressGestureRecognizer)
}
#objc
func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard gestureRecognizer.state == .began else { return }
let configuration = UIEditMenuConfiguration(
identifier: "textViewEdit",
sourcePoint: gestureRecognizer.location(in: txtView)
)
editMenuInteraction?.presentEditMenu(with: configuration)
}
}
extension ViewController: UIEditMenuInteractionDelegate {
func editMenuInteraction(_ interaction: UIEditMenuInteraction,
menuFor configuration: UIEditMenuConfiguration,
suggestedActions: [UIMenuElement]) -> UIMenu? {
var actions = suggestedActions
let customMenu = UIMenu(title: "", options: .displayInline, children: [
UIAction(title: "menuItem1") { _ in
print("menuItem1")
},
UIAction(title: "menuItem2") { _ in
print("menuItem2")
},
UIAction(title: "menuItem3") { _ in
print("menuItem3")
}
])
actions.append(customMenu)
return UIMenu(children: actions) // For Custom and Suggested Menu
return UIMenu(children: customMenu.children) // For Custom Menu Only
}
}
Output

Argument type 'ContentView' expected to be an instance of a class or class-constrained type. SwiftUI

I want to trigger a function that's in StatusBar Controller with a button in Content view which hides or shows a menubar popover but i'm getting this error:
Argument type 'ContentView' expected to be an instance of a class or
class-constrained type
StatusBar Controller
..........
init(_ popover: NSPopover)
{
self.popover = popover
statusBar = NSStatusBar.init()
statusItem = statusBar.statusItem(withLength: 28.0)
if let statusBarButton = statusItem.button {
statusBarButton.image = #imageLiteral(resourceName: "StatusBarIcon")
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
}
eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown], handler: mouseEventHandler)
}
#objc func showPopover(sender: AnyObject) {
if(popover.isShown) {
hidePopover(sender)
}
else {
displayPopover()
}
}
func displayPopover() {
if let statusBarButton = statusItem.button {
popover.show(relativeTo: statusBarButton.bounds, of: statusBarButton, preferredEdge: NSRectEdge.maxY)
eventMonitor?.start()
}
}
func hidePopover(_ sender: AnyObject) {
popover.performClose(sender)
eventMonitor?.stop()
}
func mouseEventHandler(_ event: NSEvent?) {
if(popover.isShown) {
hidePopover(event!)
}
}
ContentView
var statusBar: StatusBarController?
Button("Show/Hide Popover "){
statusBar?.showPopover(sender: self)
}
Just give it nil sender, like
Button("Show/Hide Popover "){
statusBar?.showPopover(sender: nil) // << here !!
}
and make all controller actions with optional sender, like
#objc func showPopover(sender: AnyObject?) {
// ...

Conditionally show either a Window or the Menu bar view SwiftUI macOS

I'm creating an app where it simply lives in the menu bar, however I'd like a full-sized normal window to pop up if the user is not logged in, I have made a little pop over window which is sufficient for my main app to go into:
The code I have used to achieve this:
class AppDelegate: NSObject, NSApplicationDelegate{
var statusItem: NSStatusItem?
var popOver = NSPopover()
func applicationDidFinishLaunching(_ notification: Notification) {
let menuView = MenuView().environmentObject(Authentication())
popOver.behavior = .transient
popOver.animates = true
popOver.contentViewController = NSViewController()
popOver.contentViewController?.view = NSHostingView(rootView: menuView)
popOver.contentViewController?.view.window?.makeKey()
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let MenuButton = statusItem?.button{
MenuButton.image = NSImage(systemSymbolName: "gearshape.fill", accessibilityDescription: nil)
MenuButton.action = #selector(MenuButtonToggle)
}
if let window = NSApplication.shared.windows.first {
window.close()
}
}
#objc func MenuButtonToggle(sender: AnyObject? = nil){
if popOver.isShown{
popOver.performClose(sender)
}
else{
if let menuButton = statusItem?.button{
NSApplication.shared.activate(ignoringOtherApps: true)
self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.minY)
}
}
}
#objc func closePopover(_ sender: AnyObject? = nil) {
popOver.performClose(sender)
}
#objc func togglePopover(_ sender: AnyObject? = nil) {
if popOver.isShown {
closePopover(sender)
} else {
MenuButtonToggle(sender: sender)
}
}
}
I make the popover view inside the AppDelegate, I'd like to either render this (with the icon in the menu bar) or just a normal macOS window (without the icon in the menu bar). Then have the ability to switch between the two easily via something like this:
if session != nil{
// show menu bar style
else{
// show window view to log in
}
I think you can reference the demo
Create a reference to an instance of NSWindowController in your AppDelegate class.
private var mainVC: MainViewController?
func showMainWindow() {
if mainVC == nil {
mainVC = MainViewController.create()
mainVC?.onWindowClose = { [weak self] in
self?.mainVC = nil
}
}
mainVC?.showWindow(self)
}
The MainviewController is like following:
class MainViewController: NSWindowController {
var onWindowClose: (() -> Void)?
static func create() -> MainViewController {
let window = NSWindow()
window.center()
window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
window.title = "This is a test main title"
let vc = MainViewController(window: window)
// Use your SwiftUI here as the Main Content
vc.contentViewController = NSHostingController(rootView: ContentView())
return vc
}
override func showWindow(_ sender: Any?) {
super.showWindow(sender)
NSApp.activate(ignoringOtherApps: true)
window?.makeKeyAndOrderFront(self)
window?.delegate = self
}
}
extension MainViewController: NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
onWindowClose?()
}
}

Unrecognized selector on OS X 10.11

I'm using a custom item with menu in the system Status Bar for controlling some functions in my app. Here is my code:
import Foundation
class StatusBarMenuController {
var statusItem: NSStatusItem
init() {
self.statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength)
statusItem.image = NSImage(named: "StatusBarButtonImage")
let menu = NSMenu()
let isListeningMenuItem = NSMenuItem(title: "Listening", action: #selector(StatusBarMenuController.isListeningAction(_:)), keyEquivalent: "")
isListeningMenuItem.isAlternate = true
isListeningMenuItem.target = self
isListeningMenuItem.state = NSOnState
menu.addItem(isListeningMenuItem)
statusItem.menu = menu
}
#objc func isListeningAction(_ item: NSMenuItem) {
if (item.state == NSOffState) {
item.state = NSOnState
// Handle switch-on action...
}
else {
item.state = NSOffState
// Handle switch-off action...
}
}
}
This class is instantiated in applicationDidFinishLaunching method of AppDelegate.
All works fine on the latest version of macOS (10.12) - I tried it on multiple computers, but when try to start the app on a machine with older version of os, e.g. OS X 10.11, it instantly crashes.
Crash details:
Application Specific Information:
Unrecognized selector -[MyAppName.StatusBarMenuController methodForSelector:]
abort() called
Any ideas why is this happening?
Deriving from NSObject solved this issue:
import Foundation
class StatusBarMenuController: NSObject {
var statusItem: NSStatusItem
override init() {
self.statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength)
super.init()
statusItem.image = NSImage(named: "StatusBarButtonImage")
let menu = NSMenu()
let isListeningMenuItem = NSMenuItem(title: "Listening", action: #selector(StatusBarMenuController.isListeningAction(_:)), keyEquivalent: "")
isListeningMenuItem.isAlternate = true
isListeningMenuItem.target = self
isListeningMenuItem.state = NSOnState
menu.addItem(isListeningMenuItem)
statusItem.menu = menu
}
#objc func isListeningAction(_ item: NSMenuItem) {
if (item.state == NSOffState) {
item.state = NSOnState
// Handle switch-on action...
}
else {
item.state = NSOffState
// Handle switch-off action...
}
}
}
It's a very strange behavior, because in other parts of my app I'm using selectors with NotificationCenter in not NSObject-derived classes and it works, e.g.:
class StatusBarMenuController {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleMyNotification),
name: NSNotification.Name(rawValue: myNotification),
object: nil
)
#objc func handleMyNotifiction(_ notification: Notification) {
// ...
}
}

Swift 2.2 selector in NSMenuItem

I have a simple one file menu bar app in swift:
import Cocoa
class StatusBarApp : NSObject {
func buildMenu() {
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
statusItem.title = "StatusBarApp"
let menu = NSMenu()
let aboutMenuItem = NSMenuItem()
aboutMenuItem.title = "About"
aboutMenuItem.target = self
aboutMenuItem.action = #selector(about)
menu.addItem(aboutMenuItem)
statusItem.menu = menu
}
func about() {
print("XXX")
}
}
NSApplication.sharedApplication()
StatusBarApp().buildMenu()
NSApp.run()
I can't make the "About" menu bar item to connected to the about() function. When I run the app, the "About" item is disabled.
How do I pass the selector to menu item action in Swift 2.2? Thanks
The selector is supposed to have a parameter (the NSMenuItem instance)
aboutMenuItem.action = #selector(StatusBarApp.about(_:))
...
func about(sender : NSMenuItem) {
print("XXX")
}
Edit:
The solution is to run the app as full Cocoa app including its delegate.
I added a second menu item to terminate the app.
import Cocoa
class StatusBarApp : NSObject, NSApplicationDelegate {
var statusItem : NSStatusItem!
func applicationDidFinishLaunching(aNotification: NSNotification) {
statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
statusItem.title = "StatusBarApp"
let menu = NSMenu()
let aboutMenuItem = NSMenuItem(title:"About", action:#selector(StatusBarApp.about(_:)), keyEquivalent:"")
aboutMenuItem.target = self
let quitMenuItem = NSMenuItem(title:"Quit", action:#selector(StatusBarApp.quit(_:)), keyEquivalent:"")
quitMenuItem.target = self
menu.addItem(aboutMenuItem)
menu.addItem(quitMenuItem)
statusItem.menu = menu
}
func about(sender : NSMenuItem) {
print("XXX")
}
func quit(sender : NSMenuItem) {
NSApp.terminate(self)
}
}
NSApplication.sharedApplication()
let statusBarApp = StatusBarApp()
NSApp.delegate = statusBarApp
NSApp.run()
update action
aboutMenuItem.action = Selector("about")
and add
aboutMenuItem.enabled = true
Consider this:
import Cocoa
class StatusBarApp : NSObject {
func buildMenu() {
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
statusItem.title = "StatusBarApp"
let menu = NSMenu()
let aboutMenuItem = NSMenuItem()
aboutMenuItem.title = "About"
aboutMenuItem.target = self
aboutMenuItem.action = #selector(about)
menu.addItem(aboutMenuItem)
statusItem.menu = menu
}
func about() {
print("XXX")
}
}
let app = StatusBarApp()
NSApplication.sharedApplication()
app.buildMenu()
NSApp.run()