macOS と iOS の ダークモード の サポート方法
以下の記事を参考に書いてます。
・Supporting Dark Mode in Your Interface
1. はじめに
macOSおよびiOSでは、ユーザーはシステム全体で、「明るい外観」と「暗い外観」のいずれかを選択できます。ダークモード と呼ばれる暗い外観は、多くのアプリがすでに採用しているインターフェイススタイルを実装しています。ユーザーは好みのデザインを選択し、周囲の照明条件や特定のスケジュールに基づいて、インターフェースを切り替えることができます。
全てアプリは「ライトモード」と「ダークモード」の両方をサポートする必要がありますが、場所によっては特定の外観でパフォーマンスが向上する場合があります。たとえば、印刷には常に明るい外観を採用できます。
コードを変更する前に、「ダークモード」をオンにして、アプリの反応を確認してください。システムは多くの作業を行います。アプリが標準のビューとコントロールを使用している場合は、多くの変更を行う必要がない場合があります。標準のビューとコントロールは、現在のインターフェイススタイルに合わせて外観を自動的に更新します。色と画像のアセットを既に使用している場合は、コードを変更せずにダークバリアントを追加できます。
2. UIの適応色の利用
「ライトモード」と「ダークモード」では、異なるカラーパレットを使用します。「ライトモード」でうまく機能する色は、「ダークモード」では見にくい場合があり、その逆も同様です。
適応色は、インターフェイススタイルごとに異なる色値を返します。適応色の作成方法は、次の2つです。
◎ セマンティックカラーの選択
UI要素を構成するときは、labelColorなどのセマンティックカラーを選択します。これは、特定の色値ではなく、カラーの使用目的を意味します。現在の設定に適した色の値でレンダリングされます。セマンティックカラーの名前の完全なリストについては、「NSColor」「UIColor」を参照してください。
◎ アセットカタログでカスタムカラーを定義
特定の色が必要な場合は、アセットカタログでカスタムカラーを定義します。アセットで、ライトモードとダークモードの両方に異なる色値を指定します。色の高コントラストバージョンを指定することもできます。
Xcodeのアセットエディタを使用してカスタムカラーアセットを設定します。プロジェクトにカラーセットアセットを追加し、変更する外観のバリアントを構成します。Any Appearanceバリアントを使用して、ダークモードをサポートしていない古いシステムで使用する色値を指定できます。
アセットカタログから色値をロードするコードは、次のとおりです。
// macOS
let aColor = NSColor(named: NSColor.Name("customControlColor"))
// iOS
let aColor = UIColor(named: "customControlColor")
カラーアセットから色を作成する場合、現在の外観が変更された時に、そのオブジェクトを再作成する必要はありません。描画の塗りつぶしまたはストロークの色を設定するたびに、カラーオブジェクトは現在の環境設定に一致するカラーバリアントをロードします。同じことが、現在の環境に自動的に適応するlabelColorなどのセマンティックカラーにも当てはまります。対照的に、固定コンポーネント値を使用して作成したカラーオブジェクトは適応しません。 代わりに、新しいカラーオブジェクトを作成する必要があります。
【注意】ユーザーのコンテンツについては、ユーザーが明示的に選択した色を常に保持します。たとえば、ペイントアプリは、ユーザーがキャンバスに適用する色を変更しようとするべきではありません。主にアプリのクロムのビューとコントロールで、適応可能な色を使用します。
3. すべての外観に対応した画像の作成
ライトモードとダークモードの両方でインタフェースの画像を確認してください。インターフェイスは、ボタン、画像ビュー、カスタムビューとコントロールなど、多くの場所で画像を使用します。外観を変更した時に画像が見づらい場合は、外観ごとに見栄えのよい画像を用意します。
さらにおすすめなのは、レンダリングするシェイプのみを定義するシンボルイメージまたはテンプレートイメージを使用することです。そのため、ライトモード、ダークモード、高コントラスト環境で、別々の画像を必要としません。ライトモードとダークモードの両方の画像を設定する方法については、「さまざまな外観のイメージの提供」を参照してください。
4. カスタムビューの更新
ユーザーがシステムの外観を変更すると、システムは自動的に各ウィンドウとビューに再描画を要求します。この処理の間に、システムはmacOSとiOSの両方のいくつかのメソッドを呼び出してコンテンツを更新します。システムは、これらのメソッドを呼び出す前に、トレイト環境を更新するため、これらのメソッドで外観の変更を行った場合、アプリは正しく更新されます。
◎ NSView
・updateLayer()
・draw(_:)
・layout()
・updateConstraints()
◎ UIView
・ traitCollectionDidChange(_:)
・ layoutSubviews()
・ draw(_:)
・ updateConstraints()
・ tintColorDidChange()
◎ UIViewController
・ traitCollectionDidChange(_:)
・ updateViewConstraints()
・ viewWillLayoutSubviews()
・ viewDidLayoutSubviews()
◎ UIPresentationController
・ traitCollectionDidChange(_:)
・ containerViewWillLayoutSubviews()
・ containerViewDidLayoutSubviews()
これらメソッド外で外観に影響のある変更を加えると、アプリは現在の環境でコンテンツを正しく描画しない可能性があります。
解決策は、コードをこれらのメソッドに移動することです。たとえば、以下のコード例に示すように、作成時にNSViewオブジェクトのレイヤーの背景色を設定する代わりに、そのコードをビューのupdateLayer()に移動します。作成時に背景色を設定することは適切に思えるかもしれませんが、CGColorは適応しないため、作成時に設定すると、変更されない固定背景色がビューに残ります。コードをupdateLayer()に移動すると、環境が変化するたびに背景色が更新されます。
override func updateLayer() {
self.layer?.backgroundColor = NSColor.textBackgroundColor.cgColor
// 他の更新
}
5. VisualEffectのマテリアルの選択
VisualEffectビューは、背景ビューに透明度を追加します。これにより、背景が不透明である場合よりもUIの視覚的深度が向上します。
VisualEffectビューをコンテナとして使用し、サブビューをビューに追加して、フォアグラウンドコンテンツを表します。各VisualEffectビューを、必要な外観に適したマテリアルまたはエフェクトで設定します。
◎ macOS
ビューの使用方法に基づいて、適切なマテリアルで NSVisualEffectView を利用します。 たとえば、サイドバーインターフェースの背景としてVisualEffectビューを使用する場合は、NSVisualEffectView.Material.sidebarマテリアルで利用します。
◎ iOS
VisualEffectビューで、背景ビューにエフェクトをかけます。Blur効果(ぼかし)または Vibrancy効果(磨りガラス)を適用できます。たとえば、ビューにラベルが含まれている場合、UIVibrancyEffectStyle.label スタイル、または vibrancy オプションを選択します。
【重要】macOS 10.14以降では、NSVisualEffectView.Material.lightなどの非推奨のマテリアルを使用しないでください。これらのマテリアルはダークモードに適応しないためです。代わりに、環境に正しく適応する新しいマテリアルを選択してください。
6. オプトアウト
アプリでライトモードとダークモードの両方を採用するように、あらゆる努力をしてください。アプリ全体または一部で1つの外観をサポートしても意味がない場合は、適切なウィンドウまたはビューでの外観の変更をオプトアウトできます。たとえば、アプリの印刷ビューには常にライトモードを使用できます。
詳細については、以下を参照してください。
・macOSアプリに特定の外観を選択
・iOSアプリに特定の外観を選択
7. 外観の移行中の高負荷なタスクの回避
ユーザーがライトモードとダークモードを切り替えると、システムはアプリにすべてのコンテンツの再描画を要求します。システムは描画プロセスを管理しますが、そのプロセス中のいくつかの時点でカスタムコードに依存します。コードは可能な限り高速で、外観の変更に関係のないタスクを実行しないようにする必要があります。
macOSでは、AppKitは通常、外観の変更中に遷移アニメーションを作成しますが、アプリが再描画するのに時間がかかりすぎる場合、それらのアニメーションを中止します。
この記事が気に入ったらサポートをしてみませんか?