9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

iOS #2Advent Calendar 2019

Day 16

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

Last updated at Posted at 2019-12-15

こんばんは。着色職人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のネタ話を売るので絶対に買ってください。

9
6
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
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?