使用 SwiftUI 创建 MacOS 无窗口菜单栏应用程序

Iva*_*Fan 4 macos xcode swift swiftui macos-big-sur

我正在寻找一种使用 SwiftUI 创建 Macos 无窗口菜单栏应用程序的解决方案。

我已经实现了与菜单栏相关的功能,问题是删除主窗口并从扩展坞中删除应用程序。

我尝试设置Application is agent (UIElement)YESin Info.plist,但它仅在窗口仍然存在时从扩展坞中隐藏应用程序。

我尝试修改@main,但它也不起作用。

有什么办法可以实现这一点吗?太感谢了!

我的代码:

  • App.swift
import SwiftUI

@main
struct DiskHealthApp: 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 = ContentView()
        
        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(systemSymbolName: "externaldrive", accessibilityDescription: nil)
            menuButton.action = #selector(menuButtonToggle)
        }
    }
    
    @objc func menuButtonToggle() {
        if let menuButton = statusItem?.button {
            self.popOver.show(relativeTo: menuButton.bounds, of: menuButton, preferredEdge: NSRectEdge.minY)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  • ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Run Code Online (Sandbox Code Playgroud)

Vis*_*kse 16

仅设置Application is agent (UIElement)toYES是不够的。您还必须AppDelegate通过添加以下内容来更改您的,

  1. ANSPopover
  2. 添加一个NSStatusItem

进入你的AppDelegate工作状态

如何制作 NSPopover?

  1. 转到您的应用程序委托。(如果您没有AppDelegate。创建一个AppDelegate类并将其委托到应用程序的起点,该应用程序将用 进行注释@mainAppDelegate按如下方式添加您的)
@main
struct SomeApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
}

Run Code Online (Sandbox Code Playgroud)
  1. 完成此操作后,您可以开始制作菜单栏应用程序,方法是将您更改Appdelegate为代表以下内容
class AppDelegate: NSObject, NSApplicationDelegate {

    // popover
    var popover: NSPopover!
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the SwiftUI view (i.e. the content).
        let contentView = ContentView()

        // Create the popover and sets ContentView as the rootView
        let popover = NSPopover()
        popover.contentSize = NSSize(width: 400, height: 500)
        popover.behavior = .transient
        popover.contentViewController = NSHostingController(rootView: contentView)
        self.popover = popover
        
        // Create the status bar 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(_:))
        }
    }
    
    // Toggles popover
    @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)
            }
        }
    }
    
}

Run Code Online (Sandbox Code Playgroud)
  1. 这样做之后你应该/可以设置Application is agent(UIElement)YES

最后一步

本节将分为 2 部分,4.14.2

  • 4.1适合那些使用AppDelegate生命周期来初始化项目的人
  • 4.2适合那些使用SwiftUI生命周期创建项目的人。
4.1 - AppDelegate 生命周期

转到您的Main.storyboard并删除Window Controller scene 如果您有Main.storyboard. 这应该消除NSWindow弹出的内容。

在此输入图像描述图片来源

4.2 - SwiftUI 生命周期

在这里,由于您没有用于Storyboard删除场景的文件,此时您的应用程序将使用NSWindow和启动NSPopover。要删除NSWindow打开的 ,请转到注释为 的应用程序起点@main,并对代码进行以下更改

@main
struct SomeApp: App {
    // Linking a created AppDelegate
    @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
    var body: some Scene {
        // IMPORTANT
        Settings {
            AnyView()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

欲了解更多信息,请参阅这篇文章

  • 我们可以添加一个“EmptyView().frame(width:.zero)”,而不是“Settings”中的“AnyView”,因为“AnyView”可能不再有任何没有参数的初始值设定项 (6认同)