iPhone XS Max/XR と iPhone XSではランドスケープ時のUISplitViewControllerの挙動が異なります

はじめに

ついにiOS 12 そして Xcode 10が正式にリリースされましたね。そして先日iPhone XS/XS Max/XRなど新製品の発表もありました。 これに伴い、Appleが新たにBuilding Apps for iPhone XS, iPhone XS Max, and iPhone XRというビデオを公開しています。 このビデオの中で、iPhone XS Max/XRの画面サイズへの対応方法やiOS 12でのAutoLayoutのパフォーマンスアップなどについて説明があるのですが、UISplitViewControllerについてもXS Max/XR とXSで挙動が異なる部分があったので記事にしたいと思います。

検証環境

本エントリは以下の環境で検証を行っています。

  • macOS High Sierra バージョン 10.13.6
  • Xcode Version 10.0 (10A255)
  • iPhone XS Max iOS 12.0 シミュレーター
  • iPhone XS iOS 12.0 シミュレーター
  • Apple Swift version 4.2 (swiftlang-1000.11.37.1 clang-1000.11.45.1)

違いはMaster-Detail Appのプロジェクトテンプレートですぐに確認できます

UISplitViewControllerの挙動の違いについてはXcode 10でiOS > Master-Detail Appのプロジェクトテンプレートから新規プロジェクトを作成すればすぐに確認できます。

以下はiPhone XS Maxでの実行結果です。

そして以下はiPhone XSでの実行結果です。

ご覧の通り、ランドスケープ時の画面の表示が違いますね。 iPhone XS Maxの方は画面左上のボタンをタップするとMaster画面が左からDetail画面に重なる形で表示されます(iPhone XRも同じ挙動でした)。さらに画面左端をスワイプしても画面を表示できます。 一方、XSの方は一度に1つの画面のみ表示されています。

コードはどうなっているか

コードはどうなっているか見てみましょう。AppDelegateでアプリ起動時にnavigationItem.leftBarButtonItemsplitViewController.displayModeButtonItemを設定しています。これによりXS Max/XRではMaster画面を左から表示するボタンを表示しています。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let splitViewController = window!.rootViewController as! UISplitViewController
    let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
    navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
    splitViewController.delegate = self
    return true
}

挙動を変えてみる

プロジェクトを作成した直後は上記のような実装になっていますが、Master画面を左から表示するボタンは表示したくない、画面左端スワイプによる画面表示をしたくない場合もあるでしょう。 その場合は以下のようにコードを変更します。

// displayModeButtonItemを使っている部分を削除(コメントアウト)
//let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
//navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem

// スワイプによるMaster画面表示を禁止
splitViewController.presentsWithGesture = false

この場合のXS Max/XRでの実行結果は以下になります。

なぜランドスケープ時の画面の表示が違うのか

なぜ、iPhone XS Max/XRは一度に2画面表示され、XSは1画面のみ表示されるのでしょうか。
実はこちらの記事にもあるように過去にはiPhone 6+などもランドスケープ時はiPadのように2画面表示でした。そしてその理由は、iPhone 6+のランドスケープ時のSize ClassのWidthがiPad 同様にRegularになっていたためでした。

そこで今回もSingle View Appを作成し、以下のようにtraitCollectionDidChange(_:)メソッドをオーバーライドして各デバイスのSize Classを取得してみました。

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    let horizontalSizeClass = traitCollection.horizontalSizeClass
    let verticalSizeClass   = traitCollection.verticalSizeClass

    print("--- H ---")
    switch horizontalSizeClass {
    case .unspecified:
        print("unspecified")
    case .compact:
        print("compact")
    case .regular:
        print("regular")
    }

    print("--- V ---")
    switch verticalSizeClass {
    case .unspecified:
        print("unspecified")
    case .compact:
        print("compact")
    case .regular:
        print("regular")
    }
}

まとめると結果は以下の通り。やはりランドスケープ時のSize ClassのWidthがXS MaxとXRではRegularになっていました。ということで2画面表示も納得ですね( ´◡` )

Portrait - Width Portrait - Height Landscape - Width Landscape - Height
iPhone XS Compact Regular Compact Compact
iPhone XS Max Compact Regular Regular Compact
iPhone XR Compact Regular Regular Compact

おわりに

今回はiPhone XS Max/XR と iPhone XSでのランドスケープ時のUISplitViewControllerの挙動・表示の違いをご紹介しました。 ランドスケープでUISplitViewControllerを使ってアプリを開発する場合はこの違いを認識しておきましょう!