web-dev-qa-db-ja.com

アプリでカスタムカラーを使用してライトモードとダークモードを簡単にサポートするにはどうすればよいですか?

アプリにカスタムカラーがあるとします。

extension UIColor {
    static var myControlBackground: UIColor {
        return UIColor(red: 0.3, green: 0.4, blue: 0.5, alpha: 1)
    }
}

これをカスタムコントロール(およびその他の場所)でコントロールの背景として使用します。

class MyControl: UIControl {
    override init(frame: CGRect) {
        super.init(frame: frame)

        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)

        setup()
    }

    private func setup() {
        backgroundColor = .myControlBackground
    }

    // Lots of code irrelevant to the question
}

IOS 13では、カスタムコントロールでライトモードとダークモードの両方をサポートしたいと考えています。

1つの解決策は、traitCollectionDidChangeをオーバーライドして色が変更されているかどうかを確認し、必要に応じて背景を更新することです。また、明るい色と暗い色の両方を提供する必要があります。

だから私は私のカスタムカラーを更新します:

extension UIColor {
    static var myControlBackgroundLight: UIColor {
        return UIColor(red: 0.3, green: 0.4, blue: 0.5, alpha: 1)
    }
    static var myControlBackgroundDark: UIColor {
        return UIColor(red: 0.4, green: 0.3, blue: 0.2, alpha: 1)
    }
}

そして、制御コードを更新します。

extension MyControl {
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)

        if #available(iOS 13.0, *) {
            if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
                backgroundColor = traitCollection.userInterfaceStyle == .dark ?
                   .myControlBackgroundDark : .myControlBackgroundLight
            }
        }
    }
}

これは機能しているように見えますが、不格好で、たまたまmyControlBackgroundを使用する他の場所では、同じコードを追加する必要があります。

カスタムカラーとコントロールでライトモードとダークモードの両方をサポートするより良い解決策はありますか?

9
rmaddy

結局のところ、これは新しいUIColor init(dynamicProvider:)イニシャライザを使用すると本当に簡単です。

カスタムカラーを次のように更新します。

extension UIColor {
    static var myControlBackground: UIColor {
        if #available(iOS 13.0, *) {
            return UIColor { (traits) -> UIColor in
                // Return one of two colors depending on light or dark mode
                return traits.userInterfaceStyle == .dark ?
                    UIColor(red: 0.5, green: 0.4, blue: 0.3, alpha: 1) :
                    UIColor(red: 0.3, green: 0.4, blue: 0.5, alpha: 1)
            }
        } else {
            // Same old color used for iOS 12 and earlier
            return UIColor(red: 0.3, green: 0.4, blue: 0.5, alpha: 1)
        }
    }
}

それでおしまい。 2つの個別の静特性を定義する必要はありません。コントロールクラスは、元のコードからの変更を必要としません。 traitCollectionDidChangeなどをオーバーライドする必要はありません。

これの良い点は、設定アプリでモードを変更した直後にアプリスイッチャーで色の変化を確認できることです。そしてもちろん、アプリに戻ると、色は自動的に最新の状態になります。

ライトモードとダークモードをサポートする場合の関連ノート-UIColorから提供される色をできるだけ多く使用します。 I Elements および Standard Colors から利用可能な動的色を参照してください。また、明るいモードと暗いモードの両方をサポートするために独自のアプリ固有の色が必要な場合は、この回答のコードを例として使用してください。


Objective-Cでは、以下を使用して独自の動的な色を定義できます。

UIColor + MyApp.h:

@interface UIColor (MyApp)

@property (class, nonatomic, readonly) UIColor *myControlBackgroundColor;

@end

UIColor + MyApp.m:

+ (UIColor *)myControlBackgroundColor {
    if (@available(iOS 13.0, *)) {
        return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traits) {
            return traits.userInterfaceStyle == UIUserInterfaceStyleDark ?
                [self colorWithRed:0.5 green:0.4 blue:0.2 alpha:1.0] :
                [self colorWithRed:0.3 green:0.4 blue:0.5 alpha:1.0];
        }];
    } else {
        return [self colorWithRed:0.3 green:0.4 blue:0.5 alpha:1.0];
    }
}
26
rmaddy

iOS 13の別の解決策は、Xcodeのアセットエディターを使用して、アセットカタログでカスタムカラーを定義することです。

documentation で述べたように、特定の色が必要な場合は、それをカラーアセットとして作成します。アセットで、lightdarkの両方の外観に異なる色の値を指定します。また、色の高コントラストバージョンを指定することもできます。

enter image description here

Any Appearanceバリアントは、ダークモードをサポートしていない古いシステムで表示される色です。

アセットカタログから色の値を読み込むには、色を名前で読み込むことができます。

// iOS
let aColor = UIColor(named: "customControlColor")

// macOS
let aColor = NSColor(named: NSColor.Name("customControlColor"))

これで、ユーザーがダークモードとライトモードを切り替えるたびに、指定された色がアプリを通じて動的に変化します。