Present Modal View Error when iPhone enable screen mirroring

In one of our SwiftUI projects, we intensively use UIViewController to present a SwiftUI View modally, and it works perfectly under normal circumstances.

However, we have observed that when screen mirroring is enabled on the iPhone, the @Environment viewControllerHolder becomes nil, preventing the proper presentation of another view. Xcode (14.5) does not flag any issues with the code, and our project is set to build for iOS 17.5.

Without changing too many codebase, is there a way to fix this unexpected issue?

import SwiftUI
import UIKit

struct ContentView: View {
    @Environment(\.viewController) private var viewControllerHolder: UIViewController?
    @State var presentSecondPage = false
    var body: some View {
        VStack(spacing: 40) {
            Text("This is First Page")
            Button("Present Second Page") {
                presentSecondPage = true
            }
        }
        .onChange(of: presentSecondPage) {
            if presentSecondPage {
                viewControllerHolder?.present(style: .fullScreen) {
                    SecondPage(presentSecondPage: $presentSecondPage)
                }
            }
        }
    }
}

struct SecondPage: View {
    @Environment(\.viewController) private var viewControllerHolder: UIViewController?
    @Binding var presentSecondPage: Bool
    var body: some View {
        VStack(spacing: 40) {
            Text("This is Second Page")
            Button("Back to First Page") {
                presentSecondPage = false
                viewControllerHolder?.dismiss(animated: true)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.gray)
    }
}


struct ViewControllerHolder {
    weak var value: UIViewController?
}

struct ViewControllerKey: EnvironmentKey {
    static var defaultValue: ViewControllerHolder {
        let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
        let rootVC = windowScene?.windows.first(where: { $0.isKeyWindow })?.rootViewController
        return ViewControllerHolder(value: rootVC)
    }
}

extension EnvironmentValues {
    var viewController: UIViewController? {
        get { return self[ViewControllerKey.self].value }
        set { self[ViewControllerKey.self].value = newValue }
    }
}

extension UIViewController {
    func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) {
        let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
        toPresent.modalPresentationStyle = style
        toPresent.rootView = AnyView(
            builder()
                .environment(\.viewController, toPresent)
        )
        NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "dismissModal"), object: nil, queue: nil) { [weak toPresent] _ in
            toPresent?.dismiss(animated: true, completion: nil)
        }
        self.present(toPresent, animated: true, completion: nil)
    }
}

Normal Circumstances

Screen Mirroring

Answered by DTS Engineer in 799802022

The concept of an application wide key window is flawed in the presence of scenes because you can have multiple key window (and the “application” level one may not be the one you are currently looking at).

let rootVC = windowScene?.windows.first(where: { $0.isKeyWindow })?.rootViewController

Consider that there may be 2 scenes from an app on screen at the same time. In this case, it would be extremely arbitrary. It basically means you want to get the rootViewController of some random connected scene.

The only way to do this is to keep reference of the rootViewController defined when you set the window of your Scene


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = /* Some View controller */
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

The concept of an application wide key window is flawed in the presence of scenes because you can have multiple key window (and the “application” level one may not be the one you are currently looking at).

let rootVC = windowScene?.windows.first(where: { $0.isKeyWindow })?.rootViewController

Consider that there may be 2 scenes from an app on screen at the same time. In this case, it would be extremely arbitrary. It basically means you want to get the rootViewController of some random connected scene.

The only way to do this is to keep reference of the rootViewController defined when you set the window of your Scene


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = /* Some View controller */
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
Present Modal View Error when iPhone enable screen mirroring
 
 
Q