I see a lot of tutorials that show how to open a SwiftUI View when a NSStatusItem is clicked on. That's not what I want. I need to show a SwiftUI View when I click on a button over SwiftUI View. So far the following is what I have.
import SwiftUI
@main
struct MyStatusApp_App: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
#if os(macOS)
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem!
private var popover: NSPopover?
func applicationDidFinishLaunching(_ notification: Notification) {
hideTitleBar()
NSApp.setActivationPolicy(.accessory)
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem.button {
if let image = NSImage(named: "statusImage") {
button.image = image
}
}
}
#endif
// ContentView //
import SwiftUI
struct ContentView: View {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some View {
VStack {
Button("Click me!") {
let popOver = NSPopover()
popOver.contentViewController = NSHostingController(rootView: NotificationView())
appDelegate.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let statusBarbutton = appDelegate.statusItem.button {
popOver.show(relativeTo: statusBarbutton.bounds, of: statusBarbutton, preferredEdge: .minY)
}
}
}
.frame(width: 200, height: 100)
}
}
If I run the application and click on the button (orange arrow) over ContentView, a guy from NotificationView will appear (green rectangle). That's good. But it appears not below the status item (red arrow). It's positioned at an odd location. It's way below the status item guy. What am I doing wrong? Muchos thankos.
I guess site's add image function is broken. It doesn't show my screenshot.
![]("https://developer.apple.com/forums/content/attachment/7e19bf2e-439d-4ed0-a03c-740b77e94e24" "title=Screenshot.jpg;width=364;height=400")
I've gotten the following lines of code working.
import SwiftUI
@main
struct MyStatusApp_App: App {
#if os(macOS)
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#endif
@StateObject private var statusBarViewModel = StatusBarViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.onDisappear {
NSApplication.shared.terminate(self)
}
.environmentObject(statusBarViewModel)
}
.defaultSize(width: 640, height: 360)
}
}
#if os(macOS)
class AppDelegate: NSObject, NSApplicationDelegate {
let fileManager = FileManager.default
func applicationDidFinishLaunching(_ notification: Notification) {
hideTitleBar()
NSApp.setActivationPolicy(.accessory)
}
func applicationWillFinishLaunching(_ notification: Notification) {
NSWindow.allowsAutomaticWindowTabbing = false
}
}
#endif
// ContentView.swift //
import SwiftUI
struct ContentView: View {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@EnvironmentObject var statusBarViewModel: StatusBarViewModel
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Button("Click me!") {
if let statusItem = statusBarViewModel.statusItem {
if let button = statusItem.button {
if statusBarViewModel.popover == nil {
let popover = NSPopover()
statusBarViewModel.popover = popover
popover.behavior = .transient
popover.animates = false
popover.contentViewController = NSHostingController(rootView: NotificationView())
}
statusBarViewModel.popover?.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
}
}
}
}
.frame(width: 200, height: 100)
.onAppear {
statusBarViewModel.makeStatusMenu()
}
}
}
class StatusBarViewModel: ObservableObject {
@Published var statusItem: NSStatusItem!
@Published var popover: NSPopover?
func makeStatusMenu() {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusItem.button {
if let image = NSImage(named: "statusImage") {
button.image = image
}
}
}
}