Greetings, traveler!
In most SwiftUI-only projects, direct interaction with UIWindow is no longer a daily necessity. SwiftUI abstracts presentation, layout, environment, and view hierarchy in ways that rarely require developers to reach back into UIKit.
However, as soon as you introduce multiple scenes, custom overlays, advanced gesture handling, window-level presentation, or programmatic window manipulation (e.g., adjusting windowLevel for alerts), you may find yourself needing access to the underlying UIWindow.
SwiftUI does not provide a native API for this. Fortunately, UIKit still exposes the window hierarchy, and with a minimal bridging layer we can extract the window at the correct moment in the view’s lifecycle.
This article walks through one reusable pattern for retrieving a UIWindow from SwiftUI using UIViewRepresentable.
A Reusable Window Extractor for SwiftUI
The snippet below demonstrates an elegant way to observe when a SwiftUI view moves into a window and retrieve that window reference.
extension View {
func onChangeWindow(_ perform: @escaping (UIWindow?) -> Void) -> some View {
background {
WindowExtractor(onExtract: perform)
}
}
}This allows any SwiftUI view to attach a handler that triggers whenever the underlying window changes.
Usage:
struct ContentView: View {
var body: some View {
Text("Hello World!")
.onChangeWindow { window in
// do somehing with the window
}
}
}The WindowExtractor
The core implementation is a UIViewRepresentable wrapper containing an invisible UIView subclass that listens for didMoveToWindow().
struct WindowExtractor: UIViewRepresentable {
let onExtract: (UIWindow?) -> Void
@MainActor
final class ViewWithWindow: UIView {
var onMoveToWindow: ((UIWindow?) -> Void)
private weak var lastWindow: UIWindow?
init(onMoveToWindow: (@escaping (UIWindow?) -> Void)) {
self.onMoveToWindow = onMoveToWindow
super.init(frame: .null)
backgroundColor = .clear
isUserInteractionEnabled = false
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMoveToWindow() {
super.didMoveToWindow()
guard window !== lastWindow else { return }
lastWindow = window
onMoveToWindow(window)
}
}
func makeUIView(context: Context) -> ViewWithWindow {
ViewWithWindow(onMoveToWindow: onExtract)
}
func updateUIView(_ uiView: ViewWithWindow, context: Context) {
uiView.onMoveToWindow = onExtract
}
}Key Implementation Details
didMoveToWindow()is the earliest reliable UIKit lifecycle call where the view is attached to a window.- A weak
lastWindowreference ensures the handler runs only when the window actually changes. - The view is invisible and does not participate in hit-testing.
- Because the
UIViewlives inside SwiftUI using.background(), it automatically follows view hierarchy updates.
Multi-Scene Considerations
In a multi-scene environment (e.g., an iPad app or when enabling multiple windows on macOS/iOS), each scene maintains its own window hierarchy. This extractor obtainis the exact window that SwiftUI attached the view to.
This is particularly useful when:
- Presenting views differently depending on the scene;
- Injecting UI that must stay inside the correct window;
- Managing separate overlay systems per scene;
- Coordinating window-specific services.
Because the window is delivered reactively, the API is safe even when scenes are created or destroyed dynamically.
Conclusion
SwiftUI continues to evolve, yet certain tasks still require leaning on UIKit’s primitives—especially when dealing with multi-scene applications. By embedding a minimal UIView inside SwiftUI and listening for didMoveToWindow(), developers can reliably retrieve the correct UIWindow at runtime.
This approach is lightweight, scene-aware, production-ready, and easy to integrate into any project.
If your architecture requires custom window-level interaction, custom overlays, or scene-specific behavior, this pattern provides a safe and flexible foundation.
