简体   繁体   中英

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 .

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). error on this line:

let appDelegate = NSApplication.shared.delegate as! AppDelegate

Main Swift File

@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

When you're using SwiftUI to manage your app lifecycle, you don't use an app delegate directly. SwiftUI will set the application's app delegate to an instance of its own class, SwiftUI.AppDelegate . This is what you saw when trying to cast it to MenuBarTest.AppDelegate . You can't cast it to your class, because it's not an instance of your class.

As a backwards compatibility feature, SwiftUI's app delegate lets you provide your own delegate instance to that it will forward to. This is wired up with the NSApplicationDelegateAdaptor property wrapper. If you want to access an instance, that delegate instance property is the way to get at it ( not NSApplication.shared.delegate ).

Don't even use the 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. 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. 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. This code can just be put in any other appropriate place, like any other stateful SwiftUI code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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