macOS状态栏应用窗口置顶时菜单偶尔失效的问题求助
问题
我开发的一款macOS状态栏应用存在偶现问题(无法100%复现):当显示引导窗口(Onboarding)时,窗口处于聚焦状态时菜单正常工作,窗口未聚焦时菜单有时无法显示。移除OnboardingWindowController中设置NSApplication.shared.activate(ignoringOtherApps: true)和window?.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))这两行代码后,问题消失。但我需要引导窗口保持在前台(避免用户在引导流程中打开设置或其他窗口而偏离流程,影响转化),请问如何在保留窗口前台显示的同时修复菜单失效问题?
相关代码
OnboardingWindowController代码
class OnboardingWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() NSApplication.shared.activate(ignoringOtherApps: true) window?.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) } }
窗口打开代码
@MainActor private func showOnboardingWindow() { showInitialController(ofStoryboard: "Onboarding", withIdentifier: "OnboardingWindowController") } @MainActor private func showInitialController( ofStoryboard storyboardName: String, withIdentifier identifier: String ) { closeAllWindows() DispatchQueue.main.async { let storyboard = NSStoryboard(name: storyboardName, bundle: nil) guard let windowController = storyboard.instantiateController(withIdentifier: identifier) as? NSWindowController else { self.logHandler.log(errorString: "Could not initialize NSWindowController \(identifier) on \(storyboardName)") return } guard let window = windowController.window else { self.logHandler.log(errorString: "NSWindowController \(windowController) does not have a window while opening \(identifier) on \(storyboardName)") return // TODO: Handle? } if #available(macOS 14.0, *) { NSApp.activate() } else { NSApp.activate(ignoringOtherApps: true) } DispatchQueue.main.async { windowController.showWindow(self) //window.level = NSWindow.Level.init(rawValue: 1) window.orderFrontRegardless() //window.makeKeyAndOrderFront(nil) } } } @MainActor private func closeAllWindows() { NSApplication.shared.windows.filter { $0.contentViewController != nil }.forEach { window in window.close() } }
解决方案
1. 替换窗口层级,避免脱离应用管理
使用NSWindow.Level.floating替代最高层级maximumWindow,这个层级既能保证窗口在绝大多数普通窗口上方,又不会完全脱离应用的窗口逻辑链,避免菜单交互异常:
class OnboardingWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() // 移除原activate调用,统一在打开逻辑中处理 window?.level = .floating } }
2. 统一激活逻辑,避免重复操作
原代码中OnboardingWindowController和showInitialController都调用了应用激活,重复操作会导致焦点混乱。仅在窗口打开逻辑中处理激活,并确保窗口成为关键窗口:
修改showInitialController中的窗口显示代码段:
// 替换原嵌套的DispatchQueue.main.async代码块 windowController.showWindow(self) window.level = .floating window.makeKeyAndOrderFront(nil) // 确保窗口成为关键窗口,绑定应用菜单交互 if #available(macOS 14.0, *) { NSApp.activate() } else { NSApp.activate(ignoringOtherApps: true) }
3. 简化异步调用,避免时序问题
原代码存在多层嵌套的DispatchQueue.main.async,可能导致窗口显示时序混乱,直接移除冗余嵌套:
@MainActor private func showInitialController( ofStoryboard storyboardName: String, withIdentifier identifier: String ) { closeAllWindows() let storyboard = NSStoryboard(name: storyboardName, bundle: nil) guard let windowController = storyboard.instantiateController(withIdentifier: identifier) as? NSWindowController else { self.logHandler.log(errorString: "Could not initialize NSWindowController \(identifier) on \(storyboardName)") return } guard let window = windowController.window else { self.logHandler.log(errorString: "NSWindowController \(windowController) does not have a window while opening \(identifier) on \(storyboardName)") return } windowController.showWindow(self) window.level = .floating window.makeKeyAndOrderFront(nil) if #available(macOS 14.0, *) { NSApp.activate() } else { NSApp.activate(ignoringOtherApps: true) } }
4. 可选:窗口失焦时自动抢回焦点
如果需要严格保证引导窗口不被遮挡,可以添加窗口失焦监听,自动重新激活窗口:
class OnboardingWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() window?.level = .floating window?.delegate = self } } extension OnboardingWindowController: NSWindowDelegate { func windowDidResignKey(_ notification: Notification) { DispatchQueue.main.async { self.window?.makeKeyAndOrderFront(nil) if #available(macOS 14.0, *) { NSApp.activate() } else { NSApp.activate(ignoringOtherApps: true) } } } }
内容的提问来源于stack exchange,提问作者Lucas van Dongen

