使用 SwiftUI 创建一个 macOS 无窗口菜单栏应用程序
Creating a macos windowless menu bar application with SwiftUI
我正在寻找使用 SwiftUI 创建 macos window无菜单栏应用程序的解决方案。
我已经实现了与菜单栏相关的功能,问题是删除主要 window 并从停靠栏中删除应用程序。
我曾尝试在 Info.plist
中将 Application is agent (UIElement)
设置为 YES
,但它仅在 window 仍然存在时从扩展坞中隐藏了应用程序。
我也试过修改@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()
}
}
仅将 Application is agent (UIElement)
设置为 YES
是不够的。您还必须通过添加以下内容来更改 AppDelegate
,
- 一个
NSPopover
- 添加一个
NSStatusItem
进入你的 AppDelegate
以便工作
如何制作 NSPopover?
- 转到您的应用委托。 (如果你没有
AppDelegate
。创建一个 AppDelegate
class 并将其委托到你的应用程序的起点,该起点将用 @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)
}
}
}
}
- 这样做之后,您 should/can 将
Application is agent(UIElement)
设置为 YES
最后一步
本节将分为2个部分,即4.1
和4.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()
}
}
}
有关详细信息,请参阅 this 文章
我正在寻找使用 SwiftUI 创建 macos window无菜单栏应用程序的解决方案。
我已经实现了与菜单栏相关的功能,问题是删除主要 window 并从停靠栏中删除应用程序。
我曾尝试在 Info.plist
中将 Application is agent (UIElement)
设置为 YES
,但它仅在 window 仍然存在时从扩展坞中隐藏了应用程序。
我也试过修改@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()
}
}
仅将 Application is agent (UIElement)
设置为 YES
是不够的。您还必须通过添加以下内容来更改 AppDelegate
,
- 一个
NSPopover
- 添加一个
NSStatusItem
进入你的 AppDelegate
以便工作
如何制作 NSPopover?
- 转到您的应用委托。 (如果你没有
AppDelegate
。创建一个AppDelegate
class 并将其委托到你的应用程序的起点,该起点将用@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)
}
}
}
}
- 这样做之后,您 should/can 将
Application is agent(UIElement)
设置为YES
最后一步
本节将分为2个部分,即4.1
和4.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()
}
}
}
有关详细信息,请参阅 this 文章