ダークモードとは何か？各OSの設定方法からiOS/Androidアプリの対応方法まで

# はじめに

勉強会をご覧の皆さま

元々のテーマは「10分でわかるダークモード対応」でしたが、あちこち調べているうちにページ数が30を超えてしまい、「**１０分ではわからないダークモード対応**」になりました🙇‍♂️

20分には収めます

---

# ダークモードとは？

OSシステムレベルで設定可能な、画面表示色の設定です。
![light_and_dark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/f057c91a-a01a-d235-7987-c1ff39a6d9f4.png)
Light Modeが従来通りの白基調な画面、**Dark Modeは黒基調な画面**です。

---

# 何が明るく/暗くなるの？

特定の何に適用すべきという法則は無いようですが、**閲覧者に見せたいコンテンツ以外のウィンドウ枠、背景など**に対して適用されることが多いようです。

もちろん、メインコンテンツ（テキスト色など）も背景色に応じて調整する必要があります。

---

# ダークモードにすると何がいいの？

* 目へのダメージを抑える
  * 眩しい画面を見ることによる**ドライアイ**、**頭痛**、メラトニン抑制による**不眠**など
* バッテリーの持ちが長くなる
  * OLEDスクリーンで輝度が100％の場合、DarkはLightと比較して約60％のエネルギーを節約。輝度が50％の場合、約15％節約（YouTubeアプリで計測）
