iOS 13からダークモードをちゃんとサポートしてねという話し

# Apple からのニュース

[Get Ready for Dark Mode](https://developer.apple.com/news/?id=0829019a)

ダークモードの準備をしてね、という記事です。

> Dark Mode introduces a dramatic new look for iPhone and iPad that is seamlessly integrated throughout the system and simple to turn on from Control Center or Siri. With a dark appearance for all user interface elements, your app’s content becomes the focus.

ダークモードはiPhoneおよびiPadに劇的な新しいデザインを導入します。それはシステム全体にシームレスに統合されControl CenterまたはSiriから簡単にオンにできます。 すべてのユーザーインターフェイス要素のデザインが暗いため、アプリのコンテンツが焦点になります。

> When a user has Dark Mode turned on, all apps built with the iOS 13 SDK will run in Dark Mode. Learn how to optimize for Dark Mode, then test your apps on a device running the latest beta version of iOS 13 or iPadOS. If you need more time to make your apps look fantastic in Dark Mode, or if Dark Mode is not suited for your app, you can learn how to opt out.

ユーザーがダークモードをオンにすると、iOS 13 SDKで構築されたすべてのアプリがダークモードで実行されます。 ダークモードに最適化する方法を学びiOS 13やiPadOSの最新ベータ版を実行しているデバイスでアプリをテストしましょう。ダークモードでアプリの見栄えを良くするためにもっと時間が必要な場合やダークモードがアプリに適していない場合は、オプトアウトする方法を学ぶことができます。



# Dark Mode のドキュメント

https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface/

ダークモードがアクティブなときにアプリが自動的に適応するように色・画像・動作を更新します。

## Overview

macOSおよびiOSでは、ユーザーはシステム全体の明るいデザインまたは暗いデザインを選択できます。ダークモードとして知られるダークなデザインは、多くのアプリがすでに採用しているinterfaceを実装しています。 ユーザーは好みのデザインを選択し、周囲の照明条件や特定のスケジュールに基づいてinterfaceを切り替えることもできます。

すべてのアプリは明るいinterface styleと暗いinterface styleの両方をサポートする必要がありますが一部の場所で特定の外観を使用するとパフォーマンスが向上する場合があります。 たとえば、印刷されたコンテンツには常に明るいデザインを採用できます。

コードを変更する前にダークモードをオンにして、アプリのレスポンスを確認してください。システムが多くの作業を行います。アプリで標準のViewとControlを使用している場合、多くの変更を行う必要はありません。標準のViewとControlは、現在のインターフェイススタイルに合わせてデザインを自動的に更新します。 既にカラーおよび画像アセットを使用している場合、コードを変更せずにダークバリアントを追加できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44392/e14a3a72-650c-062b-7963-33a07c020bc1.png)


## Choose Adaptive Colors for Your UI

基になるinterface styleに自動的に適合する色を選択します。 明るいinterfaceと暗いinterfaceは、非常に異なるカラーパレットを使用します。 明るいデザインではうまく機能する色は、暗いデザインでは見にくい場合があります。adaptive color objectは、異なるinterface styleに対して異なるカラー値を返します。

adaptive color objectsを作成するには、2つの方法があります。

- 固定色値の代わりにセマンティックカラーを選択します。 UI要素を構成するときは、labelColorなどの名前の色を選択します。 これらのセマンティックカラーは、特定のカラー値ではなくカラーの使用目的を伝えます。これらを意図した目的で使用すると、現在の設定に適した色の値でレンダリングされます。セマンティックカラーカラーの完全なリストについては、[NSColor](https://developer.apple.com/documentation/appkit/nscolor)および[UIColor](https://developer.apple.com/documentation/uikit/uicolor)を参照してください。
- Asset catalogでカスタムなカラーを定義します。特定の色が必要な場合は、カラーのAssetとして作成します。 Assetで、明るいデザインと暗いデザインの両方に異なる色の値を指定します。色のコントラストの高いバージョンを指定することもできます。

Xcodeの Asset Editor を使用してカスタムな color assetを設定します。 color set assetをプロジェクトに追加し、変更するデザインのバリエーションを構成します。 Any Appearanceバリアントを使用して、ダークモードをサポートしない古いシステムで使用するカラー値を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44392/48282778-366b-515a-2050-0e43867cb618.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44392/1f0fb417-47ef-65c6-1789-c57d06648fff.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44392/f98807c9-aa1d-440c-9fd7-6680fa0b4736.png)


