PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

iOS デバイスに接続した外部ディスプレイに対して別の画面を表示させる方法について調べてみた

はじめまして、ソリューション技術部の伊藤です。

今携わっているプロジェクトで、iPhone/iPadをHDMIケーブルを使って外部ディスプレイと接続した時に iPhoneと外部ディスプレイで別の画面を表示してほしいという依頼があり、実際に開発した内容をまとめていきたいと思います。

はじめに

通常、iPhone/iPadをAirPlayやHDMI等の有線ケーブルで外部ディスプレイと接続するとミラーリングとなりますが本方法を用いるとiPhone/iPadと接続した外部ディスプレイで別の画面を表示することができます。

例えば、ゲームアプリでiPhoneをコントローラーにし外部ディスプレイをプレイ画面したり、 iPhoneと外部ディスプレイで異なる動画を同時に再生するなどといったことが可能です。

開発の流れ

事前に外部ディスプレイの実装方針や調査結果はもらっていたのですが、 実際に開発するにあたりイメージがつかなかったので自分でも調べました。

ViewController内で外部ディスプレイの接続/切断通知を受け取って画面表示する方法もありますが、外部ディスプレイ側は基本的に同じ画面を表示するため下記のような実装方針となりました。

  • SceneDelegateに新たに外部ディスプレイ用のクラスを追加
  • 接続があればAppDelegateに通知
  • 表示する画面を設定
  • AppDelegateのユーティリティを基に画面遷移

実際の実装方法

まずは、アプリ起動したときにiPhoneとは別のViewControllerを表示するようにしていきます。 SceneDelegate、Info.Plistなどに必要な記述、設定を行いました。

SceneDelegate

新しくExternalSceneDelegateを追加します。

SceneDelegate

class SceneDelegate: UIResponder, UIWindowSceneDelegate {    
    var window: UIWindow?
    // 〜〜〜 省略〜〜〜
}

// 外部ディスプレイ用に追加
class ExternalSceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    // 〜〜〜 省略〜〜〜
}

Info.Plist

SceneDelegate>Application Session RoleにExternal configurationを追加します。

外部ディスプレイ Info.Plist設定

AppDelegate

configurationForConnecting で 外部ディスプレイ用画面をセットします。 HDMI接続があればUIApplication.willEnterForegroundNotificationに通知するよう設定しています。

AppDelegate

var window: UIWindow?
// 外部ディスプレイ用に追加
var externalWindow: UIWindow?
var externalViewController: ExternalViewController?

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    if connectingSceneSession.role == .windowExternalDisplay {
        let scene = UIWindowScene(session: connectingSceneSession, connectionOptions: options)
        self.externalWindow = UIWindow(frame: scene.screen.bounds)
  
        let rootViewController = R.storyboard.externalDisplay().instantiateInitialViewController()
        if let controller = (rootViewController as? UINavigationController)?.topViewController as? ExternalViewController {
            externalViewController = controller
        }
        self.externalWindow!.rootViewController = rootViewController
        self.externalWindow!.windowScene = scene
        self.externalWindow!.isHidden = false
        self.externalWindow!.makeKeyAndVisible()
        // 外部ディスプレイの接続があれば、追加したExternalSceneDelegateを返却 
        return UISceneConfiguration(name: "External configuration", sessionRole: connectingSceneSession.role)
     }
    // それ以外はデフォルトのSceneDelegate
    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

simulatorで確認

ここまでで、アプリを起動するとiPhoneと外部ディスプレイで別画面が表示されるようになります。 Xcode simulatorで確認するにはウィンドウメニューの I/O>External Displaysで画面サイズを選択すると外部ディスプレイ画面が表示されます。 simulatorで確認すると下記のようになります。

アプリ起動して外部ディスプレイに別画面が表示される

画面遷移してみる

続いて、iPhoneでボタンが押されたら外部ディスプレイ側のみ遷移するようにしていきます。

ViewController

AppDelegate で ユーティリティを定義し、externalViewControllerを参照して遷移します。

AppDelegate

static var shared: AppDelegate {
    return UIApplication.shared.delegate as! AppDelegate
}

ボタンがタップされたら、外部ディスプレイのみ遷移しiPhone側は画面はそのままにします。

ViewController

private func buttonTapped() {
    let storyboard = UIStoryboard(name: "NextViewController", bundle: nil)
    let controller = storyboard.instantiateViewController(withIdentifier: "NextViewController") as! NextViewController
    AppDelegate.shared.externalViewController?.navigationController?.pushViewController(controller, animated: true)
}

simulatorで確認

ボタンをタップすると外部ディスプレイ側だけ画面遷移が行われるようになりました。

外部ディスプレイ側だけ画面遷移した結果

末筆

外部ディスプレイに対してiOSから任意のコンテンツを表示し、画面遷移までの簡単なまとめとなります。

本記事の実装だけでは遷移だけで画面に動きはないので今後コントローラーとプレイ画面の実装など試してみたいです。 iPhone/iPadと外部ディスプレイで別画面表示させたい時はお試しください。 参考になれば幸いです。
最後までお読みいただきありがとうございました。

参考資料

Apple Developer Presenting content on a connected display