[iOS]-使用SceneDelegate通过系统镜像将APP自定义内容投屏

303 阅读2分钟

由于公司业务布局变化,接到了制作AppleTV项目的任务。在制作的过程中,发现了一款APP可以随时将APP中自定义的内容投屏到电视/Mac/显示器等终端。这块技术对以后的业务布局可能会有帮助,决定研究一下。

最后功能简单实现,但是中间还有很多的细节没弄懂,以后有时间了再研究一下。

iOS13引入了SceneDelegate,使APP业务增加了多场景,iPhone-外接屏(TV/Mac/投影仪),在AppDelegate中新增了代理方法configurationForConnecting,能够监听到外部屏幕接入的状态。

整理一下基本的代码逻辑就是:

  1. application:configurationForConnecting:options 监听到外部进入了屏幕,分析并返回不同场景的配置信息(包括屏幕代理是谁)。
// MARK: UISceneSession Lifecycle

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

        if connectingSceneSession.role == .windowApplication {

            return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)

        } else {

            let config = UISceneConfiguration(name: "Screen Configuration", sessionRole: connectingSceneSession.role)

            config.delegateClass = ScreenDelegate.self

            return config

        }
    }

我一直没有拿到options中的信息,connectingSceneSession中的role来区分是默认场景还是新接入的场景。 这里做好判断之后默认场景不需要额外的配置,新接入的场景需要指定一个config name,并且将新建的代理类提供给config,是为了让新的场景的生命周期等在你指定的类中回调。这里我新建的代理类是 ScreenDelegate 2. scene:willConnectTo:options 在指定的屏幕代理类中实现根视图设置。

class ScreenDelegate: 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 = ScreenViewController()

            window.makeKeyAndVisible()

            self.window = window

        }
    }

    func sceneDidDisconnect(_ scene: UIScene) {

        print("场景断开连接")
    }
}

注意,新建的代理类是继承UIResponder,并且要遵守UIWindowSceneDelegate协议。 ScreenViewController是要显示在外部屏幕上的根视图控制器。

调试:APP运行之后,找到镜像选中显示屏,显示ScreenViewController了内容,完成。

存疑:

  1. 在网上找到了很多不教程,包括说如果在info.plist中添加了配置项就不要再application:configurationForConnecting:options中设置实现了,然后我尝试了不实现代理方法是显示不出来的。
  2. self.window = window 一定要写这句代码,否则也是显示不出来
  3. 我还使用了UIScreen.didConnectNotification监听,通过每次监听获取外界屏幕的状态变化,然后在通过UIApplication.shared.connectedScenes新增的Scene中的screen和监听到的screen做对比之后设置screen的方法,但是在SwiftUI中大概是因为导入了Combine框架,UIApplication.shared.connectedScenes能准确获取到新增的场景,但是在纯Swift中就无法准确的获取到,可能是都放到application:configurationForConnecting:options中来实现了。