Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Created June 11, 2025 04:49
Show Gist options
  • Save brennanMKE/e13630234ec497a3200fb39573dad8f2 to your computer and use it in GitHub Desktop.
Save brennanMKE/e13630234ec497a3200fb39573dad8f2 to your computer and use it in GitHub Desktop.
Using UIWindowScene and UIWindow with SwiftUI to Create a Debugging Window

Using UIWindowScene and UIWindow with SwiftUI to Create a Debugging Window

This tutorial explains the relationship between UIWindowScene, UIWindow, and UIScene, and shows how to create a secondary debug window using SwiftUI inside a UIKit-hosted UIWindow. This is especially useful for developers working on internal tools or debugging overlays for iOS, iPadOS, or visionOS apps.


Overview

Apple's scene-based lifecycle (introduced in iOS 13) allows multiple UI scenes per app. Each scene is an instance of UIScene, and for UIKit-based apps, it takes the form of a UIWindowScene. Each UIWindowScene can own multiple UIWindow instances.

A UIWindow is the visual container for your app's interface. Typically, an app has one UIWindow per scene, but you can create additional windows for overlays or floating utilities.


Relationships

UIApplication
  └── UIScene (abstract)
        └── UIWindowScene (concrete, UIKit-specific)
              └── UIWindow (visual container)
                    └── View Controller or SwiftUI view
  • UIScene: Abstract base class.
  • UIWindowScene: Concrete class for UIKit scenes.
  • UIWindow: Hosts view hierarchies, requires a UIWindowScene.

Use Case: Floating Debug Window

In this tutorial, you'll learn how to attach a SwiftUI-based debug overlay window to the existing scene. This is useful for logging, inspecting state, or interacting with tools during development.


Step 1: Create a SwiftUI Debug View

import SwiftUI

struct DebugOverlayView: View {
    var body: some View {
        VStack {
            Text("Debug Window")
                .font(.headline)
                .foregroundColor(.white)
            Spacer()
            Button("Close Debug Info") {
                // Hook into dismissal or action
            }
            .padding()
        }
        .frame(width: 300, height: 200)
        .background(.black.opacity(0.8))
        .cornerRadius(12)
        .padding()
    }
}

Step 2: Create a UIWindow for Debug UI

import UIKit
import SwiftUI

final class DebugWindowController {
    private var window: UIWindow?

    func show(in scene: UIWindowScene) {
        guard window == nil else { return }

        let window = UIWindow(windowScene: scene)
        window.frame = CGRect(x: 100, y: 100, width: 320, height: 240)
        window.windowLevel = .alert + 1
        window.rootViewController = UIHostingController(rootView: DebugOverlayView())
        window.isHidden = false

        self.window = window
    }

    func hide() {
        window?.isHidden = true
        window = nil
    }
}

Step 3: Integrate with SceneDelegate

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    let debugWindow = DebugWindowController()

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }

        // Main window
        let mainWindow = UIWindow(windowScene: windowScene)
        mainWindow.rootViewController = UIHostingController(rootView: ContentView())
        self.window = mainWindow
        mainWindow.makeKeyAndVisible()

        // Debug window
        debugWindow.show(in: windowScene)
    }
}

Notes

  • Use windowLevel = .alert + 1 to layer on top of other UI.
  • Optional: add drag gestures to move the debug overlay.
  • Restrict to debug builds using #if DEBUG.
  • You can later expand this pattern to use a separate UIScene if you want full multitasking support.

Conclusion

By understanding the hierarchy of UIScene, UIWindowScene, and UIWindow, and combining it with SwiftUI via UIHostingController, you can create powerful development tools and overlays directly within your app. This pattern is especially useful for diagnostics, development panels, or inspector-style UI components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment