I'm confused about implementing external monitor support via Airplay with SwiftUI.
In SceneDelegate.swift I'm using UIScreen.didConnectNotification
observer and it actually detects a new screen being attached but I'm unable to assign a custom UIScene to the screen.
I found a few good examples using Swift with iOS12 and lower, but none of them work in SwiftUI, since the whole paradigm has been changed to use UIScene instead of UIScreen. Here's the list:
https://www.swiftjectivec.com/supporting-external-displays/
Apple even spoke about it last year
Perhaps something changed and now there is a new way to do this properly.
Moreover, setting UIWindow.screen = screen
has been deprecated in iOS13.
Has anyone already tried implementing an external screen support with SwiftUI. Any help is much appreciated.
I modified the example from the Big Nerd Ranch blog to work as follows.
Remove Main Storyboard: I removed the main storyboard from a new project. Under deployment info, I set Main interface to an empty string.
Editing plist: Define your two scenes (Default and External) and their Scene Delegates in the Application Scene Manifest section of your plist.
<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)
}
}
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
}
Make a scene delegate for your external screen. I made a new Swift file ExtSceneDelegate.swift that contained the same text as SceneDelegate.swift, changing the name of the class from SceneDelegate to ExtSceneDelegate.
Modify application(_:configurationForConnecting:options:) in AppDelegate. Others have suggested that everything will be fine if you just comment this out. For debugging, I found it helpful to change it to:
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)")
}
}
For me, the key reference for figuring all of this out was https://onmyway133.com/posts/how-to-use-external-display-in-ios/.