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

3
我正在寻找一种使用SwiftUI创建macOS无窗口菜单栏应用的解决方案。
我已经实现了与菜单栏相关的功能,问题在于如何去除主窗口并将应用从dock中移除。
我尝试在Info.plist中将"Application is agent (UIElement)"设置为"YES",但它只隐藏了应用程序,而窗口仍然存在。
我也尝试修改@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)
        }
    }
}
  • ContentView.swift
import SwiftUI

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

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
1个回答

15

仅将 Application is agent (UIElement) 设置为 YES 是不够的。你还需要通过添加以下内容更改你的 AppDelegate

  1. NSPopover
  2. 添加一个 NSStatusItem

这样才能使它发挥作用。

如何创建 NSPopover?

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


完成后,您可以开始制作菜单栏应用程序,通过更改您的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)
            }
        }
    }
    
}

  1. 完成后,您应该/可以将Application is agent(UIElement)设置为YES

最后一步

此部分将被分为两个小节,即4.14.2

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

前往您的Main.storyboard并删除Window Controller scene 如果您有Main.storyboard。这应该可以摆脱弹出的NSWindow

enter image description here (图片来源)

4.2 - SwiftUI生命周期

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

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

欲了解更多信息,请参考文章。


我已经创建了Popover并且它运作良好,但主窗口仍然显示。 - IvanEFan
抱歉,我忘记了两个步骤。答案已经被编辑过了,请检查第三步和第四步。 - Visal Rajapakse
我使用SwiftUI,所以我没有Main.storyboard :( - IvanEFan
找到了解决方法,请查看我的代码第二部分。 - Visal Rajapakse
6
在“设置”中,我们可以添加一个EmptyView().frame(width:.zero)来替代AnyView,因为AnyView可能不再有没有参数的初始化程序。 - gtxtreme
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接