個人開発のアプリでダークモード対応したので進め方とその所感

## はじめに

ついに iOS 13 がリリースされましたね。
私のリリースしているアプリに関しては対応を済ませて
iOS 13 リリース日の 9/20 ギリギリにリリースしました。

iOS 13 では大きな変更が多数あり，さらに意図しないところでクラッシュしたり，
変更があったりして今までになく対応を強いられる部分も多かったです😓
大きな変更のひとつがダークモード対応ですね。
みなさん記事を書かれているので私もダークモード対応の進め方などについて書こうと思います。

## キャッチアップ

WWDC 18 で macOS にダークモードが搭載されることが発表され，
いつも通りなら来年は iOS かなと思われていましたが，
やはりiOS 13からダークモード搭載となりました。
(WWDC19のサイトのデザインやもらったアップルロゴの漆黒のピンバッチでも感じられましたね)

![IMG_1421.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/5512bd85-0013-95d4-b4e4-67e27e35b776.jpeg)

私はその発表を会場で聞いていてダークモードでの表示にかっけーと感心していました。
早速 Xcode 11 で自分のアプリをビルドしてみて，予想以上の表示崩れに絶望してから
SwiftUI のキャッチアップに移ってしまったので 6月はじめから 8月中旬までほとんど手つかずでした。

お盆が終わって業務でやっている案件が落ち着いてきたので，
個人アプリの iOS 13 対応にようやく取り組めた感じです。
そもそもダークモード対応するか，Xcode 11 でビルドしてみた
アプリをしばらく触ってみつつ，案件でも対応することがあるかもしれないし，
どういう感じかまずはみてみようと決めてキャッチアップを始めました。

## ライトモードだけの選択肢

Apple は全アプリがダークモードに対応することを期待してるよ！と言っているのですが，
今すぐに対応しなくてもライトモードで引き続きアプリをリリース出来なくはないです。
`info.plist` に `UIUserInterfaceStyle` キーを追加して Value を `Light` にすれば良いです。
逆に追加しない場合は Xcode 11 でビルドするとダークモード対応となってしまうということです。

<img width="430" alt="LightModeOnly.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/86b49ba1-454f-6b7a-2466-3ccd269fccff.png">


ダークモード対応するなら，設定が全画面に及ぶことからデザインは全体的な見直しが必要です。
コードで画面ごとに対応・・・もできますが現実的ではないです。
業務レベルならば，アプリのテーマカラーなどの見直しに始まり，
画像の色あいを変更したり，各画面のデザイン仕様書を作ってから実装を始めるという感じになると思われます。

そして実装したはいいが，画面によっては少しテコ入れをーとか普通にありそうです。
一気に対応をするとなると，デザイナーやクライアントの確認を含め，
画面数に比例して結構な工数を準備して取り組む必要がありそうです。

## 私のダークモードの対応手順

どんな表示になるかをまず確認する必要があります。
私の場合，まず Xcode 11 で既存のプロジェクトをビルドして実機で確認するところから始めました。
各画面を表示させてみて，対応が必要な画面をリストアップして画面ごとにチケットとしました。

<img width="413" alt="Ticket.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/efec6983-3a01-c665-dc90-d0b6e301560a.png">

チケットごとにブランチを切って，マージリクエストを作成する際にどういう表示になるのか，
修正前のスクリーンショットとともに貼って確認しました。
(個人開発なのでレビュアも自分。レビューはやらせですw)

![IMG_2579.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/be160007-a589-252f-b1ae-054264d4685d.jpeg)


今回は個人アプリでの対応なのでそこまで深刻に考えずに，
シンプルに対応してみようと考えました。
ライトモードは iOS 12 までと同じ見え方としてダークモードは別に対応しようと決めました。

## 画像アセットの準備

これまで使っていた天気のアイコン画像は黒で背景は透過になっています。
ライトモードだと白基調なので問題ないのですがダークモードだと
黒基調の背景になるのでダークモード用に白アイコンを用意しました。

Xcode 上でライトモード時，ダークモード時に設定される画像アセットを用意可能です。

<img width="610" alt="icon_any_dark.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/d695357f-09ce-617b-5a78-b93a1fdf924e.png">


