Displaying a SwiftUI View Above a System Alert Using windowLevel


Greetings, traveler!

In UIKit, every window has a window level. That detail is easy to forget, but it can be useful when you need to present content above a specific system layer.

Here, we will walk through a concrete example: how a SwiftUI view can be displayed above a system alert by creating a separate UIWindow and assigning it a higher windowLevel.

This is not a recommendation for everyday UI composition. However, it can be useful for creating components like toasts.

Anyway, understanding these mechanisms can be valuable when dealing with edge cases, debugging tools, or highly constrained flows.

Alert window level

SwiftUI and UIKit already provide clear presentation layers, and system alerts sit at one of the highest window levels available. Although this is an uncommon requirement, you can achieve it by creating a dedicated UIWindow placed above the system alert’s windowLevel.

System alerts use UIWindow.Level.alert, which already sits above most app windows. SwiftUI itself does not provide an API to layer views above it. However, UIKit still allows you to create your own window and raise its level beyond the system alert level:

window.windowLevel = .alert + 1

Because the system alert is rendered in a separate window, this new window will appear above it.

Implementation Overview

The example below demonstrates a small view with a button: tapping it shows a system alert. While the alert is visible, we inject another overlay window that hosts SwiftUI content.

struct SwiftUIView: View {
    @State private var showAlert = false
    @State private var overlay = OverlayWindow()
    
    var body: some View {
        VStack {
            Button("Show System Alert") {
                showAlert = true
            }
        }
        .alert("System Alert", isPresented: $showAlert) {
            Button("OK", role: .cancel) {}
        }
        .onChange(of: showAlert) { _, show in
            if show {
                overlay.show {
                    Button("Overlay ABOVE alert") {
                        overlay.hide()
                    }
                    .buttonStyle(.borderedProminent)
                }
            } else {
                overlay.hide()
            }
        }
        .onDisappear {
            overlay.hide()
        }
    }
}

The key part is the .onChange modifier. When the alert becomes visible, we create an overlay window and pass SwiftUI content into it. When the alert is dismissed—or the view disappears—we hide the overlay.

Overlay Window Manager

@MainActor
final class OverlayWindow {
    private var window: UIWindow?
    
    func show<Content: View>(@ViewBuilder content: () -> Content) {
        guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
        
        let window = UIWindow(windowScene: scene)
        let controller = UIHostingController(rootView: content())
        controller.view.backgroundColor = .clear
        
        window.windowLevel = .alert + 1
        window.backgroundColor = .clear
        window.rootViewController = controller
        
        window.makeKeyAndVisible()
        self.window = window
    }
    
    func hide() {
        window?.isHidden = true
        window = nil
    }
}

By setting window.windowLevel = .alert + 1 you explicitly place this window above system alerts. Any SwiftUI view hosted inside will be visible even while the alert is presented.

2. You must manage the window’s lifetime manually

Since this window is not part of SwiftUI’s view hierarchy:

  • Hide it when the alert disappears.
  • Hide it when the view disappears.
  • Avoid leaving orphaned windows active—this can cause layout issues or blocked touches.

3. Use .clear backgrounds

To make only your overlay content visible:

controller.view.backgroundColor = .clear
window.backgroundColor = .clear

If you forget this, the window may appear as a full-screen sheet above your app.

4. Scene selection

In multi-window environments (iPadOS, macOS via Catalyst), you should pick the correct active UIWindowScene. The simplest approach is to take the first connected scene, though in production you may want a more explicit selection strategy. You can extract the proper window using this aproach.

A Word of Caution

While this technique works, it should be used sparingly. Additional UIWindow instances bypass SwiftUI’s built-in rendering and lifecycle rules. They also affect focus, first responder behavior, accessibility, and gesture routing.

In most applications, presenting content above system alerts is a sign that the UX flow should be reconsidered. But if you do have that edge case, this pattern provides a clean and isolated way to handle it.