LoginSignup
9
6

More than 3 years have passed since last update.

Xcode11で外部ディスプレイに表示できるようにする方法(iOS13対応)

Last updated at Posted at 2019-10-20

はじめに

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.swiftvar 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を使った方法がわかりました。
本当にありがとうざいました。

参考サイト

9
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6