Search code examples
iosios13swift5airplay

iOS13 external screen mirroring, getting error: setScreen should not be called


I am trying to present a custom view controller to external connected screen in iOS 13. I followed this tutorial:

http://www.spazstik-software.com/blog/article/how-to-display-custom-content-on-a-external-screen-from-a-ios-device

I have following code:

/// A private method used to setup a external screen with a window
/// loaded with a ExternalScreenViewController
///
/// - parameter screen: A UIScreen object to connect the
/// ExternalScreenViewController too
private func setupExternalScreen(screen: UIScreen) {
  guard externalWindow == nil,
    let vc = self.storyboard?.instantiateViewControllerWithIdentifier("ExternalScreen") as? ExternalScreenViewController else {
      return
  }

  externalWindow = UIWindow(frame: screen.bounds)
  externalWindow!.rootViewController = vc
  externalWindow!.screen = screen
  externalWindow!.hidden = false
}

In the line externalWindow!.screen = screen I am getting an error in the Xcode 11.4.1 console:

[Assert] Error in UIKit client: -[UIWindow setScreen:] should not be called if the client adopts UIScene lifecycle. Call -[UIWindow setWindowScene:] instead.

The custom view controller does not show up in the external display.

What should I change? I want to handle both iOS 13.x and prior devices.


Solution

  • In iOS 13 devices, instead of setting screen, you should set windowScene on the externalWindow object. Before that, you should find out matching windowScene from UIApplication's connected scenes. The revised method is as follows:

    private func setupExternalScreen(screen: UIScreen, shouldRecurse: Bool = true) {
        // For iOS13 find matching UIWindowScene
        var matchingWindowScene: UIWindowScene? = nil
        if #available(iOS 13.0, *) {
            let scenes = UIApplication.shared.connectedScenes
            for aScene in scenes {
                if let windowScene = aScene as? UIWindowScene {
                    // Look for UIWindowScene that has matching screen
                    if (windowScene.screen == screen) {
                        matchingWindowScene = windowScene
                        break
                    }
                }
            }
            if matchingWindowScene == nil {
                // UIWindowScene has not been created by iOS rendered yet
                // Lets recall self after delay of two seconds
                if true == shouldRecurse {
                    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
                        self.setupExternalScreen(screen:screen, shouldRecurse: false)
                    }
                }
                // Dont proceed furthure in iOS13
                return
            }
        }
    
        guard externalWindow == nil, let vc = self.storyboard?.instantiateViewControllerWithIdentifier("ExternalScreen") as? ExternalScreenViewController else {
          return
        }
    
        externalWindow = UIWindow(frame: screen.bounds)
        externalWindow!.rootViewController = vc
        if #available(iOS 13.0, *) {
            // Set windowScene here, no need to set screen
            externalWindow!.windowScene = matchingWindowScene
        } else {
            // Set screen the traditional way
            externalWindow!.screen = screen
        }
        externalWindow!.isHidden = false
    
    }
    

    Note: we should set isHidden as hidden is deprecated.