You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS SwiftUI菜单栏应用:设置唤起及Dock图标显示问题

解决SwiftUI macOS菜单栏应用设置窗口的两个问题

问题1:已打开的设置窗口无法前置激活

默认SettingsLink点击时,若设置窗口已存在,不会自动将其前置。解决方法是自定义按钮逻辑,先检测窗口是否存在,存在则激活,否则打开设置:

  • 实现一个WindowManager类,负责窗口状态跟踪和激活逻辑
  • 用自定义Button替代SettingsLink,调用激活方法

问题2:控制设置窗口状态对应的Dock图标显示

要实现设置窗口打开时显示Dock图标,关闭时隐藏,需要监听窗口生命周期事件,动态切换应用激活策略:

  • 监听窗口激活和关闭通知,识别设置窗口
  • 启动时设置.accessory策略隐藏Dock,设置窗口打开时切换为.regular显示Dock,关闭时恢复

完整实现代码

import SwiftUI

final class WindowManager: ObservableObject {
    @Published var isSettingsWindowOpen = false
    private let settingsWindowID = NSUserInterfaceItemIdentifier("SettingsWindow")
    
    init() {
        // 注册窗口状态监听通知
        NotificationCenter.default.addObserver(self, selector: #selector(handleSettingsWindowOpen), name: NSWindowDidBecomeKeyNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(handleSettingsWindowClose), name: NSWindowDidCloseNotification, object: nil)
    }
    
    @objc private func handleSettingsWindowOpen(_ notification: Notification) {
        guard let window = notification.object as? NSWindow,
              window.identifier == settingsWindowID else { return }
        
        isSettingsWindowOpen = true
        NSApp.setActivationPolicy(.regular)
        NSApp.dockTile.isHidden = false
    }
    
    @objc private func handleSettingsWindowClose(_ notification: Notification) {
        guard let window = notification.object as? NSWindow,
              window.identifier == settingsWindowID else { return }
        
        isSettingsWindowOpen = false
        // 确认无其他窗口打开时隐藏Dock
        if NSApp.windows.allSatisfy({ $0.identifier != settingsWindowID }) {
            NSApp.setActivationPolicy(.accessory)
            NSApp.dockTile.isHidden = true
        }
    }
    
    // 激活现有设置窗口或打开新窗口
    func activateOrOpenSettings() {
        if let settingsWindow = NSApp.windows.first(where: { $0.identifier == settingsWindowID }) {
            settingsWindow.makeKeyAndOrderFront(nil)
            NSApp.activate(ignoringOtherApps: true)
        } else {
            NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
        }
    }
}

final class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_: Notification) {
        // 启动时设置为纯菜单栏应用,隐藏Dock图标
        NSApp.setActivationPolicy(.accessory)
        NSApp.dockTile.isHidden = true
    }
}

@main
struct TestApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject private var windowManager = WindowManager()
    
    var body: some Scene {
        Settings {
            Text("123")
                .onAppear {
                    // 给设置窗口分配唯一标识符,用于后续识别
                    if let window = NSApp.windows.first(where: {
                        $0.contentView?.superview?.subviews.contains(where: { $0 is NSHostingView }) ?? false
                    }) {
                        window.identifier = windowManager.settingsWindowID
                    }
                }
        }
        MenuBarExtra("Test", systemImage: "star.fill") {
            Button("Open settings") {
                windowManager.activateOrOpenSettings()
            }
        }
    }
}

关键说明

  • 窗口识别:通过给设置窗口分配唯一identifier,避免其他窗口的通知干扰,比依赖窗口标题更可靠(不受本地化影响)
  • 激活逻辑:自定义按钮先检查设置窗口是否存在,存在则调用makeKeyAndOrderFront和activate将其前置,否则触发系统设置窗口打开动作
  • Dock控制:启动时用.accessory策略隐藏Dock,设置窗口激活时切换为.regular显示Dock,关闭时检查无其他窗口后恢复隐藏状态

内容的提问来源于stack exchange,提问作者graduationchampion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:35:33