I have a small NSStatusItem
only application, with a settings windows. From a menu in the NSStatusItem
I want to reactivate the main window.
The app keeps a single NSWindow
instance that is set to not be released when closed.
Upon closing the window, I change the activation policy of the app to accessory
, so the Dock icon is hidden away from the user.
When the user reopens the window, I change the activation policy back to regular
and show the window again.
This is the entirety of my app delegate in a sample app that isolates the issue:
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var statusItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: EmptyView())
window.makeKeyAndOrderFront(nil)
window.isReleasedWhenClosed = false
window.delegate = self
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
statusItem.button?.title = "X"
statusItem.button?.target = self
statusItem.button?.action = #selector(toggleWindow(sender:))
NSApp.setActivationPolicy(.accessory)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
@objc func toggleWindow(sender: AnyObject) {
if window.isVisible {
window.close()
} else {
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
NSApp.setActivationPolicy(.regular)
}
}
}
extension AppDelegate: NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory)
}
}
The app is the default template from Xcode 11, but I changed the main view to EmptyView()
so no extra class is needed.
If I close the window with the semaphore button, and reopen it with status bar icon, it works fine.
However, if I close it through CMD-W and reopen it, I get the window back, but the menu remains selected, as if the previous Close command hadn't completed.
Since the menu is in this weird state, the app does not respond to events until the menu issue is solved (by clicking another menu item, for example).
Update:
As a workaround, I discovered that if I change the activation policy after a few milliseconds, the menu issue is solved:
func windowWillClose(_ notification: Notification) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
NSApp.setActivationPolicy(.accessory)
}
}
However, once the window appears it is still not properly processing the events (the first click is missed).