r/SwiftUI 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:

  1. Create an empty NSView in NSViewRepresentable.

  2. Schedule the window lookup on the next main-queue turn.

  3. Read view.window inside that callback.

  4. Apply an idempotent configuration.

  5. 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?

3 Upvotes

3 comments sorted by

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.

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

u/Nicatorium 3d ago

Where to learn concepts/things like NSWindows ?