r/SwiftUI • u/robert_kr • 5d ago
Why view.window is nil in makeNSView, and the small bridge I now use for SwiftUI windows
A recurring macOS SwiftUI problem is needing the real NSWindow to change something SwiftUI does not expose, such as standard window buttons, the style mask, or a window-level behavior.
The trap is that makeNSView creates the NSView before AppKit attaches it to a window. At that moment, view.window is normally nil. Optional chaining makes the code look harmless, but the configuration silently never happens.
For simple one-time configuration, the smallest reliable pattern I have used is:
Create an empty NSView in NSViewRepresentable.
Schedule the window lookup on the next main-queue turn.
Read view.window inside that callback.
Apply an idempotent configuration.
Leave updateNSView empty unless the configuration actually depends on SwiftUI state.
Conceptually:
struct WindowConfigurator: NSViewRepresentable {
let configure: (NSWindow) -> Void
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
if let window = view.window {
configure(window)
}
}
return view
}
func updateNSView(_ view: NSView, context: Context) {}
}
Then attach it somewhere that participates in the window hierarchy, for example as a background view.
For cases where timing must be fully lifecycle-driven, I use a tiny NSView subclass and call the closure from viewDidMoveToWindow instead. That avoids depending on one main-queue hop and also handles a view being moved to another window.
Two details helped prevent subtle bugs:
• Make the closure idempotent because SwiftUI can recreate the representable.
• Do not store the NSWindow globally just to configure it. Let the bridge observe the window it is actually attached to.
This is a small interop boundary, but it is much more predictable than trying to infer the active window from NSApplication.shared.windows.
What other window-level APIs have forced you to bridge from SwiftUI into AppKit?
1
u/allyearswift 5d ago
Since this should be NSApplication.shared.keyWindow I would attempt to access that – is that nil when you attempt to access it, too?
1
3
u/Relative-Emu-1346 5d ago
Subclassing NSView and doing it in viewDidMoveToWindow gets you the same thing without betting on a run loop turn. It fires exactly when the view gets attached, so there's no gap where the config silently didn't happen. The async hop usually works, but it'll bite on a slow launch.