Как мне легко поддерживать светлый и темный режим с помощью пользовательского цвета, используемого в моем приложении?

Допустим, у меня в приложении есть собственный цвет:

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 я хочу, чтобы мой пользовательский элемент управления поддерживал как светлый, так и темный режим.

Одно из решений - переопределить 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, необходимо добавить тот же код.

Есть ли лучшее решение, чтобы мой собственный цвет и управление поддерживали как светлый, так и темный режим?


person rmaddy    schedule 07.06.2019    source источник


Ответы (4)


Как оказалось, это действительно просто с новым инициализатором 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)
        }
    }
}

Вот и все. Нет необходимости определять две отдельные статики. Класс элемента управления не требует изменений исходного кода. Не нужно переопределять traitCollectionDidChange или что-то еще.

Приятно то, что вы можете увидеть изменение цвета в переключателе приложений сразу после изменения режима в приложении «Настройки». И, конечно же, цвет обновляется автоматически, когда вы возвращаетесь в приложение.

По соответствующему примечанию при поддержке светлого и темного режима - используйте как можно больше цветов из UIColor. См. Доступные динамические цвета в элементах пользовательского интерфейса и Стандартные цвета. А когда вам нужны собственные цвета для конкретного приложения для поддержки как светлого, так и темного режима, используйте в качестве примера код из этого ответа.


В 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];
    }
}
person rmaddy    schedule 07.06.2019
comment
Да, вы правильно поняли! Для полноты картины есть еще один вариант: определить собственный цвет в каталоге активов, а затем получить цвет с помощью UIColor(named:) / +[UIColor colorNamed:]. - person Kurt Revis; 09.06.2019
comment
Есть идеи, почему в Xcode 11 Beta 5 я получаю Type of expression is ambiguous without more context при вызове инициализации UIColor? - person plamkata__; 23.08.2019
comment
Тайна раскрыта - я использую тот же самый быстрый файл в самой цели Apple Watch, и он не работает в операционной системе часов. - person plamkata__; 23.08.2019
comment
как бы мне обрабатывать разные изображения, вы используете удобную инициализацию, предоставляемую UIColor. Я попытался добавить это локально в расширение, но, похоже, это не сработало. - person Usama bin Attique; 20.11.2019
comment
@UsamabinAttique Пожалуйста, опубликуйте свой вопрос со всеми соответствующими деталями и кодом. - person rmaddy; 20.11.2019
comment
Мне пришлось использовать ссылку developer.apple.com/documentation/uikit/uicolor/, чтобы перейти к упомянутым вами цветам элементов пользовательского интерфейса (вам просто не хватало s в конце. Возможно, они изменили URL-адрес). - person oelna; 02.04.2020

Другое решение с iOS 13 - определение пользовательских цветов в каталоге ресурсов с помощью редактора ресурсов Xcode.

Как упоминалось в документации, когда вам нужен определенный цвет, создайте его как цветовой ресурс. . В своем активе укажите разные значения цвета для внешнего вида светлого и темного. Вы также можете указать высококонтрастные версии ваших цветов.

введите описание изображения здесь

Обратите внимание, что вариант Любой вид - это цвет, который отображается в старых системах, не поддерживающих темный режим.

Чтобы загрузить значение цвета из каталога активов, вы можете загрузить цвет по его имени:

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

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

Теперь каждый раз, когда пользователь переключается между темным и светлым режимами, указанные цвета будут динамически меняться в приложении.

person M Reza    schedule 17.06.2019

Вот этот вспомогательный метод для создания динамических цветов:

extension UIColor {
    static func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
        guard #available(iOS 13.0, *) else { return light }
        return UIColor { $0.userInterfaceStyle == .dark ? dark : light }
    }
}

И для решения вопроса следует использовать вспомогательный метод следующим образом:

extension UIColor {
    static let myControlBackground: UIColor = dynamicColor(light: UIColor(red: 0.3, green: 0.4, blue: 0.5, alpha: 1), dark: UIColor(red: 0.4, green: 0.3, blue: 0.2, alpha: 1))
}

Нет необходимости переопределять traitCollectionDidChange, просто установите backgroundColor один раз, и все готово.

person Lucas Chwe    schedule 27.05.2020
comment
Это действительно очень круто - person Codetard; 25.05.2021

Если вы хотите программно создавать динамические цвета:

Многоразовое расширение:

extension UIColor {

   public class func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
      if #available(iOS 13.0, *) {
         return UIColor {
            switch $0.userInterfaceStyle {
            case .dark:
               return dark
            default:
               return light
            }
         }
      } else {
         return light
      }
   }
}

Цвета приложения:

struct MyColors {

   ///> This is what you are getting from designers,
   /// in case they are not providing consistent color naming.
   /// Can be also just strings with HEX-codes.
   static let xF9EFB1 = #colorLiteral(red: 0.9764705882352941, green: 0.9372549019607843, blue: 0.6941176470588235, alpha: 1)
   static let x6A6A6A = #colorLiteral(red: 0.4156862745098039, green: 0.4156862745098039, blue: 0.4156862745098039, alpha: 1)
   static let xFEFEFE = #colorLiteral(red: 0.9960784313725490, green: 0.9960784313725490, blue: 0.9960784313725490, alpha: 1)
   static let x202020 = #colorLiteral(red: 0.1254901960784314, green: 0.1254901960784314, blue: 0.1254901960784314, alpha: 1)
   ///<

   static var myLabelForeground: UIColor {
      return UIColor.dynamicColor(light: MyColors.x6A6A6A, dark: MyColors.xF9EFB1)
   }

   static var myViewBackground: UIColor {
      return UIColor.dynamicColor(light: MyColors.xFEFEFE, dark: MyColors.x202020)
   }
}

Использование:

class SampleView: View {

   private lazy var label = Label(text: "Hello!")

   override func setupUI() {
      label.textColor = MyColors.myLabelForeground
      label.font = UIFont.systemFont(ofSize: 24, weight: .semibold)
      backgroundColor = MyColors.myViewBackground
      addSubview(label)
      LayoutConstraint.centerXY(label).activate()
   }

}

Результат:

Light  Темный


ОБНОВЛЕНИЕ: NSColor расширение:


import AppKit

extension NSColor {

   public class func dynamicColor(light: NSColor, dark: NSColor) -> NSColor {
      if #available(OSX 10.15, *) {
         return NSColor(name: nil) {
            switch $0.name {
            case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark:
               return dark
            default:
               return light
            }
         }
      } else {
        return light
      }
   }
}
person Vlad    schedule 13.05.2020
comment
Они не будут реагировать на изменения внешнего вида системы, если вы их так создадите. - person Erik Aigner; 27.05.2020
comment
Зависит от того, как он используется. Если вызывается динамически в представлении SwiftUI, изменения внешнего вида системы будут сохранены. - person lassej; 13.08.2020