ライトモードとダークモードの2種類で良いので `Appearances` は `Any, Dark` とします。
iOS 13 の端末でライトモードの場合は `Any` の画像が，ダークモードの場合は `Dark` の方の画像が使われます。
そして，iOS 13 未満では `Any` として設定した画像が使われます。
2 つのアセットが必要かもと考えていましたが，だいぶ楽に導入できました。
(`img_hoge_light` と `img_hoge_dark` のアセットを用意してコードで書き分けるなどは不要です！)

下記のように各モードの場合自動で画像が切り替わります。

|ライトモード|ダークモード|
|:--:|:--:|
|![top_light.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/e02756d9-fe1f-62c1-9176-d4c8a1e12b5b.png)|![top_dark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/83ee9681-0590-1534-0e37-5c41ac96eb0c.png)|

## ライトモード時とダークモード時に適用する色

いわゆる白と黒とする場合，背景色などであれば `System Background Color` を，
ラベルなどのテキストのフォントカラーであれば `Label Color` を
Storyboard やコードで指定するだけで良いです。
他にも多くのシステムカラーが用意されています。
システムカラーはライトモードとダークモードとで少し色合いが異なっています。

<img width="362" alt="systemcolor.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/2164a95c-80a9-0dfb-4a65-1e99c6fd7fe9.png">

中途半端に RGB などの指定色やデフォルト色を使っていると
ダークモードにした際に表示がおかしく見えるわけです。
黒背景に黒ラベルなので何も見えないとか逆もしかり。。。

用意されていない色の指定をしたい場合は少しコードを書く必要があります。
FLAT Weather Clock アプリでは背景色は白ではなく，
テーマカラーの半蔵門線パープルよりかなり明るいパープルを採用している部分があります。
そのような単純に白・黒で表現できない場合などが例として挙がります。
(**イケてないのでデザイン再考は必須・・・**)

|ライトモード|ダークモード|
|:--:|:--:|
|<img width="584" alt="tab2_light.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/e7e9670d-f457-8784-aee5-50ce01656f41.png">|<img width="584" alt="tab2_dark.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/207e3d72-f4c9-0be1-cd39-27f2d4ef18f2.png">|

ライトモード，ダークモードを取得するには `UITraitCollection` クラスの
`userInterfaceStyle` の値を見ます。
また，ライトモードからダークモードに変更された際に
`UIUserInterfaceStyle` が変更されるので `UITraitCollection` クラスの
`userInterfaceStyle` の値を見て変更するコードを書く必要があります。
この辺りを考慮しないとライトモードからダークモードに変わった時に自動更新できないです。

その対応のため，`UIColor` の extension としてクラスメソッドを用意しました。
一度書いておくとコードで色を指定する際便利なメソッドです。

```swift
extension UIColor {

    /// ライトモード時のColorとダークモード時のColorを受けて端末のuserInterfaceStyleの値で適切な方の色を返却
    public class func setDynamicColor(light: UIColor, dark: UIColor) -> UIColor {
        if #available(iOS 13, *) {
            return UIColor { (traitCollection) -> UIColor in
                return traitCollection.userInterfaceStyle == .dark ? dark: light
            }
        }
        // iOS 13 未満はライトモード用のColorを返却
        return light
    }
}
```

例えば，あるラベルのテキストカラーをライトモードの際は `System Green Color`，
ダークモードの際は `System Blue Color` としたい場合は下記のように呼べば設定できます。

```swift
hogeLabel.textColor = UIColor.setDynamicColor(light: .systemGreen, dark: .systemBlue)
```

`Label Color` などは，iOS 13 以上なので Storyboard で指定する際はいいのですが，
コードで指定する際は注意が必要です。

```swift
// 例
if #available(iOS 13, *) {
    hogeLabel.textColor = .label
} else {
    hogeLabel.textColor = .black
}
```

今回は，不具合がある？と噂されていたため使わなかったのですが，
Color Asset もあるのでそちらで画像のときと同じように，
`Any` または `Dark` の色を設定することもできますね！

<img width="752" alt="coloraseet.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/c758417e-afda-febe-e052-341f514769f7.png">


## Secondary Label Color や Secondary System Background Color の色の使いどころ

