Reputation: 2398
At this answer the solution work for Scene plus swiftUI.
However using @main like:
@main
struct MyApp: App {
@StateObject private var model = MyModel()
var body: some Scene {
WindowGroup {
Router {
AppContent()
}.environmentObject(self.model)
}
}
}
I also tried to get the main window by using
var window: NSWindow? {
let window = NSApplication.shared.mainWindow
return window
}
Nevertheless, the mainWindow
always return nil
I need the NSWindow
due to the need of conforming with ASWebAuthenticationPresentationContextProviding
which obligates to return a NSWindow
. Basically, I'm trying to do something like:
LoginView(store: AuthStore(window: window))
Where AuthStore
uses the AuthenticationServices
to perform an authentication.
Upvotes: 16
Views: 7774
Reputation: 120022
You can access the keyWindow
through an @Environment
variable from anywhere like:
@Environment(\.keyWindow) private var keyWindow
By simply extending the EnvironmentValues
it like:
extension EnvironmentValues {
var keyWindow: NSWindow? {
get { self[KeyWindow.self] }
set { self[KeyWindow.self] = newValue }
}
}
struct KeyWindow: EnvironmentKey {
static var defaultValue = NSApplication.shared.keyWindow
}
You need to add a simple logic to keep the keyWindow
always up-to-date like:
@main
struct MyApp: App {
@State private var keyWindow = KeyWindow.defaultValue // 👈 A simple storage
var body: some Scene {
WindowGroup {
ContentView()
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)) { keyWindow = $0.object as? NSWindow } // 👈 Update the storage on change
.environment(\.keyWindow, keyWindow) // 👈 Pass in the updated window
}
}
}
Here is the UIKit version
Upvotes: 2
Reputation: 258443
Basically, I'm trying to do something like:
LoginView(store: AuthStore(window: window))
Here is a demo of possible approach (with some replicated entities)
class AuthStore {
var window: NSWindow
init(window: NSWindow) {
self.window = window
}
}
struct DemoWindowAccessor: View {
@State private var window: NSWindow? // << detected in run-time so optional
var body: some View {
VStack {
if nil != window {
LoginView(store: AuthStore(window: window!)) // << usage
}
}.background(WindowAccessor(window: $window))
}
}
struct WindowAccessor: NSViewRepresentable {
@Binding var window: NSWindow?
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
self.window = view.window // << right after inserted in window
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
struct LoginView: View {
let store: AuthStore
var body: some View {
Text("LoginView with Window: \(store.window)")
}
}
Upvotes: 24
Reputation: 818
Many ways but there is a gotcha:
NSApplication.shared.keyWindow, NSApp.keyWindow,
NSApp.mainWindow,
Sometimes, these can return nil
especially during launch or if the app is inactive.
I think it is because setting these properties may not be instantaneous from the Appkit side of things
The best way is to access all windows:
NSApp.windows.first
This will return all windows but in an unpredictable order.
If you have multiple windows you can do further filtering to find the required window but this method avoids that crazy behaviour where the other methods return nil
.
Upvotes: 3