【问题标题】:Implementing external monitor support in SwiftUI在 SwiftUI 中实现外接显示器支持
【发布时间】:2020-01-21 20:01:20
【问题描述】:

我对通过 Airplay 和 SwiftUI 实现外接显示器支持感到困惑。

在 SceneDelegate.swift 中,我使用 UIScreen.didConnectNotification 观察者,它实际上检测到附加了一个新屏幕,但我无法将自定义 UIScene 分配给屏幕。

我发现了一些在 iOS12 及更低版本中使用 Swift 的好例子,但它们都不能在 SwiftUI 中工作,因为整个范例已更改为使用 UIScene 而不是 UIScreen。这是列表:

https://www.bignerdranch.com/blog/adding-external-display-support-to-your-ios-app-is-ridiculously-easy/

https://developer.apple.com/documentation/uikit/windows_and_screens/displaying_content_on_a_connected_screen

https://www.swiftjectivec.com/supporting-external-displays/

苹果连spoke about it last year

也许有些事情发生了变化,现在有一种新方法可以正确地做到这一点。 此外,设置 UIWindow.screen = screen 在 iOS13 中已被弃用。

有没有人尝试过使用 SwiftUI 实现外部屏幕支持。非常感谢任何帮助。

【问题讨论】:

    标签: ios swift swiftui ios13


    【解决方案1】:

    我修改了 Big Nerd Ranch 博客中的示例,使其工作如下。

    1. 删除主故事板:我从一个新项目中删除了主故事板。在部署信息下,我将主界面设置为空字符串。

    2. 编辑 plist:在 plist 的 Application Scene Manifest 部分定义两个场景(默认和外部)及其场景委托。

        <key>UIApplicationSceneManifest</key>
        <dict>
            <key>UIApplicationSupportsMultipleScenes</key>
            <true/>
            <key>UISceneConfigurations</key>
            <dict>
                <key>UIWindowSceneSessionRoleApplication</key>
                <array>
                    <dict>
                        <key>UISceneConfigurationName</key>
                        <string>Default Configuration</string>
                        <key>UISceneDelegateClassName</key>
                        <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                    </dict>
                </array>
                <key>UIWindowSceneSessionRoleExternalDisplay</key>
                <array>
                    <dict>
                        <key>UISceneDelegateClassName</key>
                        <string>$(PRODUCT_MODULE_NAME).ExtSceneDelegate</string>
                        <key>UISceneConfigurationName</key>
                        <string>External Configuration</string>
                    </dict>
                </array>
            </dict>
        </dict>
    
    1. 编辑视图控制器以显示一个简单的字符串:
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .blue
            view.addSubview(screenLabel)
        }
    
        var screenLabel: UILabel = {
            let label = UILabel()
            label.textColor = UIColor.white
            label.font = UIFont(name: "Helvetica-Bold", size: 22)
            return label
        }()
    
        override func viewDidLayoutSubviews() {
            /* Set the frame when the layout is changed */
            screenLabel.frame = CGRect(x: 0,
                                    y: 0,
                                    width: view.frame.width - 30,
                                    height: 24)
        }
    }
    
    1. 修改 SceneDelegate 中的场景(_:willConnectTo:options:) 以在主 (iPad) 窗口中显示信息。
        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 }
    
            window = UIWindow(frame: windowScene.coordinateSpace.bounds)
            window?.windowScene = windowScene
            let vc = ViewController()
            vc.loadViewIfNeeded()
            vc.screenLabel.text = String(describing: window)
            window?.rootViewController = vc
            window?.makeKeyAndVisible()
            window?.isHidden = false
        }
    
    1. 为您的外部屏幕制作场景委托。我创建了一个新的 Swift 文件 ExtSceneDelegate.swift,其中包含与 SceneDelegate.swift 相同的文本,将类的名称从 SceneDelegate 更改为 ExtSceneDelegate。

    2. 在 AppDelegate 中修改应用程序(_:configurationForConnecting:options:)。其他人建议,如果您对此进行评论,一切都会好起来的。对于调试,我发现将其更改为:

        func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    
            // This is not necessary; however, I found it useful for debugging
            switch connectingSceneSession.role.rawValue {
                case "UIWindowSceneSessionRoleApplication":
                    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
                case "UIWindowSceneSessionRoleExternalDisplay":
                    return UISceneConfiguration(name: "External Configuration", sessionRole: connectingSceneSession.role)
                default:
                    fatalError("Unknown Configuration \(connectingSceneSession.role.rawValue)")
                }
        }
    
    1. 在 iOS 上构建和运行应用程序。您应该会看到一个丑陋的蓝屏,其中包含有关编写的 UIWindow 的信息。然后我使用屏幕镜像连接到 Apple TV。您应该会在外部屏幕上看到一个同样难看的蓝屏,其中包含不同的 UIWindow 信息。

    对我来说,弄清楚这一切的关键参考是https://onmyway133.github.io/blog/How-to-use-external-display-in-iOS/

    【讨论】:

      【解决方案2】:

      不了解 SwiftUI(我是死硬的 ObjectiveC),但在 iOS13 中,您在应用程序委托中处理 application:configurationForConnectingSceneSession:options 然后查找 [connectingSceneSession.role isEqualToString:UIWindowSceneSessionRoleExternalDisplay]

      在那里,您创建一个新的 UISceneConfiguration 并将其 delegateClass 设置为您选择的 UIWindowSceneDelegate 派生类(您要在该外部显示器上管理内容的类。)

      我认为您还可以在 info.plist 文件中将 UIWindowSceneSessionRoleExternalDisplay 与您的 UIWindowSceneDelegate 相关联(但我更喜欢对其进行编码!)

      【讨论】:

        【解决方案3】:

        我在SceneDelegate 中尝试了同样的事情,但后来我意识到UISceneSession 是在UIAppDelegate.application(_:configurationForConnecting:options:) 中定义的,它在外部屏幕连接时调用,就像UIScreen.didConnectNotification 一样。所以我在现有方法中添加了以下代码:

        func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
            self.handleSessionConnect(sceneSession: connectingSceneSession, options: options)
            return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
        }
        
        func handleSessionConnect(sceneSession: UISceneSession, options: UIScene.ConnectionOptions) {
            let scene = UIWindowScene(session: sceneSession, connectionOptions: options)
            let win = UIWindow(frame: scene.screen.bounds)
            win.rootViewController = UIHostingController(rootView: SecondView())
            win.windowScene = scene
            win.isHidden = false
            managedWindows.append(win)
          }
        

        第二个屏幕连接正确。我唯一不确定的是application(_:didDiscardSceneSessions:) 似乎没有被调用,所以我不确定如何最好地管理断开连接的窗口。

        ** 后续编辑**

        我意识到我可以使用原来的UIScreen.didDisconnectNotification 来监听断开连接。

        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            NotificationCenter.default.addObserver(forName: UIScreen.didDisconnectNotification, object: nil, queue: nil) { (notification) in
              if let screen = notification.object as? UIScreen {
                self.handleScreenDisconnect(screen)
              }
            }
            return true
          }
        
        func handleScreenDisconnect(_ screen: UIScreen) {
            for window in managedWindows {
              if window.screen == screen {
                if let index = managedWindows.firstIndex(of: window) {
                  managedWindows.remove(at: index)
                }
              }
            }
          }
        

        但由于没有调用实际的场景会话断开方法,我不确定这是否不正确或不必要。

        【讨论】:

          猜你喜欢
          • 2016-08-19
          • 2021-06-29
          • 1970-01-01
          • 2021-01-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多