基本的に，Storyboard の下地(`self.view` 相当)は `System Background Color` を使っています。Grouped スタイルの TableView は `Group Table View Background Color` という感じに用意された色を充てるようにしています。

`System Background Color` などは系統が 4 つあります。
`Secondary`，`Tertiary`，`Quaternary Background Color` です。
(`Label Color` も同じ)

セッションの動画でも紹介されてました。(下記画像)
左がライトモード，右がダークモードでの色合いですね。(白背景なのでほとんど見えないけど) 

<img width="512" alt="primarytachi.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/008aceaa-269b-8d3f-0cd6-873de2a8a57b.png">

[Implementing Dark Mode on iOS セッション動画](https://developer.apple.com/videos/play/wwdc2019/214/) より

これらの使い分けはどんな感じでやればいいんだろう？と思っていましたが，
今回の対応で 1度使う機会がありました。View を重ねるような場合です。
`FLAT Weather Clock` アプリでは時計ボタンをタップすることで
時計Viewが画面上に天気画面に重ねて表示されます。
この View の背景色に `Secondary Background Color` を用いています。

<img width="480" alt="secondary_background_color.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/44b8d40f-75ec-9d4e-34bd-c01873006136.png">

Label も同様で，普通の Label とは意味合いが違うといった場合に使う感じです。
あえて `Gray Color` 使っていた部分を `Secondary Label Color` に変更したなどの例があります。

`Tertiary` や `Quaternary` までは使う機会はあまりないかもですが，
`Secondary` はうまく使っていきたいと思いました。


## 対応時に困ったこと

当然対応済みのアプリもなく，この場合はどう実装するのが正解なんだろう？ということは多かったです。
ここではそれ以外で困ったことを何個かピックアップして書きます。

### `ScrollBar Indicator` の仕様変更と色付け

先日 Qiita にも書いたのですが，`ScrollBar` のインジケータに画像を
これまで充てていたのですがこのコード部分でクラッシュしてしまっていました。

原因は iOS 12 まではインジケータを `UIImageView` で取得できていたのですが，
iOS 13 からはこちらが UIView に変わっていたため，
`UIImageView` として取得できずに nil となりクラッシュとなっていたことでした。
`UIView` として取得して，インジケータ用の画像を `addSubView` することで対応します。

取得した `UIView` の `Background Color` を設定しても色がおかしいということがあります。
こちらはおそらく Indicator の Style の問題です。
ライトモード，ダークモードでそれぞれ色合いが変わってしまうので，
好きな色を出したい場合は画像として `addSubView` した方がいいかもなと感じました。
(現行アプリはまだ色がおかしいままなので次バージョンで変更する)

詳しくは，
[iOS 13 端末で UIScrollView の Indicator に画像を設定するコード部分がクラッシュしていたのでその対応](https://qiita.com/MilanistaDev/items/8151dd2544b2eda9353b)
をご覧ください。

### NavigationBar のLarge Title の色付け

iOS 13 から Large Title 設定時に `Navigation Bar` の
`barTintColor` が効いていないのが気になる。

デフォルトの NavigationBar 色なら良いが，
着色する場合はどう表示させるのが正解なのかいまいちわかっていない。
`UINavigationBarAppearance` あたりを見て対応する。

## おわりに

今回は，個人アプリのダークモード対応について書きました。
最初は表示崩れに頭抱えてしまったのですが，いざ対応を始めてみると
ほぇ〜こんな表示になるのかぁと楽しくなってどんどん対応進められました。
また，ダークモードに対応することでそれぞれの画面で謎の満足感が得られました。
Map などとても表示がカッコ良いので各アプリはぜひ対応して欲しいです。

今回は，ダークモードの表面だけ的な意識なので，ドキュメントの読み込みや
他の対応したアプリを使ってみて，こういう場合はこうするといいのかーとか
どんどんダークモードに関する知見をためて，活かしていきたいと思います。
今回の対応では扱う機会がなかったので Blur 周りも触ってみたい。

皆様も楽しいダークモード対応を！

乱文になりましたが，ご覧いただきありがとうございました。

## 参考セッション

[Implementing Dark Mode on iOS](https://developer.apple.com/videos/play/wwdc2019/214/)