Asset Catalogから色の値をロードするには、名前で色をロードします。

```swift:Sample.swift
/// Color Set の Name を指定する
// macOS
let aColor = NSColor(named: NSColor.Name("SampleCustomColor"))

// iOS
let aColor = UIColor(named: "SampleCustomColor")
```

Color AssetからColor Objectを作成する場合、**現在のデザインが変わってもそのオブジェクトを再作成する必要はありません**。描画の塗りつぶしまたはストロークの色を設定するたびに、カラーオブジェクトは現在の環境設定に一致するカラーバリアントをロードします。同じことは現在の環境に自動的に適応するlabelColorなどのセマンティックカラーにも当てはまります。対照的に、**固定コンポーネント値を使用して作成したカラーオブジェクトは適応しません**。 代わりに新しいカラーオブジェクトを作成する必要があります。

> Note
ユーザーのコンテンツについては、ユーザーが明示的に選択した色を常に保持します。たとえば、**Paintアプリは、ユーザーがキャンバスに適用する色を変更しようとしてはなりません**。主にアプリのクロムのビューとコントロールで適応可能な色を使用します。

## Create Images for All Appearances (画像の話し)

interfaceの画像が明るいデザインと暗いデザインの両方で見え方が良いことを確認してください。interfaceは、Button、ImageView、Custom ViewとCustom Controlなど、多くの場所でimageを使用します。デザインを変更したときにimageが見にくい場合は、他のデザインで見栄えの良い新しいimage assetを提供します。さらに良いのは、symbol imageまたはtemplate imageを使用することです。symbol imageまたはtemplate imageは、レンダリングする形状のみを定義するため、明るい環境、暗い環境、コントラストの高い環境に個別の画像を必要としません。

