使 UIColor 可编码
Make UIColor Codable
struct Task: Codable {
var content: String
var deadline: Date
var color: UIColor
...
}
有警告说 "Type 'Task' does not conform to protocol 'Decodable'" 和 "Type 'Task' does not conform to protocol 'Encodable'"。搜索了一下,发现这是因为UIColor不符合Codable。但我不知道如何解决这个问题。所以...
如何使UIColor Codable?
如果您只关心 4 个颜色分量,这是一个使用包装器结构的简单解决方案
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)
}
}
在这种情况下,您必须编写一个自定义初始化程序来将 4 个颜色分量从 Color
转换为 UIColor
,反之亦然。
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)
}
}
现在你可以编码和解码了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) }
Swift 5.1 及更高版本的明智替代方案是 属性 包装器
@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)
}
}
并用 @CodableColor
标记 属性
struct Task: Codable {
var content: String
var deadline: Date
@CodableColor var color: UIColor
...
}
我用UIColor
子class
final class Color: UIColor, Decodable {
convenience init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let hexString = try container.decode(String.self)
self.init(hex: hexString)
}
}
因此,不需要每个class或结构来实现Decodable
协议的功能。在我看来,这是最方便的方法,尤其是当一个 class 或结构中可以有多个颜色参数时。
如果有必要,您可以以相同的方式实现 Encodable
。
我用允许自动符合可编码的自定义 class 解决了这个问题。这是有益的,因为它可以防止将自定义一致性写入可编码。它还可以更轻松地使用 UIColor 和 CGColor
class Color:Codable{
private var _green:CGFloat
private var _blue:CGFloat
private var _red:CGFloat
private var alpha:CGFloat
init(color:UIColor) {
color.getRed(&_red, green: &_green, blue: &_blue, alpha: &alpha)
}
var color:UIColor{
get{
return UIColor(red: _red, green: _green, blue: _blue, alpha: alpha)
}
set{
newValue.getRed(&_red, green:&_green, blue: &_blue, alpha:&alpha)
}
}
var cgColor:CGColor{
get{
return color.cgColor
}
set{
UIColor(cgColor: newValue).getRed(&_red, green:&_green, blue: &_blue, alpha:&alpha)
}
}
}
这是一个解决方案 which I've published as a GitHub gist,它适用于任何颜色 space:
/// Allows you to use Swift encoders and decoders to process UIColor
public struct CodableColor {
/// The color to be (en/de)coded
let color: UIColor
}
extension CodableColor: Encodable {
public func encode(to encoder: Encoder) throws {
let nsCoder = NSKeyedArchiver(requiringSecureCoding: true)
color.encode(with: nsCoder)
var container = encoder.unkeyedContainer()
try container.encode(nsCoder.encodedData)
}
}
extension CodableColor: Decodable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
let decodedData = try container.decode(Data.self)
let nsCoder = try NSKeyedUnarchiver(forReadingFrom: decodedData)
self.color = try UIColor(coder: nsCoder).unwrappedOrThrow()
// `unwrappedOrThrow()` is from OptionalTools: https://github.com/RougeWare/Swift-Optional-Tools
// You can use this if you don't want to use OptionalTools:
/*
guard let color = UIColor(coder: nsCoder) else {
struct UnexpectedlyFoundNilError: Error {}
throw UnexpectedlyFoundNilError()
}
self.color = color
*/
}
}
public extension UIColor {
func codable() -> CodableColor {
return CodableColor(color: self)
}
}
比较好用:
let color = UIColor.label
let encoder = JSONEncoder()
let encodedData = try encoder.encode(color.codable())
let decoder = JSONDecoder()
let decodedColor = try decoder.decode(CodableColor.self, from: encodedData).color
当然,您也可以将其用作任何其他 Swift 可编码,例如在具有自动合成可编码一致性的结构中:
struct Foo: Codable {
let color: CodableColor
init(color: UIColor) {
self.color = CodableColor(color: color)
}
}
let fooInstance = Foo(color: .systemPurple)
let encoder = JSONEncoder()
let encodedData = try encoder.encode(fooInstance)
let decoder = JSONDecoder()
let decodedFoo = try decoder.decode(Foo.self, from: encodedData)
这也适用于 NSColor
。
我们可以制作 UIColor 及其所有后代 Codable
。
import UIKit
extension Decodable where Self: UIColor {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let components = try container.decode([CGFloat].self)
self = Self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
}
}
extension Encodable where Self: UIColor {
public func encode(to encoder: Encoder) throws {
var r, g, b, a: CGFloat
(r, g, b, a) = (0, 0, 0, 0)
var container = encoder.singleValueContainer()
self.getRed(&r, green: &g, blue: &b, alpha: &a)
try container.encode([r,g,b,a])
}
}
extension UIColor: Codable { }
检查一下
import XCTest
class ColorDescendant: UIColor { }
let testColor = ColorDescendant.green
class CodingTextCase: XCTestCase {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
func testUIColor() throws {
let colorAsJSON = try encoder.encode(UIColor.red)
print(String(data: colorAsJSON, encoding: .utf8)!)
let uiColor = try? decoder.decode(UIColor.self, from: colorAsJSON)
XCTAssertEqual(uiColor!, UIColor.red)
}
func testUIColorDescendant() throws {
let colorAsJSON = try encoder.encode(testColor)
print(String(data: colorAsJSON, encoding: .utf8)!)
let uiColor = try? decoder.decode(ColorDescendant.self, from: colorAsJSON)
XCTAssertEqual(uiColor!, testColor)
}
}
CodingTextCase.defaultTestSuite.run()
此解决方案仅需要 9 个字节用于数据存储,而 将需要大约 500 个字节。
struct Task: Codable {
var content: String
var deadline: Date
var color: UIColor
...
}
有警告说 "Type 'Task' does not conform to protocol 'Decodable'" 和 "Type 'Task' does not conform to protocol 'Encodable'"。搜索了一下,发现这是因为UIColor不符合Codable。但我不知道如何解决这个问题。所以...
如何使UIColor Codable?
如果您只关心 4 个颜色分量,这是一个使用包装器结构的简单解决方案
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)
}
}
在这种情况下,您必须编写一个自定义初始化程序来将 4 个颜色分量从 Color
转换为 UIColor
,反之亦然。
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)
}
}
现在你可以编码和解码了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) }
Swift 5.1 及更高版本的明智替代方案是 属性 包装器
@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)
}
}
并用 @CodableColor
struct Task: Codable {
var content: String
var deadline: Date
@CodableColor var color: UIColor
...
}
我用UIColor
子class
final class Color: UIColor, Decodable {
convenience init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let hexString = try container.decode(String.self)
self.init(hex: hexString)
}
}
因此,不需要每个class或结构来实现Decodable
协议的功能。在我看来,这是最方便的方法,尤其是当一个 class 或结构中可以有多个颜色参数时。
如果有必要,您可以以相同的方式实现 Encodable
。
我用允许自动符合可编码的自定义 class 解决了这个问题。这是有益的,因为它可以防止将自定义一致性写入可编码。它还可以更轻松地使用 UIColor 和 CGColor
class Color:Codable{
private var _green:CGFloat
private var _blue:CGFloat
private var _red:CGFloat
private var alpha:CGFloat
init(color:UIColor) {
color.getRed(&_red, green: &_green, blue: &_blue, alpha: &alpha)
}
var color:UIColor{
get{
return UIColor(red: _red, green: _green, blue: _blue, alpha: alpha)
}
set{
newValue.getRed(&_red, green:&_green, blue: &_blue, alpha:&alpha)
}
}
var cgColor:CGColor{
get{
return color.cgColor
}
set{
UIColor(cgColor: newValue).getRed(&_red, green:&_green, blue: &_blue, alpha:&alpha)
}
}
}
这是一个解决方案 which I've published as a GitHub gist,它适用于任何颜色 space:
/// Allows you to use Swift encoders and decoders to process UIColor
public struct CodableColor {
/// The color to be (en/de)coded
let color: UIColor
}
extension CodableColor: Encodable {
public func encode(to encoder: Encoder) throws {
let nsCoder = NSKeyedArchiver(requiringSecureCoding: true)
color.encode(with: nsCoder)
var container = encoder.unkeyedContainer()
try container.encode(nsCoder.encodedData)
}
}
extension CodableColor: Decodable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
let decodedData = try container.decode(Data.self)
let nsCoder = try NSKeyedUnarchiver(forReadingFrom: decodedData)
self.color = try UIColor(coder: nsCoder).unwrappedOrThrow()
// `unwrappedOrThrow()` is from OptionalTools: https://github.com/RougeWare/Swift-Optional-Tools
// You can use this if you don't want to use OptionalTools:
/*
guard let color = UIColor(coder: nsCoder) else {
struct UnexpectedlyFoundNilError: Error {}
throw UnexpectedlyFoundNilError()
}
self.color = color
*/
}
}
public extension UIColor {
func codable() -> CodableColor {
return CodableColor(color: self)
}
}
比较好用:
let color = UIColor.label
let encoder = JSONEncoder()
let encodedData = try encoder.encode(color.codable())
let decoder = JSONDecoder()
let decodedColor = try decoder.decode(CodableColor.self, from: encodedData).color
当然,您也可以将其用作任何其他 Swift 可编码,例如在具有自动合成可编码一致性的结构中:
struct Foo: Codable {
let color: CodableColor
init(color: UIColor) {
self.color = CodableColor(color: color)
}
}
let fooInstance = Foo(color: .systemPurple)
let encoder = JSONEncoder()
let encodedData = try encoder.encode(fooInstance)
let decoder = JSONDecoder()
let decodedFoo = try decoder.decode(Foo.self, from: encodedData)
这也适用于 NSColor
。
我们可以制作 UIColor 及其所有后代 Codable
。
import UIKit
extension Decodable where Self: UIColor {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let components = try container.decode([CGFloat].self)
self = Self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
}
}
extension Encodable where Self: UIColor {
public func encode(to encoder: Encoder) throws {
var r, g, b, a: CGFloat
(r, g, b, a) = (0, 0, 0, 0)
var container = encoder.singleValueContainer()
self.getRed(&r, green: &g, blue: &b, alpha: &a)
try container.encode([r,g,b,a])
}
}
extension UIColor: Codable { }
检查一下
import XCTest
class ColorDescendant: UIColor { }
let testColor = ColorDescendant.green
class CodingTextCase: XCTestCase {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
func testUIColor() throws {
let colorAsJSON = try encoder.encode(UIColor.red)
print(String(data: colorAsJSON, encoding: .utf8)!)
let uiColor = try? decoder.decode(UIColor.self, from: colorAsJSON)
XCTAssertEqual(uiColor!, UIColor.red)
}
func testUIColorDescendant() throws {
let colorAsJSON = try encoder.encode(testColor)
print(String(data: colorAsJSON, encoding: .utf8)!)
let uiColor = try? decoder.decode(ColorDescendant.self, from: colorAsJSON)
XCTAssertEqual(uiColor!, testColor)
}
}
CodingTextCase.defaultTestSuite.run()
此解决方案仅需要 9 个字节用于数据存储,而