Help us understand the problem. What is going on with this article?

遷移とviewヒエラルキーとDark Mode

こんばんは。着色職人Lv.2です。

世の中のiOSエンジニアは以下の2つに分類されます(要出典)。

  • お行儀よくアプリを開発してきたので、サクッとDark Mode対応を終わらせることができた人
  • 無秩序にviewと色を作成し乱用してきたので、すぐにはDark Mode対応が終わらない残念な人

1番目のお行儀のよい人は羨ましい
2番目の残念な人はどうすればいいでしょうか?
先に負債を完済してお行儀よい人になってもよいのですが、Dark Mode対応が終わった箇所から順次解禁という道がある……かもしれません。

つまりどういうことかというと、
Dark Mode対応が終わっていないviewを全てlight styleに指定し、バージョンアップのタイミングで対応済みviewがあればlight style指定を解除し公開する、ということです。
(全viewへの対応が完了するまでは白黒のまだら模様なアプリを晒すことに……)

というわけで、本記事ではいかにlight styleに強制するかを考えていきます。

復習: user interface styleを固定する

Dark Modeになると困るviewには以下のように指定しておく。

view.overrideUserInterfaceStyle = .light
viewController.overrideUserInterfaceStyle = .light

この指定はviewヒエラルキーにおける子viewにも適用される。上位のviewやview controllerを選んで指定すれば、子view全体に対して一発で指定することができる。

UIUserInterfaceStyle 効果
.light 自身と子viewをlight styleに強制
.dark 自身と子viewをdark styleに強制
.unspecified 親viewの設定に従う(初期値)

例えば、以下のような設定とviewヒエラルキーになっている場合、 UIWindow はDark Modeのオンオフに従ってstyleが変わるが、 UIViewController から先はlight styleに強制されて変化しない。

** info.plist **
User Interface Style = Automatic

** view hierarchy **
[UIWindow] overrideUserInterfaceStyle = .unspecified
          ↓
          ↓
[UIViewController] overrideUserInterfaceStyle = .light
          ↓
[UIView] overrideUserInterfaceStyle = .unspecified

本題: 遷移先のview controllerのuser interface styleを固定する

遷移先のコンテンツは全部light styleに固定したい、という場合。
遷移先のview controller全部に直接

override func viewDidLoad() {
    super.viewDidLoad()
    overrideUserInterfaceStyle = .light
}

などと書いてもよいが、ここでは遷移元からまとめて指定する方法を考えてみる。

遷移元のview controllerがlight styleなら遷移先もlight styleになってくれないかな、とか、
遷移先view controllerのstyleをサクッと設定する方法がないかな、とか。
それができれば楽なんだけどなー

pushしたview controllerのuser interface styleを固定する

遷移後のviewヒエラルキーを見てみる。
Screen Shot 2019-12-16 at 0.34.55.png
遷移元navigation controllerと遷移先view controller(上の画像ではSecondViewController)には親子関係がある。なので、navigation controllerをlight styleに強制しておけば、遷移先も全てlight styleになる。
view controller間には親子関係がないので、遷移前のview controllerがlight style強制になっていても遷移後のview controllerには影響がない。

こちらのメソッドを使ってもOK。子view controllerのuser interface styleをまとめて指定できる。

class NavigationController: UINavigationController {
    override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
        return .init(userInterfaceStyle: .light)
    }
}

バックスワイプすると……

こんな感じのコードを用意してみる。

  • NavigationController は子view controllerを全部light styleに強制。
  • ViewController のボタンをタップすると SecondViewController にpush遷移する。
    • 背景色はどちらも UIColor.systemBackground 。つまり設定されたstyleに対応した色。
class NavigationController: UINavigationController {
    override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
        return .init(userInterfaceStyle: .light)
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
    }

    @IBAction func tappedButton(_ sender: UIButton) {
        self.navigationController?.pushViewController(SecondViewController(), animated: true)
    }
}

class SecondViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
    }
}

Dark Mode Onの状態で実行。 NavigationController 自体は何も強制されていないので、navigation barはモードに合わせてdark styleになる。一方 ViewControllerNavigationController からの指定によりlight styleとなって白い。

ボタンをタップして遷移。 SecondViewController も同様にlight styleで白。

ここからずずずっとスワイプしてバックを仕掛ける。すると、

突然の黒????
子view controllerから外れてstyle強制がなくなってdarkになる。
ちなみにこの状態からスワイプバック操作をキャンセルしてSecondViewControllerを元の位置に戻しても黒いまま。

modal遷移した先のuser interface styleを固定する

結論から言うと、modal表示で遷移して出したview controllerのuser interface styleを強制する場合は直接 overrideUserInterfaceStyle = .light するしかない。はず。

こちらはmodal遷移した時のviewヒエラルキーの様子。
Screen Shot 2019-12-16 at 0.41.27.png
ご覧のように、遷移元のview controller(NavigationControllerやViewController)と遷移先のview controller(SecondViewController)は親子関係にない。従って、遷移元view controllerに対して何かしても遷移先view controllerには影響しない。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // 自分はlight styleに強制されるが遷移先のview controllerに効力は及ばない
        overrideUserInterfaceStyle = .light
    }

    @IBAction func tappedButton(_ sender:) {
        let vc = SecondViewController()

        // もちろん直接設定すればstyleを強制できる
        vc.overrideUserInterfaceStyle = .light
        present(vc, animated: true, completion: nil)
    }

    // modal遷移先のview controllerは子view controllerではないので効果なし
    override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
        return .init(userInterfaceStyle: .light)
    }
}

まとめ

  • 安易に overrideUserInterfaceStyle = .light しても白くならないことがあるので気をつける
  • 負債をためない

【宣伝】技術書典8の2日目に本記事のようなDark Modeのネタ話を売るので絶対に買ってください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした