Reputation: 232
I'm building a menu bar application with SwiftUI for macOS Big Sur and can't figure out how to open the popover (the app's main window, since it's a menu bar app) with a keyboard shortcut. I want users to be able to view the window by pressing Command + [a letter] regardless of what else they're doing on their computer (as long as the application is open of course). Here are the main functions and code that control the popover:
@main
struct MenuBarPopoverApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
Settings{
EmptyView()
}
.commands {
MenuBarPopoverCommands(appDelegate: appDelegate)
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
var popover = NSPopover.init()
var statusBarItem: NSStatusItem?
var contentView: ContentView!
override class func awakeFromNib() {}
func applicationDidFinishLaunching(_ notification: Notification) {
print("Application launched")
NSApplication.shared.activate(ignoringOtherApps: true)
contentView = ContentView()
popover.animates = false
popover.behavior = .transient
let contentVc = NSViewController()
contentVc.view = NSHostingView(rootView: contentView.environmentObject(self))
popover.contentViewController = contentVc
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let itemImage = NSImage(named: "statusBarIcon")
itemImage?.isTemplate = true
statusBarItem?.button?.image = itemImage
statusBarItem?.button?.action = #selector(AppDelegate.togglePopover(_:))
}
@objc func showPopover(_ sender: AnyObject?) {
if let button = statusBarItem?.button {
NSApplication.shared.activate(ignoringOtherApps: true)
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
@objc func closePopover(_ sender: AnyObject?) {
popover.performClose(sender)
}
@objc func togglePopover(_ sender: AnyObject?) {
if popover.isShown {
closePopover(sender)
} else {
showPopover(sender)
}
}
}
And the MenuBarPopoverCommands (the main part of the app is a text editor, so I have a bunch of keyboard shortcuts relating to that):
struct MenuBarPopoverCommands: Commands {
let appDelegate: AppDelegate
init(appDelegate: AppDelegate) {
self.appDelegate = appDelegate
}
var body: some Commands {
CommandMenu("Edit") {
Section {
Button("Cut") {
appDelegate.contentView.editCut()
}.keyboardShortcut(KeyEquivalent("x"), modifiers: .command)
Button("Copy") {
appDelegate.contentView.editCopy()
}.keyboardShortcut(KeyEquivalent("c"), modifiers: .command)
Button("Paste") {
appDelegate.contentView.editPaste()
}.keyboardShortcut(KeyEquivalent("v"), modifiers: .command)
Button("Undo") {
appDelegate.contentView.undo()
}.keyboardShortcut(KeyEquivalent("z"), modifiers: .command)
Button("Redo") {
appDelegate.contentView.redo()
}.keyboardShortcut(KeyEquivalent("z"), modifiers: [.command, .shift])
Button("Bold") {
appDelegate.contentView.bold()
}.keyboardShortcut(KeyEquivalent("b"), modifiers: .command)
Button("Italic") {
appDelegate.contentView.italic()
}.keyboardShortcut(KeyEquivalent("i"), modifiers: .command)
Button("Select All") {
appDelegate.contentView.editSelectAll()
}.keyboardShortcut(KeyEquivalent("a"), modifiers: .command)
}
}
}
}
Upvotes: 2
Views: 852
Reputation: 1570
Swift 5 solution was presented in https://stackoverflow.com/a/58225397/3984522. However, there's a nice package, which does the job https://github.com/soffes/HotKey in a couple of lines of code:
import SwiftUI
import HotKey
@main
struct MenuBarPopoverApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
Settings{
EmptyView()
}
.commands {
MenuBarPopoverCommands(appDelegate: appDelegate)
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
var popover = NSPopover.init()
var statusBarItem: NSStatusItem?
var contentView: ContentView!
let hotKey = HotKey(key: .x, modifiers: [.control, .shift]) // Global hotkey
override class func awakeFromNib() {}
func applicationDidFinishLaunching(_ notification: Notification) {
print("Application launched")
NSApplication.shared.activate(ignoringOtherApps: true)
contentView = ContentView()
popover.animates = false
popover.behavior = .transient
let contentVc = NSViewController()
contentVc.view = NSHostingView(rootView: contentView.environmentObject(self))
popover.contentViewController = contentVc
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let itemImage = NSImage(systemSymbolName: "eye", accessibilityDescription: "eye")
itemImage?.isTemplate = true
statusBarItem?.button?.image = itemImage
statusBarItem?.button?.action = #selector(AppDelegate.togglePopover(_:))
hotKey.keyUpHandler = { // Global hotkey handler
self.togglePopover()
}
}
@objc func showPopover(_ sender: AnyObject? = nil) {
if let button = statusBarItem?.button {
NSApplication.shared.activate(ignoringOtherApps: true)
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
@objc func closePopover(_ sender: AnyObject? = nil) {
popover.performClose(sender)
}
@objc func togglePopover(_ sender: AnyObject? = nil) {
if popover.isShown {
closePopover(sender)
} else {
showPopover(sender)
}
}
}
Upvotes: 4