soranoba
soranoba Author of soranoba.net
programming

iOS13の外観モード(ダークモード)に対応する方法

これは、iOS13から追加される外観モードの対応方法を纏めたものです。現時点ではまだbeta8なので、情報が正しくなくなる可能性があります。 また、まだ対応はしていないので、間違っていたら随時更新するかもしれません。

XCode10でビルドする場合

端末でダークモードを設定した場合でも従来通りの動作をします

XCode11でビルドする場合

従来通り動作させたい場合

Info.plistUIUserInterfaceStyle (User Interface Style) を追加し、Lightを設定することで従来通りの動作にすることができます。

Supporting Dark Mode is strongly encouraged. Use the UIUserInterfaceStyle key to opt out only temporarily while you work on improvements to your app’s Dark Mode support.

Choosing a Specific Interface Style for Your iOS Appより抜粋

このような記述がある通り、この設定は一時的な対応の為のもので、将来的に消える可能性があります。また、一部のViewControllerのみで強制的にlightにしたい場合はoverrideUserInterfaceStyleで対応が可能です。

iOS13からダークモードに対応する場合

DynamiColorを作成する

DynamicColorの仕組みを使うことで、端末の外観モード変更(ライト or ダーク)に追従して自動的に色を変更することができます。ただし、Deployment Targetのバージョンによって作成方法が制限されます。

iOS10以下の場合
/// ライト/ダーク用の色を受け取ってDynamic Colorを作って返す
public 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
}

iOS 13からのダークモード対応のコツより抜粋)

上記のような関数を定義し、両対応の色をコードで作成する必要があります。

iOS11〜の場合

前述の方法に加え、Catalog AssetのColorSetを使うことができます。この方法は、XIBでもその色を指定することができます。
(Supporting Dark Mode in Your Interfaceの「Choose Adaptive Colors for Your UI」を参照)

XIBで指定した場合、< iOS13.0では常にAnyの色が適用される点に注意が必要です。
コードからこれを使用する場合は、UIColor.init(named:)もしくはUIColor.init(named:in:compatibleWith:)によって指定することができます。

また、システムカラーとしてsystemBackgroundなどがダークモード対応の色として定義されていますが、XCode11.0 beta6、iOS13.0 beta8時点では正しく切り替わらないのでstableがリリースされてから確認して使った方が良さそうです。定義されているものはUI Element Colorsを参照してください。

XIBもしくはコードでDynamicColorを指定する

指定する箇所は従来通りで問題ありません。
DynamicColorはalpha値を別で保持するので、以下のように指定することで既存のDynamicColorにalpha値を適用することができます

self.view.backgroundColor = UIColor { (traitCollection) -> UIColor in
    if traitCollection.userInterfaceStyle == .dark {
        return UIColor.blue
    } else {
        return UIColor.red
    }
}).withAlphaComponent(0.5)

この方法を用いることで、ほとんどのケースでDynamicColorで対応することができます。

従来通りのUIColorを使用することもできる

DynamicColorの使用が難しい場合は、従来通りのUIColorも使用することができます。
Viewが読み込まれる際や、端末の外観モード(ライト or ダーク)を変更した際に、traitCollectionDidChange(_:)が呼び出されるので、ここで色を指定します。
この場合は、viewDidLoad()での色指定は不要になります。

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    self.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? UIColor.red : UIColor.blue
}

既にアプリでダークモードを実装している場合

< iOS13.0ではDynamicColorの仕組みを使用することができません。
その為、Deployment TargetをiOS13.0にするまで(もしくは< iOS13.0のダークモードを捨てるか)は既存の仕組みを維持する必要があります。

端末の外観モードを無視する

前述のUIUserInterfaceStyleを指定するか、ViewController単位で指定することができるoverrideUserInterfaceStyleを使用します。
後者の場合、最上位のViewControllerのみに指定すれば、それ以下の子ViewControllerにはoverrideされたものが適用されます。

iOS13の場合だけ端末の外観モードを使用する

traitCollectionDidChange(_:)は古いOSでも呼び出される為、こちらで< iOS13の対応を行う方法が考えられます。

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    if #available(iOS 13.0, *) {
        // DynamicColorを使用している場合は何もしない
    } else {
        self.view.backgroundColor = darkMode ? UIColor.blue : UIColor.red
        
        // iOS12.0+の場合は, ColorSetから取得することもできます
        if #available(iOS 12.0, *) {
            let darkTraitCollection = UITraitCollection(userInterfaceStyle: .dark)
            self.view.backgroundColor = UIColor(named: "MyColor", in: nil, compatibleWith: darkTraitCollection)
        }
    }
}

参考文献

(Updated: )

comments powered by Disqus