LoginSignup
9
3

More than 1 year has passed since last update.

どうしても UITabBarController で6タブ表示したい

Posted at

完全に動作保証された方法ではありません

敵「この画面の露出を上げたいので、タブ増やしてそこに配置できないですかね?」
ぼく「あー今全部で5タブ使ってしまっているので、これ以上は難しいですね」
ぼく「HIG(Human Interface Guidelines)上はタブは最大5個までなので」
ぼく( UITabbarController でのタブ実装自体を変更するのは勘弁だなあ)
ぼく「最悪、審査でのリジェクトリスクもありますよ!」
敵「でも、(某有名アプリ)は6タブでストア公開してますよね?」
ぼく「 🙄 」

というわけで、 UITabBarController でどうにか6タブ表示する方法を調べました。

前提情報

iOS アプリでタブ表示を実現するとなると、 UITabBarController が最初の選択に入ってくるかと思います。

UITabBarController は非常によくできており、HIG通りiPhone端末での6タブ以上の表示を防止するために、6タブ以上を設定すると省略表記とする動きが存在します。

これを省略せずに6タブを展開した状態で表示させたいというのが今回の目的です。

調査

HIG上、タブの最大数が5個というのは界隈では比較的通じる話ではあるのですが、実は iPad 上はこの限りではありません。

In general, use between three and five tabs on iPhone; if needed, a few more are acceptable on iPad.

実際、 iPad で UITabBarController を利用して6タブ表示してみると、 Portrait な向きにおいても6タブ表示されます。

iPhone iPad
iPhone.png iPad.png

iPhone と iPad で UITabBarController の動作が変わってくるため、何かしらでの条件分岐があると思われ、実際に動かして調べたところ Size Class によって動作が変わっているようでした。

Size Classes の詳細についてはググっていただくとして、ざっくり言ってしまうと、 Size Classes は画面の幅と高さを

  • compact
  • regular

の2つのカテゴリに分類したものとなっています。

iPad での Multitasking による画面分割を無視すると、端末毎の設定は以下の表となるのですが、 UITabBarController が省略せずに6タブ表示する場合が、この表の Regular width 部分に一致してきます。

Device Portrait orientation Landscape orientation
12.9" iPad Pro Regular width, regular height Regular width, regular height
11" iPad Pro Regular width, regular height Regular width, regular height
10.5" iPad Pro Regular width, regular height Regular width, regular height
9.7" iPad Regular width, regular height Regular width, regular height
7.9" iPad mini Regular width, regular height Regular width, regular height
iPhone 12 Pro Max Compact width, regular height Regular width, compact height
iPhone 12 Pro Compact width, regular height Compact width, compact height
iPhone 12 Compact width, regular height Compact width, compact height
iPhone 12 mini Compact width, regular height Compact width, compact height
iPhone 11 Pro Max Compact width, regular height Regular width, compact height
iPhone 11 Pro Compact width, regular height Compact width, compact height
iPhone 11 Compact width, regular height Regular width, compact height
iPhone XS Max Compact width, regular height Regular width, compact height
iPhone XS Compact width, regular height Compact width, compact height
iPhone XR Compact width, regular height Regular width, compact height
iPhone X Compact width, regular height Compact width, compact height
iPhone 8 Plus Compact width, regular height Regular width, compact height
iPhone 8 Compact width, regular height Compact width, compact height
iPhone 7 Plus Compact width, regular height Regular width, compact height
iPhone 7 Compact width, regular height Compact width, compact height
iPhone 6s Plus Compact width, regular height Regular width, compact height
iPhone 6s Compact width, regular height Compact width, compact height
iPhone SE Compact width, regular height Compact width, compact height
iPod touch 5th generation and later Compact width, regular height Compact width, compact height

参照元:Human Interface Guidelines / iOS / Adaptivity and Layout / Size Class

実際、 iPhone 端末では UITabBarController による6タブ表示が難しいと思われているのですが、上の表の通り iPhone 11 で Landscapeの向きで表示してみると、何もせずとも6タブ表示できてしまいます。

Portrait Landscape
Portrait.png Landscape.png

対応方法

検証コード全体は以下を参照ください。

前述の調査の通り、 Size Classes によって6タブの場合に省略されているかどうかが制御されているようなので、強引にこれを書き換えに行きます。

具体的な対象としては、 UIViewController.traitCollection:.horizontalSizeClass となります。

var horizontalSizeClass: UIUserInterfaceSizeClass { get }

UITraitEnvironment.traitCollection

UITraitCollection.horizontalSizeClass

ただし、このプロパティは直接値を設定することはできないため、親となる ViewController から以下のメソッドを利用して、対象の ViewController の traitCollection を上書きする形となってきます。
(このため、 UITabBarController を直接 UIWindow.rootViewController に設定している場合は、親VCを1枚挟む必要があります)

func setOverrideTraitCollection(_ collection: UITraitCollection?, 
                       forChild childViewController: UIViewController)

実際のコードとしては、以下のような感じになるかと思います。

UITabBarController のtraitCollection を上書き
class RootViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    let firstVC = TabBarController()

    // `horizantalSizeClass = .regular`となるよう、`traitCollection` の設定を親 VC から上書きする
    setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: .regular),
                               forChild: firstVC)

    addChild(firstVC)
    view.addSubview(firstVC.view)
    firstVC.didMove(toParent: self)
  }
}

基本的にはこれで iPhone 端末においても6タブ表示が可能です。

ただ、traitCollection は親から子に引き継がれていくため、 UITabBarController から各タブのコンテンツとなる ViewController に対して、強制設定された traitCollection が引き継がれないような対応も入れておいた方が良さそうでした。

UITabBarController の子 VC のtraitCollectionを本来のものに戻す
class TabBarController: UITabBarController {
  private let contentViewControllers: [UIViewController] = [
    // 各タブのコンテンツとなる ViewController
  ]

  private let originalTraitCollection: UITraitCollection

  init(originalTraitCollection: UITraitCollection) {
    self.originalTraitCollection = originalTraitCollection

    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    for vc in contentViewControllers {
      // 本来の `traitCollection` に戻すように上書きする
      setOverrideTraitCollection(originalTraitCollection, forChild: contentVC)
    }
    setViewControllers(contentVCs, animated: false)
  }
}

対応後

今回の対応によって、以下のように iPhone 端末における Portrait な向きの表示において、6タブ表示が可能となってきます。
(一応、 iOS 13.X と 14.X で確認しています)

iPhone SE 1st gen iPhone 12 Pro Max
iPhone_SE_1st_gen.png iPhone_12_Pro_Max.png

1点問題があるのが Landscape 表示で、上述の表で

Compact width, compact height

となっている端末においては、 Landscape な向きの表示において、タブ上部がはみ出てしまいます。

5タブ 6タブ
5tabs.png 6tabs.png

このため、

  • Landscape はサポートしない
  • UITabBarController は利用したい
  • traitCollection の書き換えによる不明な影響を考慮しても6タブ化したい

といった条件が揃って、初めて導入できる対応といった感じとなっています。

9
3
0

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
3