在 SwiftUI 中实现外部监视器支持
Implementing external monitor support in SwiftUI
我对使用 SwiftUI 通过 Airplay 实现外部显示器支持感到困惑。
在 SceneDelegate.swift 中,我正在使用 UIScreen.didConnectNotification
观察器,它实际上检测到一个正在连接的新屏幕,但我无法将自定义 UI 场景分配给屏幕。
我发现了一些使用 Swift 和 iOS12 及更低的好例子,但其中 none 在 SwiftUI 中工作,因为整个范例已更改为使用 UIScene 而不是 UIScreen。这是列表:
https://www.swiftjectivec.com/supporting-external-displays/
也许有些事情发生了变化,现在有一种新的方法可以正确地做到这一点。
此外,设置 UIWindow.screen = screen
已在 iOS13.
中弃用
有没有人已经尝试过使用 SwiftUI 实现外部屏幕支持。非常感谢任何帮助。
不知道 SwiftUI(我是顽固的 ObjectiveC),但在 iOS13 中,您在应用程序委托中处理 application:configurationForConnectingSceneSession:options,然后查找 [connectingSceneSession.role isEqualToString:UIWindowSceneSessionRoleExternalDisplay]
在那里您创建一个新的 UISceneConfiguration 并将其 delegateClass 设置为您选择的派生 class 的 UIWindowSceneDelegate(您想要管理该外部显示器上的内容的那个。)
我认为您还可以将 UIWindowSceneSessionRoleExternalDisplay 与 info.plist 文件中的 UIWindowSceneDelegate 相关联(但我更喜欢编码!)
我在我的 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:)
似乎没有被调用,所以我不确定如何最好地管理 windows 当它们断开连接时。
** 后续编辑**
我意识到我可以使用原来的 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)
}
}
}
}
但由于没有调用实际场景会话断开方法,我不确定这是不正确还是不必要的。
我修改了 Big Nerd Ranch 博客中的示例,使其工作如下。
删除主故事板:我从一个新项目中删除了主故事板。在部署信息下,我将 Main interface 设置为空字符串。
编辑 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>
- 编辑视图控制器以显示一个简单的字符串:
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)
}
}
- 修改SceneDelegate中的scene(_:willConnectTo:options:),在main(iPad)中显示信息window。
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
}
为您的外部屏幕创建一个场景代理。我创建了一个新的 Swift 文件 ExtSceneDelegate.swift,其中包含与 SceneDelegate.swift 相同的文本,将 class 的名称从 SceneDelegate 更改为 ExtSceneDelegate。
修改AppDelegate中的application(_: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)")
}
}
- 在 iOS 上构建并 运行 应用程序。您应该看到一个丑陋的蓝屏,其中包含有关 UIWindow 的信息。然后我使用屏幕镜像连接到 Apple TV。您应该会在外屏上看到一个同样难看的蓝屏,但 UIWindow 信息不同。
对我来说,解决所有这些问题的关键参考是 https://onmyway133.github.io/blog/How-to-use-external-display-in-iOS/。
我对使用 SwiftUI 通过 Airplay 实现外部显示器支持感到困惑。
在 SceneDelegate.swift 中,我正在使用 UIScreen.didConnectNotification
观察器,它实际上检测到一个正在连接的新屏幕,但我无法将自定义 UI 场景分配给屏幕。
我发现了一些使用 Swift 和 iOS12 及更低的好例子,但其中 none 在 SwiftUI 中工作,因为整个范例已更改为使用 UIScene 而不是 UIScreen。这是列表:
https://www.swiftjectivec.com/supporting-external-displays/
也许有些事情发生了变化,现在有一种新的方法可以正确地做到这一点。
此外,设置 UIWindow.screen = screen
已在 iOS13.
有没有人已经尝试过使用 SwiftUI 实现外部屏幕支持。非常感谢任何帮助。
不知道 SwiftUI(我是顽固的 ObjectiveC),但在 iOS13 中,您在应用程序委托中处理 application:configurationForConnectingSceneSession:options,然后查找 [connectingSceneSession.role isEqualToString:UIWindowSceneSessionRoleExternalDisplay]
在那里您创建一个新的 UISceneConfiguration 并将其 delegateClass 设置为您选择的派生 class 的 UIWindowSceneDelegate(您想要管理该外部显示器上的内容的那个。)
我认为您还可以将 UIWindowSceneSessionRoleExternalDisplay 与 info.plist 文件中的 UIWindowSceneDelegate 相关联(但我更喜欢编码!)
我在我的 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:)
似乎没有被调用,所以我不确定如何最好地管理 windows 当它们断开连接时。
** 后续编辑**
我意识到我可以使用原来的 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)
}
}
}
}
但由于没有调用实际场景会话断开方法,我不确定这是不正确还是不必要的。
我修改了 Big Nerd Ranch 博客中的示例,使其工作如下。
删除主故事板:我从一个新项目中删除了主故事板。在部署信息下,我将 Main interface 设置为空字符串。
编辑 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>
- 编辑视图控制器以显示一个简单的字符串:
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)
}
}
- 修改SceneDelegate中的scene(_:willConnectTo:options:),在main(iPad)中显示信息window。
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
}
为您的外部屏幕创建一个场景代理。我创建了一个新的 Swift 文件 ExtSceneDelegate.swift,其中包含与 SceneDelegate.swift 相同的文本,将 class 的名称从 SceneDelegate 更改为 ExtSceneDelegate。
修改AppDelegate中的application(_: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)")
}
}
- 在 iOS 上构建并 运行 应用程序。您应该看到一个丑陋的蓝屏,其中包含有关 UIWindow 的信息。然后我使用屏幕镜像连接到 Apple TV。您应该会在外屏上看到一个同样难看的蓝屏,但 UIWindow 信息不同。
对我来说,解决所有这些问题的关键参考是 https://onmyway133.github.io/blog/How-to-use-external-display-in-iOS/。