Hex Color in SwiftUI
Reading time: 3 min
This recipe shows how to use specify SwiftUI Colors with hex values, either hexadecimal numbers or strings.
The end result can look like this:
To make this work, you have to add two extensions to your codebase.
The first one allows you to specify a hex color from an Int
, which you can conveniently specify in hexadecimal form. This is also quite a bit quicker to execute than the string version, if perfomance is of any concern to you.
extension Color {
init(_ hex: UInt, alpha: Double = 1) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xFF) / 255,
green: Double((hex >> 8) & 0xFF) / 255,
blue: Double(hex & 0xFF) / 255,
opacity: alpha
)
}
}
Then, you can use it like this:
let red = Color(0xFF0000)
let green = Color(0x00FF00)
let translucentMagenta = Color(0xFF00FF, alpha: 0.4)
The second extension allows for building a color from a hex string, covering most known formats. It allows for:
- Specifying color with or without leading #.
- 2-digit format for shades of gray.
- 3-digit format for shorthand 6-digit format.
- 4-digit format for gray with alpha.
- 6-digit format for RGB.
- 8-digit format for RGBA.
- Automatically returns
nil
for all invalid formats.
extension Color {
init?(_ hex: String) {
var str = hex
if str.hasPrefix("#") {
str.removeFirst()
}
if str.count == 3 {
str = String(repeating: str[str.startIndex], count: 2)
+ String(repeating: str[str.index(str.startIndex, offsetBy: 1)], count: 2)
+ String(repeating: str[str.index(str.startIndex, offsetBy: 2)], count: 2)
} else if !str.count.isMultiple(of: 2) || str.count > 8 {
return nil
}
guard let color = UInt64(str, radix: 16)
else {
return nil
}
if str.count == 2 {
let gray = Double(Int(color) & 0xFF) / 255
self.init(.sRGB, red: gray, green: gray, blue: gray, opacity: 1)
} else if str.count == 4 {
let gray = Double(Int(color >> 8) & 0x00FF) / 255
let alpha = Double(Int(color) & 0x00FF) / 255
self.init(.sRGB, red: gray, green: gray, blue: gray, opacity: alpha)
} else if str.count == 6 {
let red = Double(Int(color >> 16) & 0x0000FF) / 255
let green = Double(Int(color >> 8) & 0x0000FF) / 255
let blue = Double(Int(color) & 0x0000FF) / 255
self.init(.sRGB, red: red, green: green, blue: blue, opacity: 1)
} else if str.count == 8 {
let red = Double(Int(color >> 24) & 0x000000FF) / 255
let green = Double(Int(color >> 16) & 0x000000FF) / 255
let blue = Double(Int(color >> 8) & 0x000000FF) / 255
let alpha = Double(Int(color) & 0x000000FF) / 255
self.init(.sRGB, red: red, green: green, blue: blue, opacity: alpha)
} else {
return nil
}
}
}
You can find examples online of similar code that use Scanner
with scanHexInt64
to convert the hex string to a number, but that function can produce false positives. E.g, if you feed it with "fail"
, it still returns a valid number equivalent to fa
, while completely ignoring the incorrect il
at the end.
And here are some sample colors, demonstrating all the supported formats:
let gray1 = Color("4f")
let gray2 = Color("#68")
let gray3 = Color("7813")
let red = Color("f00")
let translucentGreen = Color("#00FF0066")
let blue = Color("0000FF")
let invalid = Color("0000F")