こんばんは。着色職人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ヒエラルキーを見てみる。
遷移元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になる。一方 ViewController
は NavigationController
からの指定によりlight styleとなって白い。
ボタンをタップして遷移。 SecondViewController
も同様にlight styleで白。
突然の黒????
子view controllerから外れてstyle強制がなくなってdarkになる。
ちなみにこの状態からスワイプバック操作をキャンセルしてSecondViewControllerを元の位置に戻しても黒いまま。
modal遷移した先のuser interface styleを固定する
結論から言うと、modal表示で遷移して出したview controllerのuser interface styleを強制する場合は直接 overrideUserInterfaceStyle = .light
するしかない。はず。
こちらはmodal遷移した時のviewヒエラルキーの様子。
ご覧のように、遷移元の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のネタ話を売るので絶対に買ってください。