3

If I compile onto an iOS 12 device (doesn't use UIScene) and AirPlay Mirror to my Apple TV the app is mirrored as expected to the TV.

On an iOS 13 device, it seems to treat it as an external display where it's formatted to fit the screen (but I have no way to control it).

I'd prefer the old functionality of just mirroring it.

How do I accomplish mirroring on iOS 13? I'm digging around in the docs for:

application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration

And in the UISceneConfiguration there's a role property (it has UISceneSession.Role.windowExternalDisplay when I try to AirPlay Mirror) but it doesn't seem to have any value like UISceneSession.Role.windowMirror.

christianselig
  • 306
  • 4
  • 14

4 Answers4

4

I've been playing around with mirroring and external displays and various possibilities exist with just the right combination of code/settings but certain functionality doesn't seem possible.

Under iOS 13 (with an app built with a Base SDK of iOS 13), you can get your app to be mirrored on an external display. But making this work prevents your app from showing different content on an external display. Basically your app only mirrors or it only shows a unique scene for an external display.

If you wish to only have your app be mirrored, then ensure the following:

  1. Remove the application(_:configurationForConnecting:options:) from your App Delegate.
  2. In the Info.plist, make sure there is no entry for the "External Display Session Role" under the "Scene Configuration" section of the "Application Scene Manifest".

If neither of those two things are part of your app then your app will simple mirror to any external screen when you activate Screen Mirroring on the iOS device.

rmaddy
  • 298,130
  • 40
  • 468
  • 517
  • So to be clear there's no way to implement the AppDelegate method AND have AirPlay Mirroring on iOS 13? – christianselig Oct 24 '19 at 00:36
  • 1
    I have not had any success with that. If either the delegate is implemented (no matter what it returns) or if the "External Display Session Role" entry is in the Info.plist, then the external display will get its own unique scene and window. – rmaddy Oct 24 '19 at 00:38
  • @rmaddy, Q1. i want to share my app screen to Other screen like Apple TV (or supported any) can i do it in latest ios and how ? Could you please help me ? Q2. Can it be done with Google chrome cast iOS SDK too ? i need both answer, please. – Jamshed Alam Sep 09 '20 at 03:18
1

I found that with Objective-C implementation, you can achieve the screen mirroring behavior by returning nil in application:configurationForConnectingSceneSession:options:.

- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
    if (connectingSceneSession.role == UIWindowSceneSessionRoleExternalDisplay) {
        return nil;
    }
    UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:@"Main" sessionRole:connectingSceneSession.role];
    configuration.storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    configuration.delegateClass = [SceneDelegate class];
    configuration.sceneClass = [UIWindowScene class];
    return configuration;
}

Be aware that this is not a documented way and may break in the future.

Edited: In Swift, you can achieve this via method swizzling:

@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {

    override init() {
        _ = AppDelegate.performSceneConfigurationSwizzle
        super.init()
    }

    private static let performSceneConfigurationSwizzle: Void = {
        method_exchangeImplementations(
            class_getInstanceMethod(AppDelegate.self, #selector(AppDelegate.application(_:configurationForConnecting:options:)))!,
            class_getInstanceMethod(AppDelegate.self, #selector(AppDelegate.swizzle_application(_:configurationForConnecting:options:)))!
        )
    }()

    @objc func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        fatalError("Should never reach.")
    }

    @objc private func swizzle_application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration? {
        if connectingSceneSession.role == .windowExternalDisplay {
            return nil
        }
        // build scene configuration as usual…
    }
}
Jonny
  • 1,711
  • 14
  • 23
1

Just ran into this issue myself. My solution actually came from within my UIWindowSceneDelegate class.

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        // External displays should not get assigned a window. When a window isn't assigned, the default behavior is mirroring.
        guard session.role != .windowExternalDisplay else { return }
        /* the rest of your setup */
    }

When you don't assign a window, it seems that mirroring becomes the default option. Before that change, my external displays (screen mirroring) were given their own unique UIWindow instance.

I don't see this documented anywhere, and it is not intuitive. Because of this, I'm somewhat fearful that it will break in the future.

Hope it still helps.

bclymer
  • 6,260
  • 2
  • 23
  • 34
0

Instead of implementing the AppDelegate scene configuration method in iOS 13:

@available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    configuration.delegateClass = SceneDelegate.self
    return configuration
}

I instead switched to using the Info.plist variant (and removed the above code) where you effectively specify all the above in your Info.plist instead. (For an up to date version of what's expected in the Info.plist file, simply create a New Project in Xcode and copy the contents from the new Info.plist file for the Application Scene Manifest key).

It now works perfectly and AirPlay Mirror mirrors as expected. I did try changing the role to windowApplication as iOS seemingly does with the Info.plist variant but it still doesn't work.

christianselig
  • 306
  • 4
  • 14