Как мне легко поддерживать светлый и темный режим с помощью собственного цвета, используемого в моем приложении?
Допустим, у меня есть собственный цвет в моем приложении:
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
нужно добавить тот же код.
Есть ли лучшее решение для поддержки моего пользовательского цвета и поддержки управления как светлым, так и темным режимом?
2 ответа
Как оказалось, это действительно легко с новым 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];
}
}
Другое решение с iOS 13 - это определение пользовательских цветов в вашем каталоге ресурсов с помощью редактора ресурсов Xcode.
Как уже упоминалось в документации, когда вам нужен определенный цвет, создайте его в качестве цветового актива. В вашем активе укажите разные значения цвета для светлых и темных видов. Вы также можете указать высококонтрастные версии ваших цветов.
Обратите внимание, что вариант Any Appearance - это цвет, который отображается в старых системах, которые не поддерживают Dark Mode.
Чтобы загрузить значение цвета из каталога активов, вы можете загрузить цвет по его имени:
// iOS
let aColor = UIColor(named: "customControlColor")
// macOS
let aColor = NSColor(named: NSColor.Name("customControlColor"))
Теперь каждый раз, когда пользователь переключается между темным и светлым режимами, указанные цвета будут динамически меняться через приложение.
Вот этот вспомогательный метод для создания динамических цветов:
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
один раз, и все готово.
Если вы хотите программно создавать динамические цвета:
Многоразовое расширение:
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()
}
}
Результат:
ht tps:https://stackru.com/images/c52ac6d4d3046ff27250328e098fd4f1107e4dbb.png ht tps:https://stackru.com/images/f427917587d3336134d7b9a6965912edf469e037.png
ОБНОВЛЕНИЕ: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
}
}
}