如何将 UIColor 转换为 Swift 中的 3/4/6/8 位十六进制字符串?

How do I convert a UIColor to a 3/4/6/8 digits hexadecimal string in Swift?

如何将 UIColor 转换为 Swift 中 3/4/6/8 位的十六进制字符串?

如何获得特定的?例如,通过调用 UIColor.blue.eightDigitsString

得到“#0000FFFF”

请参阅this:

5.2. The RGB hexadecimal notations: #RRGGBB

The CSS hex color notation allows a color to be specified by giving the channels as hexadecimal numbers, which is similar to how colors are often written directly in computer code. It’s also shorter than writing the same color out in rgb() notation.

The syntax of a is a <hash-token> token whose value consists of 3, 4, 6, or 8 hexadecimal digits. In other words, a hex color is written as a hash character, "#", followed by some number of digits 0-9 or letters a-f (the case of the letters doesn’t matter - #00ff00 is identical to #00FF00).

The number of hex digits given determines how to decode the hex notation into an RGB color:

6 digits
The first pair of digits, interpreted as a hexadecimal number, specifies the red channel of the color, where 00 represents the minimum value and ff (255 in decimal) represents the maximum. The next pair of digits, interpreted in the same way, specifies the green channel, and the last pair specifies the blue. The alpha channel of the color is fully opaque. In other words, #00ff00 represents the same color as rgb(0 255 0) (a lime green).

8 digits
The first 6 digits are interpreted identically to the 6-digit notation. The last pair of digits, interpreted as a hexadecimal number, specifies the alpha channel of the color, where 00 represents a fully transparent color and ff represent a fully opaque color. In other words, #0000ffcc represents the same color as rgb(0 0 100% / 80%) (a slightly-transparent blue).

3 digits
This is a shorter variant of the 6-digit notation. The first digit, interpreted as a hexadecimal number, specifies the red channel of the color, where 0 represents the minimum value and f represents the maximum. The next two digits represent the green and blue channels, respectively, in the same way. The alpha channel of the color is fully opaque. This syntax is often explained by saying that it’s identical to a 6-digit notation obtained by "duplicating" all of the digits. For example, the notation #123 specifies the same color as the notation #112233. This method of specifying a color has lower "resolution" than the 6-digit notation; there are only 4096 possible colors expressible in the 3-digit hex syntax, as opposed to approximately 17 million in 6-digit hex syntax.

4 digits
This is a shorter variant of the 8-digit notation, "expanded" in the same way as the 3-digit notation is. The first digit, interpreted as a hexadecimal number, specifies the red channel of the color, where 0 represents the minimum value and f represents the maximum. The next three digits represent the green, blue, and alpha channels, respectively.

现在我已经知道如何将 UIColor 对象转换为 6 位十六进制字符串。但是我不确定如何将它转换为 3-digits/4-digits/8-digits 十六进制字符串以及应该注意什么。

guard let components = cgColor.components, components.count >= 3 else {
    return nil
}
let r = Float(components[0])
let g = Float(components[1])
let b = Float(components[2])
var a = Float(1.0)
if components.count >= 4 {
    a = Float(components[3])
}
if alpha {
    // rrggbbaa mode
    // is there any difference between rrggbbaa and aarrggbb?
    return String(format: "%02lX%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255), lroundf(a * 255))
} else {
    // rrggbb mode
    return String(format: "%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255))
}

注意:它是 UIColor 到字符串,而不是字符串到 UIColor

这是 UIColor 的扩展,可以提供多种格式的 hexString,包括 3、4、6 和 8 位数字形式:

extension UIColor {
    enum HexFormat {
        case RGB
        case ARGB
        case RGBA
        case RRGGBB
        case AARRGGBB
        case RRGGBBAA
    }

    enum HexDigits {
        case d3, d4, d6, d8
    }

    func hexString(_ format: HexFormat = .RRGGBBAA) -> String {
        let maxi = [.RGB, .ARGB, .RGBA].contains(format) ? 16 : 256

        func toI(_ f: CGFloat) -> Int {
            return min(maxi - 1, Int(CGFloat(maxi) * f))
        }

        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0

        self.getRed(&r, green: &g, blue: &b, alpha: &a)

        let ri = toI(r)
        let gi = toI(g)
        let bi = toI(b)
        let ai = toI(a)

        switch format {
        case .RGB:       return String(format: "#%X%X%X", ri, gi, bi)
        case .ARGB:      return String(format: "#%X%X%X%X", ai, ri, gi, bi)
        case .RGBA:      return String(format: "#%X%X%X%X", ri, gi, bi, ai)
        case .RRGGBB:    return String(format: "#%02X%02X%02X", ri, gi, bi)
        case .AARRGGBB:  return String(format: "#%02X%02X%02X%02X", ai, ri, gi, bi)
        case .RRGGBBAA:  return String(format: "#%02X%02X%02X%02X", ri, gi, bi, ai)
        }
    }

    func hexString(_ digits: HexDigits) -> String {
        switch digits {
        case .d3: return hexString(.RGB)
        case .d4: return hexString(.RGBA)
        case .d6: return hexString(.RRGGBB)
        case .d8: return hexString(.RRGGBBAA)
        }
    }
}

例子

print(UIColor.red.hexString(.d3))  // #F00
print(UIColor.red.hexString(.d4))  // #F00F
print(UIColor.red.hexString(.d6))  // #FF0000
print(UIColor.red.hexString(.d8))  // #FF0000FF

print(UIColor.green.hexString(.RGB))  // #0F0
print(UIColor.green.hexString(.ARGB))  // #F0F0
print(UIColor.green.hexString(.RGBA))  // #0F0F
print(UIColor.green.hexString(.RRGGBB))  // #00FF00
print(UIColor.green.hexString(.AARRGGBB))  // #FF00FF00
print(UIColor.green.hexString(.RRGGBBAA))  // #00FF00FF

print(UIColor(red: 0.25, green: 0.5, blue: 0.75, alpha: 0.3333).hexString()) // #4080c055

任何UIColor实例都可以用8个十六进制数字表示:例如#336699CC。对于某些颜色,可以使用更短的表示:

  • 对于不透明颜色(alpha 未指定或 1.0),alpha 分量可以省略:#336699FF 变为 #336699
  • 如果所有颜色分量由成对的相同数字组成,则该数字只需要指定一次:#336699CC变为#369C,但是#335799CC不能缩短
  • 以上两条规则可以组合:#336699FF变成#369

以下函数将 return 给定 UIColor 允许的最短有效表示。

struct HexRepresentationOptions: OptionSet {
    let rawValue: UInt

    static let allowImplicitAlpha = HexRepresentationOptions(rawValue: 1 << 0)
    static let allowShortForm = HexRepresentationOptions(rawValue: 1 << 1)

    static let allowAll: HexRepresentationOptions = [
        .allowImplicitAlpha, 
        .allowShortForm
    ]
}

func hexRepresentation(forColor color: UIColor, 
                       options: HexRepresentationOptions = .allowAll) -> String? {
    var red: CGFloat = 0.0
    var green: CGFloat = 0.0
    var blue: CGFloat = 0.0
    var alpha: CGFloat = 0.0

    guard color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
        return nil
    }

    let colorComponents: [CGFloat]
    if options.contains(.allowImplicitAlpha) && alpha == 1.0 {
        colorComponents = [red, green, blue]
    } else {
        colorComponents = [red, green, blue, alpha]
    }

    let hexComponents = colorComponents.map { component -> (UInt8, UInt8, UInt8) in
        let hex = UInt8(component * 0xFF)
        return (hex, hex & 0x0F, hex >> 4)
    }

    let hasAlpha = colorComponents.count == 4
    let useShortForm = options.contains(.allowShortForm) && 
        !hexComponents.contains(where: { c in c.1 != c.2 })

    let hexColor: UInt64 = hexComponents.reduce(UInt64(0)) { result, component in
        if useShortForm {
            return (result << 4) | UInt64(component.1)
        } else {
            return (result << 8) | UInt64(component.0)
        }
    }

    switch (useShortForm, hasAlpha) {
    case (true, false):
        return String(format: "#%03X", hexColor)
    case (true, true):
        return String(format: "#%04X", hexColor)
    case (false, false):
        return String(format: "#%06X", hexColor)
    case (false, true):
        return String(format: "#%08X", hexColor)
    }
}