Skip to content

Instantly share code, notes, and snippets.

@peterfriese
Created March 19, 2021 11:00
Show Gist options
  • Save peterfriese/bb2fc5df202f6a15cc807bd87ff15193 to your computer and use it in GitHub Desktop.
Save peterfriese/bb2fc5df202f6a15cc807bd87ff15193 to your computer and use it in GitHub Desktop.
Making Swift's Color codable
//
// Color+Codable.swift
// FirestoreCodableSamples
//
// Created by Peter Friese on 18.03.21.
//
import SwiftUI
// Inspired by https://cocoacasts.com/from-hex-to-uicolor-and-back-in-swift
// Make Color codable. This includes support for transparency.
// See https://www.digitalocean.com/community/tutorials/css-hex-code-colors-alpha-values
extension Color: Codable {
init(hex: String) {
let rgba = hex.toRGBA()
self.init(.sRGB,
red: Double(rgba.r),
green: Double(rgba.g),
blue: Double(rgba.b),
opacity: Double(rgba.alpha))
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let hex = try container.decode(String.self)
self.init(hex: hex)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(toHex)
}
var toHex: String? {
return toHex()
}
func toHex(alpha: Bool = false) -> String? {
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 {
return String(format: "%02lX%02lX%02lX%02lX",
lroundf(r * 255),
lroundf(g * 255),
lroundf(b * 255),
lroundf(a * 255))
}
else {
return String(format: "%02lX%02lX%02lX",
lroundf(r * 255),
lroundf(g * 255),
lroundf(b * 255))
}
}
}
extension String {
func toRGBA() -> (r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat) {
var hexSanitized = self.trimmingCharacters(in: .whitespacesAndNewlines)
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
var rgb: UInt64 = 0
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
var a: CGFloat = 1.0
let length = hexSanitized.count
Scanner(string: hexSanitized).scanHexInt64(&rgb)
if length == 6 {
r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
b = CGFloat(rgb & 0x0000FF) / 255.0
}
else if length == 8 {
r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
a = CGFloat(rgb & 0x000000FF) / 255.0
}
return (r, g, b, a)
}
}
@DaemonLoki
Copy link

That's a wonderful snippet, @peterfriese, and really useful!

I found something that was an issue for me: on line #41, the conversion from Color to cgColor is a bit flawed. In some cases, it fails.
I changed it to using UIColor(self).cgColor.components instead, which works more robustly in the cases I've tried.

Example:

print(Color.indigo.cgColor) // prints "nil"
print(UIColor(Color.indigo).cgColor // prints "<CGColor 0x60000260a820> [...]"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment