iOS中使用UIScenes 13、AirPlay如何镜像屏幕(好像默认是外接显示器)

Using UIScenes in iOS 13, how do I AirPlay Mirror a screen (seems to default to external display)

如果我编译到 iOS 12 设备(不使用 UIScene)和 AirPlay Mirror 到我的 Apple TV,该应用程序将按预期镜像到电视。

在 iOS 13 设备上,它似乎把它当作一个外部显示器,在那里它被格式化以适合屏幕(但我无法控制它)。

我更喜欢只镜像它的旧功能。

如何在 iOS 13 上完成镜像?我在文档中挖掘:

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

并且在 UISceneConfiguration 中有一个 role 属性(当我尝试使用 AirPlay Mirror 时它有 UISceneSession.Role.windowExternalDisplay)但它似乎没有任何价值像 UISceneSession.Role.windowMirror.

而不是实现iOS13中的AppDelegate场景配置方法:

@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
}

我改为使用 Info.plist 变体(并删除了上面的代码),您可以在 Info.plist 中有效地指定以上所有内容。 (对于 Info.plist 文件中预期内容的最新版本,只需在 Xcode 中创建一个新项目,然后从新的 Info.plist 文件中为 Application Scene Manifest 复制内容钥匙)。

它现在可以完美运行,并且 AirPlay Mirror 可以按预期进行镜像。我确实尝试将 role 更改为 windowApplication,因为 iOS 似乎对 Info.plist 变体进行了更改,但它仍然不起作用。

我一直在尝试镜像和外接显示器,只要 code/settings 的正确组合就存在各种可能性,但某些功能似乎无法实现。

在 iOS 13 下(使用 iOS 13 的 Base SDK 构建的应用程序),您可以将您的应用程序镜像到外部显示器上。但是,使这项工作能够阻止您的应用程序在外部显示器上显示不同的内容。基本上您的应用仅镜像或仅显示外部显示器的独特场景。

如果您希望只镜像您的应用,请确保满足以下条件:

  1. 从您的 App Delegate 中删除 application(_:configurationForConnecting:options:)
  2. 在 Info.plist 中,确保 "Application Scene Manifest" 的 "Scene Configuration" 部分下没有 "External Display Session Role" 条目。

如果这两个东西都不是您的应用程序的一部分,那么当您在 iOS 设备上激活屏幕镜像时,您的应用程序将简单地镜像到任何外部屏幕。

我发现通过 Objective-C 实现,您可以通过在 application:configurationForConnectingSceneSession:options: 中返回 nil 来实现屏幕镜像行为。

- (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;
}

请注意,这不是记录在案的方法,将来可能会中断。

已编辑: 在 Swift 中,您可以通过 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…
    }
}

我自己 运行 进入这个问题。我的解决方案实际上来自我的 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 */
    }

当您不分配 window 时,镜像似乎成为默认选项。在此更改之前,我的外部显示器(屏幕镜像)被赋予了自己独特的 UIWindow 实例。

我没有在任何地方看到这个记录,而且它不直观。因此,我有点担心它以后会坏掉。

希望它仍然有帮助。