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()
Related
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
}
}
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?()
}
}
How can I add a menuContext programmatically to ScrollView / CollectionView?
This is my code but when I right-click the menu does not appear.
private var selectedPatients: [XxPatient] = []
private var contextMenu: NSMenu {
let rightClickMenu = NSMenu()
// edit info patient
let editPatientItem = NSMenuItem()
editPatientItem.title = LocalizationUtility.localizedString("contextMenu.editPatientInfo")
editPatientItem.target = self
editPatientItem.action = #selector(didSelectContextItem(_ :))
editPatientItem.tag = 1
rightClickMenu.addItem(editPatientItem)
return rightClickMenu
}
#objc func didSelectContextItem(_ sender: NSMenuItem) {
if sender.tag == 1 {
// edit info patient
openEditPatientInfo(with: selectedPatients.first)
}
}
private func openEditPatientInfo(with patient: XxPatient?) {
let next = HomeNewPatientController.instantiateFromStoryboard()
next?.patient = patient
// next?.delegate = self
let navigationController = DSNavigationController(withRootViewController: next)
UIUtility.presentModalController(navigationController, from: self)
}
I need to create this view by calling it from another view, I need to be able to create the view without the appdelegate. I need help with this. Thanks
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
SampleData.createSampleData()
let graph = Graph()
let search = Search<Entity>(graph: graph).for(types: "Category")
var viewControllers = [PostsViewController]()
for category in search.sync() {
if let name = category["name"] as? String {
viewControllers.append(PostsViewController(category: name))
}
}
let pageTabBarController = AppPageTabBarController(viewControllers: viewControllers)
let toolbarController = AppToolbarController(rootViewController: pageTabBarController)
let menuController = AppMenuController(rootViewController: toolbarController)
let leftViewController = LeftViewController()
let navigationDrawerController = NavigationDrawerController(rootViewController: menuController,
leftViewController: leftViewController
)
window = UIWindow(frame: Screen.bounds)
window!.rootViewController = navigationDrawerController
window!.makeKeyAndVisible()
return true
}
**This is my code, crash all time **
import UIKit
import Material
import Graph
class ViewController:PageTabBarController {
var window: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
open override func prepare() {
super.prepare()
SampleData.createSampleData()
let graph = Graph()
let search = Search<Entity>(graph: graph).for(types: "Category")
var viewControllers = [PostsViewController]()
for category in search.sync() {
if let name = category["name"] as? String {
viewControllers.append(PostsViewController(category: name))
}
}
let pageTabBarController = AppPageTabBarController(viewControllers: viewControllers)
let toolbarController = AppToolbarController(rootViewController: pageTabBarController)
let menuController = AppMenuController(rootViewController: toolbarController)
let leftViewController = LeftViewController()
let navigationDrawerController = NavigationDrawerController(rootViewController: menuController,
leftViewController: leftViewController
)
window = UIWindow(frame: Screen.bounds)
window!.rootViewController = navigationDrawerController
window!.makeKeyAndVisible()
}
}
extension ViewController: PageTabBarControllerDelegate {
func pageTabBarController(_ pageTabBarController: PageTabBarController, didTransitionTo viewController: UIViewController) {
}
}
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) {
// ...
}
}