I began to learn Swift four days ago and I met a problem.
@main
struct GmailNotificationApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
@StateObject var authViewModel = AuthenticationViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(authViewModel) // <-----first place
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
private var statusItem: NSStatusItem!
private var popover: NSPopover!
func applicationDidFinishLaunching(_ notification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let statusButton = statusItem.button {
statusButton.image = NSImage(systemSymbolName: "g.circle.fill", accessibilityDescription: "Gmail Notification")
statusButton.action = #selector(togglePopover)
}
self.popover = NSPopover()
self.popover.contentSize = NSSize(width: 210, height: 300)
self.popover.behavior = .transient
self.popover.contentViewController = NSHostingController(rootView: ContentView().environmentObject(AuthenticationViewModel())) // <-----second place
}
@objc func togglePopover(){
if let button = statusItem.button {
if popover.isShown {
self.popover.performClose(nil)
} else {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
My question is: How could I pass StateObject inside AppDelegate?
Also:
I knew little about AppKit, it seems I have two "window", one is displayed just below menu bar and another shows when Google Auth calls back.
I wish the windows in the center of my screen never shows. Now I use
NSApplication.shared.windows.first?.close()
var window: NSWindow!
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 0, height: 0),
styleMask: [.borderless],
backing: .buffered, defer: false)
window.center()
window.makeKeyAndOrderFront(nil)
to work around.
But I think it's very stupid.
So is there a way to never
show the window in the screen center and just show the windows below menu bar?
I'm quite new to Swift/SwiftUI/AppKit and sorry for these dumb questions.
Notice that you don't give SwiftUI an instance of your AppDelegate
. You only give it the class object, AppDelegate.self
, and SwiftUI creates the instance once and keeps it around forever.
I think a better pattern is to create the AuthenticationViewModel
in your AppDelegate
. Then you don't need to use @StateObject
to keep the model alive, because the app delegate will do that. So you can just use @ObservedObject
:
class AppDelegate: NSObject, NSApplicationDelegate {
let authViewModel = AuthenticationViewModel()
// blah blah blah
}
@main
struct MacStudyApp: App {
@NSApplicationDelegateAdaptor private var appDelegate: AppDelegate
@ObservedObject private var authViewModel: AuthenticationViewModel
init() {
authViewModel = _appDelegate.wrappedValue.authViewModel
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(authViewModel)
}
}
}
But in your case, you don't even need to use @ObservedObject
because MacStudyApp
doesn't use any properties of authViewModel
in its body. So you could in fact just do this:
class AppDelegate: NSObject, NSApplicationDelegate {
let authViewModel = AuthenticationViewModel()
// blah blah blah
}
@main
struct MacStudyApp: App {
@NSApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appDelegate.authViewModel)
}
}
}