Search code examples
iosswifttvos

iOS app how to only mirror on apple tv and not taking up the whole screen?


I have an iOS app, and when it is mirrored to the Apple TV via Airplay, it actually takes over the entire TV screen, as if UIKit is reading the actual dimensions of the TV. However, this is not what I want - I just want to mirror the app to fit the TV screen (like most other apps) and show black areas instead. How can I do this?

Edit: To clarify, I just want to mirror the screen as it is, and not try to show any additional content.

Edit 2:

The only thing I did in App/SceneDelegate is setting up the delegate class programmatically (because I don't want to set it in the plist):

in my app delegate:

  public func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    // Since we manually create the config instead of reading "DefaultConfiguration" from Info.Plist, we should delete the entry in info.plist.
    let config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
    config.delegateClass = Self.self
    config.sceneClass = UIWindowScene.self
    return config
  }

in my scene delegate:

  public func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
    guard let windowScene = (scene as? UIWindowScene) else { return }
    let window = UIWindow(windowScene: windowScene)
    
    window.rootViewController = rootVC()
    window.makeKeyAndVisible()
    window.overrideUserInterfaceStyle = .light
  }

And then my plist's "application scene manifest" has the classes setup removed:

enter image description here


Solution

  • Finally found the issue. It's because willConnectTo will be called again with session.role == .windowExternalDisplayNonInteractive when using airplay. We need to check session.role, and early return in that scenario:

      public func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        // willConnectTo is called again when connect to an external display (airplay), with a different role type.
        // If we don't do anything, iOS will simply mirror the screen.
        guard session.role == .windowApplication else {
          return
        }
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
        
        window.rootViewController = rootVC()
        window.makeKeyAndVisible()
        window.overrideUserInterfaceStyle = .light  
      }