12

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

Update:

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.

2 Answers2

18

Basically, I'm trying to do something like:

LoginView(store: AuthStore(window: window))

Here is a demo of possible approach (with some replicated entities)

demo

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)")
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • I asked pretty much the same question (https://stackoverflow.com/a/65401530/792406) before I found your very useful example, which has worked really well, so thank you for posting. – hillmark Dec 21 '20 at 23:58
  • 2
    Out of curiosity, is this approach the only way of accessing the NSWindow instance using the new SwiftUI life cycle? I needed to change the appearance and behaviour of the window in my scenario. – hillmark Dec 22 '20 at 00:01
2

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.

codewario
  • 19,553
  • 20
  • 90
  • 159
arthas
  • 680
  • 1
  • 8
  • 16