IOS用16进制生成UIColor,以及适配浅色/深色模式

司徒池暝
2023-12-01

在ios项目开发过程中根据设计图绘制ui时,往往给咱们的是16进制颜色色值,比如:#FFFFFF、#000000、0xFFFFFFF等等,然而UIColor原生方法中并没有能直接使用这些的方法,故而对UIColor进行扩展,添加些实用方法

颜色方法

通过数字获取,如:0xFFFFFFF
extension UIColor {
    /// 通过哈希值获取颜色
    class func hexColor(_ hexColor:Int, alpha:CGFloat = 1) ->UIColor {
        let red = CGFloat((hexColor & 0xFF0000) >> 16)/255.0
        let green = CGFloat((hexColor & 0xFF00) >> 8)/255.0
        let blue = CGFloat(hexColor & 0xFF)/255.0
        return UIColor(red: red, green: green, blue: blue, alpha: alpha)
    }
}
通过字符获取,如:"#FFFFFF","#FFFFFF,%25"

"#FFFFFF,%25"中"%25"为透明度,若未设置,则默认为1

extension UIColor {
    /// 通过哈希值获取颜色
    class func zj_hexColor(_ hexColor:String) ->UIColor {
        let  resultArray = hexColor.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased().components(separatedBy: ",")
        if var cString = resultArray.first {
            // Remove the prefix
            if cString.hasPrefix("0X") {
                let start = cString.index(cString.startIndex, offsetBy: 2)
                cString = String(cString[start..<cString.endIndex])
            }
            if cString.hasPrefix("#") {
                let start = cString.index(cString.startIndex, offsetBy: 1)
                cString = String(cString[start..<cString.endIndex])
            }
            
            guard cString.count == 6 else {
                return .black
            }
            var redStr:String = ""
            var greenStr:String = ""
            var blueStr:String = ""
            for i in 0...2 {
                let start = cString.index(cString.startIndex, offsetBy: i * 2)
                let end = cString.index(start, offsetBy: 2)
                if i == 0 {
                    redStr = String(cString[start..<end])
                }else if i == 1 {
                    greenStr = String(cString[start..<end])
                }else{
                    blueStr = String(cString[start..<end])
                }
            }
            var r:UInt64 = 0
            Scanner(string: redStr).scanHexInt64(&r)
            var g:UInt64 = 0
            Scanner(string: greenStr).scanHexInt64(&g)
            var b:UInt64 = 0
            Scanner(string: blueStr).scanHexInt64(&b)
            var alpha:CGFloat = 1
            if resultArray.count > 1{
                let alphaString = resultArray[1]
                    .replacingOccurrences(of: " ", with: "", options: .literal, range: nil)
                    .replacingOccurrences(of: "%", with: "", options: .literal, range: nil)
                if let a = Float(alphaString) {
                    if resultArray[1].contains("%") {
                        alpha = CGFloat(a/100.0)
                    }else{
                        alpha = CGFloat(a)
                    }
                }
            }
            return UIColor(red: CGFloat(r)/255.0, green:  CGFloat(g)/255.0, blue:  CGFloat(b)/255.0, alpha: alpha)
        }else{
            return UIColor.clear
        }
    }
}

通过上面的两个方法,基本就可以满足项目中的颜色设置需求。

但有时候,项目还需要适配浅色/深色模式,怎么弄呢?有两种方案

  • 第一种:在每个页面上,每个需要变化的ui都根据浅色/深色模式赋予对应值。

class HZJController: UIViewController {
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        if let previous = previousTraitCollection {
            self.changeUI(previous.userInterfaceStyle)
        }
    }
    
    /// 修改控件样式等
    /// - Parameter style: <#style description#>
    func changeUI(_ style:UIUserInterfaceStyle){
        switch style {
        case .unspecified:
            self.view.backgroundColor = .white
        case .light:
            self.view.backgroundColor = .black
        case .dark:
            self.view.backgroundColor = .zj_hexColor("#FF0000,%50")
        @unknown default:
            break
        }
    }
}

不过这样确实太麻烦了,如果涉及到界面布局变化的话,可以考虑这么做,但是若只有颜色的改变,就不建议这么做了

  • 第二种:还是需要对颜色进行处理,这里就要使用到系统自带的这个方法了

public init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)
extension UIColor {
     /// 颜色翻转
    /// - Returns: <#description#>
    func invertColor() ->UIColor {
        var r:CGFloat = 0
        var g:CGFloat = 0
        var b:CGFloat = 0
        self.getRed(&r, green: &g, blue: &b, alpha: nil)
        return UIColor(red: 1-r, green: 1-g, blue: 1-b, alpha: 1)
    }

    /// 获取随系统变化的颜色
    /// - Parameters:
    ///   - light: 白天模式的颜色
    ///   - dark:  暗黑模式的颜色
    ///   - invertColor: 是否翻转颜色:仅当dark==nil时有效,若true,则使用light的翻转颜色
    /// - Returns: <#description#>
    static func getAutoColor(_ light:String,_ dark:String? = nil ,_ invertColor:Bool = false)->UIColor{
        let lightColor = UIColor.zj_hexColor(light)
        let darkColor = dark != nil ?  UIColor.zj_hexColor(dark!) : invertColor ? lightColor.invertColor() : lightColor
        if #available(iOS 13.0, *) {
            return UIColor.init { (traitCollection) -> UIColor in
                switch traitCollection.userInterfaceStyle {
                case .dark:
                    return darkColor
                default:
                    return lightColor
                }
            }
        } else {
            return lightColor
        }
    }
}

如此,我们就可以定义颜色

extension UIColor { 
    static let zj_0                = UIColor.getAutoColor("#000000", nil)
    static let zj_1                = UIColor.getAutoColor("#000000", "#ffffff", false)
    static let zj_2                = UIColor.getAutoColor("#000000", nil, true)
    static let zj_3                = UIColor.getAutoColor("#000000", "#123456", true)
}

其中zj_0只会显示为000000;

我们知道#000000进行16位反转后是#ffffff,故而zj_1和zj_2是一个效果,他们会在浅色模式下显示000000,在深色模式时显示ffffff;

而zj_3会在浅色模式下显示000000,在深色模式时显示123456;

注意:颜色的显示时根据当前window的UIUserInterfaceStyle变化的

由于我使用的SceneDelegate,故而在SceneDelegate中对window进行设置:

随系统变化:

self.window?.overrideUserInterfaceStyle = .unspecified

只要浅色:

self.window?.overrideUserInterfaceStyle = .light

只要深色:

self.window?.overrideUserInterfaceStyle = .dark

 类似资料: