简体   繁体   English

如何更改 macOS 菜单栏图标?

[英]How to change a macOS menubar icon?

I'm trying to change a macOS menubar icon at the click of a button based on this question .我正在尝试根据这个问题单击按钮更改 macOS 菜单栏图标。

However, when I run the app and click the button, I get a Could not cast value of type 'SwiftUI.AppDelegate' (0x20dfafd68) to 'MenuBarTest.AppDelegate' (0x10442c580).但是,当我运行应用程序并单击按钮时,我得到Could not cast value of type 'SwiftUI.AppDelegate' (0x20dfafd68) to 'MenuBarTest.AppDelegate' (0x10442c580). error on this line:此行错误:

let appDelegate = NSApplication.shared.delegate as! AppDelegate

Main Swift File主 Swift 文件

@main
struct MenuBarTestApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {
    var statusItem: NSStatusItem?
    var popOver = NSPopover()
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        let menuView = MenuBarView()
        
        popOver.behavior = .transient
        popOver.animates = true
        popOver.contentViewController = NSViewController()
        popOver.contentViewController?.view = NSHostingView(rootView: menuView)
        
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        
        if let MenuButton = statusItem?.button {
            MenuButton.image = NSImage(named: "settings_bw")
            MenuButton.image?.size = NSSize(width: 18.0, height: 18.0)
            MenuButton.action = #selector(MenuButtonToggle)
        }
    }
    
    func setIcon(colorType:String) {
        var icon = NSImage(named: "settings_color")
        
        if colorType != "color" {
            icon = NSImage(named: "settings_bw")
        }
        
        statusItem?.button?.image = icon
    }
    
    @objc
    func MenuButtonToggle(sender: AnyObject) {
        if popOver.isShown {
            popOver.performClose(sender)
        } else {
            if let MenuButton = statusItem?.button {
                self.popOver.show(relativeTo: MenuButton.bounds, of: MenuButton, preferredEdge: NSRectEdge.minY)
                popOver.contentViewController?.view.window?.makeKey()
            }
        }
    }
}

MenuBar View菜单栏视图

struct MenuBarView: View {
    var body: some View {
        VStack {
            Button(action: {
                let appDelegate = NSApplication.shared.delegate as! AppDelegate
                appDelegate.setIcon(colorType: "color")
            }, label: {
                Text("Change Icon")
            })
        }
        .frame(width: 150, height: 100)
    }
}

First, I'll answer the question as asked, but then I'll follow up with why it actually doesn't matter because it's solving the wrong problem.首先,我会按要求回答问题,然后我会跟进为什么它实际上无关紧要,因为它解决了错误的问题。

Accessing the App Delegate in a SwiftUI lifecycle app在 SwiftUI 生命周期应用程序中访问 App Delegate

When you're using SwiftUI to manage your app lifecycle, you don't use an app delegate directly.当您使用 SwiftUI 来管理您的应用程序生命周期时,您不会直接使用应用程序委托。 SwiftUI will set the application's app delegate to an instance of its own class, SwiftUI.AppDelegate . SwiftUI 会将应用程序的应用委托设置为它自己的 class, SwiftUI.AppDelegate的一个实例。 This is what you saw when trying to cast it to MenuBarTest.AppDelegate .这是您在尝试将其转换为MenuBarTest.AppDelegate时所看到的。 You can't cast it to your class, because it's not an instance of your class.您不能将它转换为您的 class,因为它不是您的 class 的实例。

As a backwards compatibility feature, SwiftUI's app delegate lets you provide your own delegate instance to that it will forward to.作为向后兼容功能,SwiftUI 的应用程序委托允许您提供自己的委托实例,它将转发到该实例。 This is wired up with the NSApplicationDelegateAdaptor property wrapper.这与NSApplicationDelegateAdaptor属性包装器连接在一起。 If you want to access an instance, that delegate instance property is the way to get at it ( not NSApplication.shared.delegate ).如果您想访问一个实例,delegate实例属性是获取它的方法(不是NSApplication.shared.delegate )。

Don't even use the App Delegate甚至不要使用 App Delegate

You can disregard everything I said so far, because this is just a bad design, and you shouldn't use it.你可以无视我到目前为止所说的一切,因为这只是一个糟糕的设计,你不应该使用它。 The App Delegate should be strictly limited to code related to managing the life cycle of your app and how it interacts with the system. App Delegate 应严格限于与管理应用程序生命周期及其与系统交互方式相关的代码。 It should not be a kitchen-junk-drawer of any code that fits.应该是任何适合的代码的厨房垃圾抽屉。

Having random code right in the App Delegate is the AppKit equivalent to putting all your code in the main() function of a C program.在 App Delegate 中拥有随机代码是 AppKit 等同于将所有代码放在 C 程序的main() function 中。 It's done for brevity, but rarely a good idea.这样做是为了简洁,但很少是个好主意。 This is what you'll often see in tutorials, which aim to keep file and line count at a minimum, to illustrate some other point (eg Related to menu bar configuration, in this case).这是您在教程中经常看到的内容,旨在将文件和行数保持在最低限度,以说明其他一些要点(例如,在本例中与菜单栏配置相关)。

As you'll notice, none of the code in this app delegate does much that's specific to the App life cycle.您会注意到,此应用程序委托中的代码都没有做很多特定于应用程序生命周期的事情。 It just calls out to NSStatusBar.它只是调用 NSStatusBar。 This code can just be put in any other appropriate place, like any other stateful SwiftUI code.这段代码可以放在任何其他适当的地方,就像任何其他有状态的 SwiftUI 代码一样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM