【问题标题】:How do I easily support light and dark mode with a custom color used in my app?如何使用我的应用程序中使用的自定义颜色轻松支持明暗模式?
【发布时间】:2019-10-22 13:39:31
【问题描述】:

假设我的应用中有自定义颜色:

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 的其他任何地方都需要添加相同的代码。

是否有更好的解决方案让我的自定义颜色和控件同时支持明暗模式?

【问题讨论】:

    标签: ios swift uicolor ios-darkmode uitraitcollection


    【解决方案1】:

    事实证明,使用新的 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 提供的颜色。查看来自UI ElementsStandard 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];
        }
    }
    

    【讨论】:

    • 是的,你的想法是对的!为了完整起见,还有一个选项:在资产目录中定义自定义颜色,然后使用 UIColor(named:) / +[UIColor colorNamed:] 获取颜色。
    • 知道为什么在 Xcode 11 Beta 5 上我在 UIColor 初始化调用中收到 Type of expression is ambiguous without more context 吗?
    • 谜团已解 - 我在 Apple Watch 目标本身中使用相同的 swift 文件,但它在手表操作系统上失败
    • 我将如何处理不同的图像,您正在使用 UIColor 提供的便捷初始化。我试图在扩展中本地添加它,但它似乎不起作用。
    • @UsamabinAttique 请发布您自己的问题以及所有相关详细信息和代码。
    【解决方案2】:

    iOS 13 的另一个解决方案是使用 Xcode 的资产编辑器在资产目录中定义自定义颜色。

    documentation 中所述,当您需要特定颜色时,将其创建为颜色资源。在您的资源中,为 lightdark 外观指定不同的颜色值。您还可以指定颜色的高对比度版本。

    请注意,任何外观变体是在不支持深色模式的旧系统上显示的颜色。

    要从资产目录加载颜色值,您可以按颜色名称加载颜色:

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

    现在,只要用户在暗模式和亮模式之间切换,指定的颜色就会通过应用程序动态变化。

    【讨论】:

      【解决方案3】:

      这里我得到了这个创建动态颜色的辅助方法:

      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 一次即可。

      【讨论】:

        【解决方案4】:

        如果您想以编程方式创建动态颜色:

        可重复使用的扩展:

        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()
           }
        
        }
        

        结果:


        更新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
              }
           }
        }
        

        【讨论】:

        • 如果你这样创建它们,它们不会对系统外观变化做出反应。
        • 取决于它的使用方式。如果在 SwiftUI 视图中动态调用,系统外观变化将被拾取。
        猜你喜欢
        • 2020-07-29
        • 2023-04-03
        • 1970-01-01
        • 2020-07-27
        • 1970-01-01
        • 2020-01-31
        • 1970-01-01
        • 1970-01-01
        • 2022-11-10
        相关资源
        最近更新 更多