はじめに
iPhone/iPadとApple Lightning - Digital AVアダプタを用いていることでHDMIモニター(TV)に映像を出力することができます。
通常はミラーリングとなりますが、本方法を用いると液晶(OLED)とHDMIで別の表示することができます。
例えば、iPhoneの液晶(OLED)を操作画面として、HDMIを表示画面にするなどできます。
Xcode11で外部ディスプレイに表示する方法を試行錯誤したので投稿します。
まだわかっていないところがあり、コメントいただければと思います。→解決しました。
実装
いくつかのサイトを参考に実装しました。
最後に参考サイトのリンクしますので確認してみてください。
また、サンプルプロジェクトをgithubに公開していますので参考にしてください。
[2019.10.23追記]
iOS13からのUISceneで外部ディスプレイに出力できる方法を@ura14hさんのコメントをいただいてできるようになりました。
iOS12までとiOS13以降と2つにわけました。
iOS13以降の方法
UISceneを利用し接続切断通知を受け取る方法を記載します。
外部ディスプレイの接続切断通知を受け取れるように設定
接続切断通知を受け取れるようにします。
private func setupNotification() {
let center = NotificationCenter.default
center.addObserver(self, selector: #selector(externalScreenWillConnect(notification:)), name: UIScene.willConnectNotification, object: nil)
center.addObserver(self, selector: #selector(externalScreenDidDisconnect(notification:)), name: UIScene.didDisconnectNotification, object: nil)
}
接続処理を実装
Storyboardで作成した画面にStoryboardIDを設定しておき接続時に画面を生成します。
必要に応じて外部から呼び出せるようにインスタンスを保持しておくと便利だと思います。
/// 外部ディスプレイを接続されたときに処理する
/// - Parameter notification:接続されたUIWindowSceneインスタンス
@objc func externalScreenWillConnect(notification: NSNotification) {
guard let windowScene = notification.object as? UIWindowScene else { return }
guard externalWindow == nil else { return }
guard let viewController = self.storyboard?.instantiateViewController(withIdentifier: "ExternalDisplayViewController") as? ExternalDisplayViewController else { return }
externalWindow = UIWindow(frame: windowScene.screen.bounds)
guard let _externalWindow = externalWindow else { return }
_externalWindow.rootViewController = viewController
_externalWindow.windowScene = windowScene
_externalWindow.isHidden = false
//外部ディスプレイのViewControllerをあとで操作できるようにインスタンスを保持する
externalDisplayViewController = viewController
}
切断処理を実装
外部ディスプレイが切断時の処理も記述します。
/// 外部ディスプレイを切断されたときに処理する
/// - Parameter notification: 切断されたUIWindowSceneインスタンス
@objc func externalScreenDidDisconnect(notification: NSNotification) {
guard let windowScene = notification.object as? UIWindowScene else { return }
guard let _externalWindow = externalWindow else { return }
guard let _externalWindowScene = _externalWindow.windowScene else { return }
//切断通知されたscreenと現在表示中のscreenが同じかチェックする
if windowScene == _externalWindowScene {
_externalWindow.isHidden = true
externalWindow = nil
}
}
iOS12までの方法
Xcode11ではUISceneがデフォルトになっています。iOS12までのUIScreenを利用できるように修正した後に、UIScreenから接続切断通知を受け取り処理をします。
UISceneを無効化
Xcode11からUIScreenからUISceneに変わっています。しかしながら新しいUISceneで外部ディスプレイ対応する方法がわかりませんでした😢
なのでUISceneを無効化します。
githubのサンプルプロジェクトのcommitログを参考にしてください。
概要は下記の通りです。
-
AppDelegate.swift
のUISceneに関連するメソッドを無効化(削除でも可) -
SceneDelegate.swift
の全てのメソッドを無効化(削除でも可) -
AppDelegate.swift
にvar window: UIWindow?
を追加 -
Info.plist
のUISceneに関する記述を削除
外部ディスプレイの接続切断通知を受け取れるように設定
接続切断通知を受け取れるようにします。
/// 外部ディスプレイの接続と切断の通知を受け取れるようにする
private func setupNotification() {
let center = NotificationCenter.default
center.addObserver(self, selector: #selector(externalScreenDidConnect(notification:)), name: UIScreen.didConnectNotification, object: nil)
center.addObserver(self, selector: #selector(externalScreenDidDisconnect(notification:)), name: UIScreen.didDisconnectNotification, object: nil)
}
接続処理を実装
Storyboardで作成した画面にStoryboardIDを設定しておき接続時に画面を生成します。
必要に応じて外部から呼び出せるようにインスタンスを保持しておくと便利だと思います。
/// 外部ディスプレイを接続されたときに処理する
/// - Parameter notification:接続されたUIScreenインスタンス
@objc func externalScreenDidConnect(notification: NSNotification) {
guard let screen = notification.object as? UIScreen else { return }
guard externalWindow == nil else { return }
guard let viewController = self.storyboard?.instantiateViewController(withIdentifier: "ExternalDisplayViewController") as? ExternalDisplayViewController else { return }
externalWindow = UIWindow(frame: screen.bounds)
externalWindow?.rootViewController = viewController
//iOS13.0からDeprecatedになっているがUISceneを無効化しているのでこちらのプロパティーを設定する
externalWindow?.screen = screen
externalWindow?.isHidden = false
//外部ディスプレイのViewControllerをあとで操作できるようにインスタンスを保持する
externalDisplayViewController = viewController
}
切断処理を実装
外部ディスプレイが切断時の処理も記述します。
/// 外部ディスプレイを切断されたときに処理する
/// - Parameter notification: 切断されたUIScreenインスタンス
@objc func externalScreenDidDisconnect(notification: NSNotification) {
guard let screen = notification.object as? UIScreen else { return }
guard let _externalWindow = externalWindow else { return }
//切断通知されたscreenと現在表示中のscreenが同じかチェックする
if screen == _externalWindow.screen {
_externalWindow.isHidden = true
externalWindow = nil
//外部ディスプレイのViewControllerを削除する
externalDisplayViewController = nil
}
}
最後に
他の記事を参考に作成しましたが、externalWindow?.screen
するとDeprecatedになっているプロパティで警告されてしまいます。
正しい実装方法を調べてみましたが、わかりませんでした。
コメントをいただければと思います。
(あわせてこの記事とサンプルプロジェクトを変更していきたいと思います。)
@ura14h さんのコメントによりUISceneを使った方法がわかりました。
本当にありがとうざいました。