* 背景は明るいより暗いほうが、メインのコンテンツを目立たせ、**作業に集中しやすくする**
  * [背景が可読性に与える影響についての論文](https://lite.mst.edu/media/research/ctel/documents/LITE-2003-04.pdf)

---

# ダークモードにする場合のデメリットや注意点

* 直射日光の下などの明るい場所で読みづらい
  * ナイトモード（ブルーライトカットの）と同じように、日没〜日の出までダークモードとする設定が用意されている
* テキスト色と背景色の逆転にOCRが追いついていない
  * ダークモードで撮ったスクショをGoogle翻訳にかけても（現時点では）翻訳してくれない😭

---

# ダークモード対応OS

* Windows 10 version 1903〜
* Mac OS Mojave(10.14)〜
* Android 10〜
* iOS 13〜

---

# ダークモード設定方法

* Windows
  * スタート -> 設定 -> 個人用設定 -> 色
* macOS
  *  -> システム環境設定 -> 一般 -> 外観モード
* Android
  * 設定 -> ディスプレイ -> テーマ
  * Zenfoneの場合は 設定 -> ディスプレイ -> システムカラースキーム
  * [クイック設定パネル](https://news.line.me/issue/oa-andgp/2cff3dd26068)に追加したい場合は 画面上端を下にスワイプ -> 🖋
* iOS
  * 設定 -> 画面表示と明るさ -> 外観モード
  * [コントロールセンター](https://support.apple.com/ja-jp/HT202769)に追加したい場合は 設定 -> コントロールセンター -> コントロールをカスタマイズ

---

# iOS/Androidアプリでのダークモード対応方法（アプリエンジニア向け）

---

## iOS

### 準備
* Xcode 11以上のビルド環境
* iOS 13以上の実機orシミュレーター

---

### iOSダークモード対応は急務！！

* iOSアプリが2020年4月以降、[iOS 13 SDKビルドでの審査提出が必須となる](https://developer.apple.com/news/?id=09102019a)
* iOS 13 SDKビルドはデフォルトでダークモードがONになる
* `UIUserInterfaceStyle`を使えば一時しのぎ出来るが、[公式は移行を強く推している](https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface/choosing_a_specific_interface_style_for_your_ios_app)
  * 将来効かなくなる可能性が高い

---

### まずダークモードの見え方を確認しよう！

#### StoryboardやXibのInterface Builder上でLight/Darkを切り替える

* <a href="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/f8990081-beff-9cad-c2ab-3759c107a0ec.png" target="blank"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/f8990081-beff-9cad-c2ab-3759c107a0ec.png" width="744"></a>
  * 画面中央下部

---

#### デバッグ実行中にLight/Darkを切り替える

* <a href="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/d68d6e35-5fc7-d48a-b414-ac12504d769a.png" target="blank"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/d68d6e35-5fc7-d48a-b414-ac12504d769a.png" width="320"></a>
  * 画面中央下部
* <a href="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/45ae0d34-cef0-132f-2085-cd00bfd49c7e.png" target="blank"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/45ae0d34-cef0-132f-2085-cd00bfd49c7e.png" width="342"></a>

---

#### Darkにして、どうなったか。弊社案件の場合

* テキストが自動で白くなったが背景も白い。文字が読めない！
* 背景が自動で黒くなったがテキストも黒（
* 濃緑基調のグラフィックが黒い背景では見えづらい
* QRコードが真っ黒黒助

#### 何故そうなるのか

* 場所によってiOS System Colorsを使ったり使わなかったりするため
* 大人数のプロジェクトでありがちな統一感の甘さ
* とはいえiOSのStoryboardを完璧な統一感で作った人は見たことありませんが…😅

---

### 対応作業

#### カラーセットを使う場合

1. ハードコードしている色をAssets CatalogのColor Setに移行する
1. Color SetのAppearancesにてDarkを追加する
<a href="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/c4628137-2187-0269-4558-f087f8664149.png" target="blank"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/c4628137-2187-0269-4558-f087f8664149.png" width="750"></a>
1. Dark Appearanceにフォーカスを合わせ色設定する

---

* 色は従来のsRGB方式以外に、iOS System Colorsも選択可能
  * `darkTextColor`や`systemBackgroundColor`など
  * 使うとOS側が**色の濃さを自動調整してくれる**。便利！
  * 詳しくは参考記事の[実践 iOS13ダークモード対応](https://qiita.com/hirothings/items/4834481d170332e173f5#ui-element-colors%E3%82%92%E3%81%AA%E3%82%8B%E3%81%B9%E3%81%8F%E4%BD%BF%E3%81%86)に、わかりやすく書かれています

---

#### カラーセットを使わない場合

* アプリ起動中にLight/Darkを切り替えられても即反映させたいので、次のいずれかの方法で実装

---

* dynamicColor方式

```swift
extension UIColor {
    private class func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
        if #available(iOS 13, *) {
            return UIColor { (traitCollection) -> UIColor in
                if traitCollection.userInterfaceStyle == .dark {
                    return dark
                } else {
                    return light
                }
            }
        }
        return light
    }

    public static var textColor: UIColor {
        return dynamicColor(
            light: UIColor(displayP3Red: 0, green: 9, blue: 11, alpha: 1),
            dark: UIColor(displayP3Red: 255, green: 246, blue: 244, alpha: 1)
        )
    }
}
```

---

* dynamicProvider方式

```swift
extension UIColor {
    @available(iOS 13.0, *)
    convenience init(light: UIColor, dark: UIColor) {
        self.init {
            if $0.userInterfaceStyle == .dark {
                return dark
            } else {
                return light
            }
        }
    }

    @available(iOS 13.0, *)
    public static let textColor = UIColor(
        light: UIColor(displayP3Red: 0, green: 9, blue: 11, alpha: 1),
        dark: UIColor(displayP3Red: 255, green: 246, blue: 244, alpha: 1)
    )
}
```

こちらはSDKバージョンの制限を受けますが、initとして処理を書けて、定数をimmutableにできます。

---

## Android

### 準備
* Android Studio 3.3以上のビルド環境
* Android 10(Q)以上の実機orシミュレーター

---

### まずダークモードの見え方を確認しよう！

#### Design View上でLight/Darkを切り替える

<a href="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/89765312-056c-b751-9a81-9059d9c71f93.png" target="blank"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/89765312-056c-b751-9a81-9059d9c71f93.png" width="750"></a>
* Night Mode -> Nightを選択

---

#### 開発者向けオプションを使う

* システム -> 詳細設定 -> 開発者向けオプション -> フォースダークのオーバーライド
* アプリの実装を変えずダークモードをシミュレートできる

---

#### Darkにして、どうなったか。弊社案件の場合

* Design View上では何も変わらなかった…
  * Android版は`?android:attr/textColorPrimary`などのシステムカラーを使っていなかったから
  * Androidチームはデザイン指示書に厳密に従ったので、システムカラーを使わなかった
* でも、開発者向けオプションだといい感じのダークモードになった
  * iOSで起きてた問題は一切起きなかった（細かい部分を見ると直したい色はあったが）
  * [Android10の強制ダークモードは割としっかりダークしてくれる](https://qiita.com/kuluna/items/e9e32208377d5fb3206e)の例と同じような結果だった

---

### 対応作業

1. `styles.xml`や`colors.xml`を別々に用意する
  * Light：`res/values/*`
  * Dark：`res/values-night/*`
  * 共通のものはLight側にまとめよう
2. Darkの方には、**夜間モード**か**フォースダーク**を適用する（後述）
  * 2つの重ね掛けはできないらしい

* もしくは、iOSのSystem Colorsに相当する[テーマの属性やマテリアルデザイン コンポーネントを利用する](https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#%E3%83%86%E3%83%BC%E3%83%9E%E3%81%A8%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB)こともできる。厳密にカラー指定しない場合はこちらがオススメ
* 色のハードコードは厳禁らしい

---

#### [夜間モード](https://developer.android.com/guide/topics/resources/providing-resources#NightQualifier)機能を使う

* アプリを新規作成すると自動で`Theme.AppCompat.Light`系のテーマが適用されているが、Dark側のスタイルには`Theme.AppCompat.DayNight`を適用する

```xml:res/values-night/themes.xml
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar" />
```
---

#### [フォースダーク](https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#%E5%BC%B7%E5%88%B6%E3%83%80%E3%83%BC%E3%82%AF)機能を使う

* 各ビューを分析して**自動でダークカラーに変えてくれる**機能
* 先述の開発者向けオプションを使った確認では、こちらがシミュレートされる

```xml:res/values-night/themes.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:forceDarkAllowed">true</item>
</style>
```

* 自動で変えたくないビューには、AppTheme以外のstyleを用意して適用すればよい

---

### Androidのダークモードサポートは色々手厚い！

* より詳しく知りたい場合、[こちら](https://developer.android.com/guide/topics/ui/look-and-feel/darktheme)を参照

---

## WebViewの場合は？

* Web側CSSで対応するらしい（[StackOverflowより](https://stackoverflow.com/questions/57203909/how-to-use-ios-13-darkmode-for-wkwebview)）

---

# まとめ

* ダークモードは目とバッテリーに優しく、集中できる！
* iOSは2020/04まで（03末？）にダークモードを対応するか、一時しのぎでLightに固定しなければならない
  * まずは動かしてみないと修正規模がわからない
* Androidはフォースダークで対応するのが手っ取り早そう

---

# 最後に大事なこと

* ダークモード対応は**エンジニアファーストで提案しよう！**
  * 顧客やPMは、iOS SDKの事情でダークモード対応が必要なんて知る由もない
* 工数削減のために、自動調整してくれる**システムカラーは最大限活用しよう！**
  * 活用すれば、20画面あっても半日で終わるから怖くない（ヤバいAppは知りません）
* デザイナーにも必要な情報を公開しよう！
  * 一言「ダークモード版の素材と指示書お願いします」で受けてくれるデザイナーは奇跡の存在です（個人の所感）

---

# やっぱり言わせてください

**面倒くせー！！！**
<a href="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/7da75e7b-a765-de7f-2479-47a06eaa2408.jpeg" target="blank"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277107/7da75e7b-a765-de7f-2479-47a06eaa2408.jpeg" width="1024"></a>

---

# 参考
* [Use Dark Mode on your iPhone, iPad, or iPod touch](https://support.apple.com/en-us/HT210332)
* [How to use Dark Mode on your Mac](https://support.apple.com/en-us/HT208976)
* [The Benefits of Dark Mode: Why should you turn off the lights?](https://blog.weekdone.com/why-you-should-switch-on-dark-mode/)
* [ダークテーマ | Android デベロッパー | Android Developers](https://developer.android.com/guide/topics/ui/look-and-feel/darktheme)
* [Dark Mode - How to support Dark Mode for iOS 13 using Xcode](https://zonneveld.dev/how-to-support-dark-mode-in-ios-apps/)
* [実践 iOS13ダークモード対応](https://qiita.com/hirothings/items/4834481d170332e173f5)
* [Android10の強制ダークモードは割としっかりダークしてくれる](https://qiita.com/kuluna/items/e9e32208377d5fb3206e)