明るいインターフェイスと暗いインターフェイスの両方の画像の構成については、[Images for Different Appearances](https://developer.apple.com/documentation/uikit/uiimage/providing_images_for_different_appearances)を参照してください。

## Update Custom Views Using Specific Methods (再描画の話し)

ユーザーがシステムのデザインを変更すると、システムは各ウィンドウとビューに自動的に再描画を要求します。このプロセス中に、システムは、次の表にリストされているmacOSとiOSの両方でよく知られているいくつかのメソッドを呼び出して、コンテンツを更新します。システムは、これらのメソッドを呼び出す前に特性環境を更新します。したがって、**外観に依存するすべてのメソッドを変更すると**、アプリ自体が正しく更新されます。

| Class       |    適切なライフサイクルメソッド   |
|:----------------:|:-------------------|
| NSView           |       updateLayer() <br> draw(_:) <br> layout() <br>  updateConstraints()     |
|    UIView       |            draw(_:) <br> traitCollectionDidChange(_:) <br> layoutSubviews() <br> draw(_:) <br> updateConstraints() <br> tintColorDidChange() | 
|      UIViewController       |              traitCollectionDidChange(_:) <br> updateViewConstraints() <br> viewWillLayoutSubviews() <br> viewDidLayoutSubviews() | 
|       UIPresentationController        |                traitCollectionDidChange(_:) <br> containerViewWillLayoutSubviews() <br> containerViewDidLayoutSubviews() |   


これらのメソッド以外で外観に敏感な変更を加えると、アプリは現在の環境に対してコンテンツを正しく描画しない場合があります。**解決策は、コードをこれらのメソッドに移動すること**です。 たとえば、作成時に`NSView`オブジェクトのレイヤーの背景色を設定する代わりに、以下のコード例に示すように、そのコードをビューの`updateLayer（）`メソッドに移動します。作成時に背景色を設定することは適切に思えるかもしれませんが、`CGColor`オブジェクトが適応できないため、作成時に設定すると変更されない固定の背景色がビューに残ります。コードを`updateLayer（）`に移動すると、環境が変わるたびにその背景色が更新されます。

```swift:Sample.swift
override func updateLayer() {
   self.layer?.backgroundColor = NSColor.textBackgroundColor.cgColor

   // Other updates.
}
```

## Choose Visual-Effect Materials Based on the Intended Usage

Visual-effect viewsは、background viewに透明度を追加します。これにより、UIに背景が不透明である場合よりも視覚的に深みが増します。 コンテンツが表示されたままになるように、Visual-effect viewsは背景コンテンツを微妙にぼかし、鮮やかな効果を追加して前景コンテンツの色を自動的に調整します。システムはこれらの効果を動的に更新し、基礎となるコンテンツが変更されてもアプリのコンテンツが表示されたままになるようにします。

interfaceのvisual-effect viewsをcontainer viewとして使用し、それらにサブビューを追加して、前景コンテンツを表します。必要な外観に適したマテリアルまたは効果を使用して、各視覚効果ビューを構成します。

- macOSでは、interfaceでそのViewを使用する方法に基づいて、適切なマテリアルで`NSVisualEffectView`を構成します。 たとえば、visual-effect viewをサイドバーのinterfaceの背景として使用する場合は、`NSVisualEffectView.Material.sidebar`マテリアルで構成します。
- iOSで、特定の鮮やかな効果とぼかし効果を使用して`UIVisualEffectView`を構成し、必要な外観を作成します。ぼかし効果はbackground viewの見かけの厚さを定義し、鮮やかな効果は特定の種類のコンテンツの外観を調整して、それらが表示されたままになるようにします。 たとえば、ViewにLabelが含まれている場合は、`UIVibrancyEffectStyle.label`スタイルまたは他のラベル関連の鮮やかさオプションのいずれかを選択します。

> 重要
macOS 10.14以降では、`NSVisualEffectView.Material.light`などの非推奨のマテリアルを使用しないでください。これらのマテリアルはダークモードに適応しないためです。代わりに、環境に正しく適合する新しい素材を選択してください。

## Opt Out Only as Needed

アプリで明るい外観と暗い外観の両方を採用するためにあらゆる努力を払ってください。1つの外観のサポートがアプリの全体または一部にとって意味をなさない場合、適切なwindowまたはviewで外観の変更をオプトアウトできます。 たとえば、アプリの印刷ビューには常に明るい外観を使用できます。

特定の外観をオプトアウトするように、interfaceのすべてまたは一部を構成できます。アプリ全体に特定の外観を採用することもできます。詳細については、次を参照してください。

- [Choosing a Specific Appearance for Your macOS App](https://developer.apple.com/documentation/appkit/nsappearancecustomization/choosing_a_specific_appearance_for_your_macos_app)
- [Choosing a Specific Interface Style for Your iOS App](https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface/choosing_a_specific_interface_style_for_your_ios_app)

## Avoid Expensive Tasks During Appearance Transitions

ユーザーが明るいinterfaceと暗いinterfaceを切り替えると、システムはすべてのコンテンツを**再描画するようアプリに要求します**。システムは描画プロセスを管理しますが、そのプロセス中のいくつかの時点でカスタムコードに依存します。 コードは可能な限り高速で、外観の変更に関係のないタスクを実行しないようにする必要があります。 macOSでは、AppKitは通常、外観の変更時にトランジションアニメーションを作成しますが、アプリの再描画に時間がかかりすぎると、それらのアニメーションを中止します。

# 実務としてやるべきこと

このドキュメントを見ることでやるべきことがだいたい分かりましたのでまとめます。

1. 変更が必要になる画面を全て洗い出す
2. ディレクターかデザイナーにダークモード用のカラーコードについて決めてもらっておく
3. AssetCatalog にcolor のSet を登録する
4. Appearances を `Any, Light, Dark`に設定する
5. ダークモードを適用させるViewやテキストのカラーをセットしているコードを全てDark Mode で適用できるメソッドに移動させる

実務ではこのような流れでダークモードの実装をすればいいことが分かりました。
まだダークモードを実装した経験がありませんので仮説ですが、だいたいのプロジェクトで多用するUIViewとUIViewController に関しては

| Class       |    適切なライフサイクルメソッド   |
|:----------------:|:-------------------|
|    UIView       |            draw(_:) <br> layoutSubviews() <br> draw(_:) <br> tintColorDidChange() | 
|      UIViewController       |        updateViewConstraints() <br> viewWillLayoutSubviews() <br> viewDidLayoutSubviews() | 


このあたりのメソッドに移動させればいいかなと思っています。