How to create a menu bar SwiftUI app for MacOS Big Sur
Asked Answered
D

1

11

I'm trying to create an app that only appears in the MacOS Big Sur menu bar (top right corner) by following this tutorial: https://medium.com/@acwrightdesign/creating-a-macos-menu-bar-application-using-swiftui-54572a5d5f87/. It worked on Xcode 11 and MacOS Catalina because there was an AppDelegate.swift file, but I've heard that this was replaced by this method:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

However, the first few steps of the tutorial require that I make several changes to the (now non-existant) AppDelegate.swift file. I've tried making these changes in MyApp.swift, but I can't seem to get it to work. Is anyone willing to help me adapt that tutorial for MacOS Big Sur/Xcode 12?

Note: here's how the AppDelegate.swift file was supposed to look (if it existed) according to the tutorial (if you don't want to open the tutorial for whatever reason):

import Cocoa
import SwiftUI

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var popover: NSPopover!
    var statusBarItem: NSStatusItem!
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Create the popover
        let popover = NSPopover()
        popover.contentSize = NSSize(width: 400, height: 500)
        popover.behavior = .transient
        popover.contentViewController = NSHostingController(rootView: contentView)
        self.popover = popover
        
        // Create the status item
        self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
        
        if let button = self.statusBarItem.button {
            button.image = NSImage(named: "Icon")
            button.action = #selector(togglePopover(_:))
        }
    }
    
    @objc func togglePopover(_ sender: AnyObject?) {
        if let button = self.statusBarItem.button {
            if self.popover.isShown {
                self.popover.performClose(sender)
            } else {
                self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
            }
        }
    }
    
}
Dovev answered 13/11, 2020 at 5:59 Comment(1)
You can still create an AppDelegate instead of using the new SwiftUI lifecycle. It has not even been deprecated.Amboina
D
14

In your App scene, use NSApplicationDelegateAdaptor property wrapper to tell SwiftUI it should use your AppDelegate class for the application delegate. So your App class should look like this:

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

Knowledge base link

Dissatisfactory answered 19/2, 2021 at 17:29 Comment(1)
For a complete solution, see this repo.Unifilar

© 2022 - 2024 — McMap. All rights reserved.