struct Task: Codable { var content: String var deadline: Date var color: UIColor ... }
There are warnings saying "Type 'Task' does not conform to protocol 'Decodable'" and "Type 'Task' does not conform to protocol 'Encodable'". I searched and found that this is because UIColor does not conform to Codable. But I have no idea how to fix that. So...
How to make UIColor Codable?
Codable protocol The Swift standard library contains types like String , Int , Double , Date , Data , and URL that already conform to Codable . If we create a custom struct , enum , or class , we can conform it to the Codable protocol and use the already existing types without implementing any methods.
SwiftUI Color type doesn't conform to Codable by default. If we need to save it to disk as part of a Codable type or on its own we need to define how it should be encoded ourselves. For encoding purposes we can divide SwiftUI colors into 2 types: constant and dynamic colors.
Codable is the combined protocol of Swift's Decodable and Encodable protocols. Together they provide standard methods of decoding data for custom types and encoding data to be saved or transferred.
Codable is a type alias for the Encodable and Decodable protocols. When you use Codable as a type or a generic constraint, it matches any type that conforms to both protocols.
If you care only about the 4 color components this is a simple solution using a wrapper struct
struct Color : Codable { var red : CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 0.0 var uiColor : UIColor { return UIColor(red: red, green: green, blue: blue, alpha: alpha) } init(uiColor : UIColor) { uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) } }
In this case you have to write a custom initializer to convert the 4 color components from Color
to UIColor
and vice versa.
struct Task: Codable { private enum CodingKeys: String, CodingKey { case content, deadline, color } var content: String var deadline: Date var color : UIColor init(content: String, deadline: Date, color : UIColor) { self.content = content self.deadline = deadline self.color = color } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) content = try container.decode(String.self, forKey: .content) deadline = try container.decode(Date.self, forKey: .deadline) color = try container.decode(Color.self, forKey: .color).uiColor } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(content, forKey: .content) try container.encode(deadline, forKey: .deadline) try container.encode(Color(uiColor: color), forKey: .color) } }
Now you can encode and decode UIColor
let task = Task(content: "Foo", deadline: Date(), color: .orange) do { let data = try JSONEncoder().encode(task) print(String(data: data, encoding: .utf8)!) let newTask = try JSONDecoder().decode(Task.self, from: data) print(newTask) } catch { print(error) }
A smart alternative for Swift 5.1 and higher is a property wrapper
@propertyWrapper struct CodableColor { var wrappedValue: UIColor } extension CodableColor: Codable { init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let data = try container.decode(Data.self) guard let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) else { throw DecodingError.dataCorruptedError( in: container, debugDescription: "Invalid color" ) } wrappedValue = color } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() let data = try NSKeyedArchiver.archivedData(withRootObject: wrappedValue, requiringSecureCoding: true) try container.encode(data) } }
and mark the property with @CodableColor
struct Task: Codable { var content: String var deadline: Date @CodableColor var color: UIColor ... }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With