struct Task: Codable {
var content: String
var deadline: Date
var color: UIColor
...
}
Run Code Online (Sandbox Code Playgroud)
有警告说"类型'任务'不符合协议'可解码'"和"类型'任务'不符合协议'可编码'".我搜索并发现这是因为UIColor不符合Codable.但我不知道如何解决这个问题.所以...
如何制作UIColor Codable?
vad*_*ian 13
如果您只关心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)
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您必须编写自定义初始值设定项以将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)
}
}
Run Code Online (Sandbox Code Playgroud)
现在你可以编码和解码了 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) }
Run Code Online (Sandbox Code Playgroud)
这是我作为 GitHub 要点发布的解决方案,它适用于任何颜色空间中的任何颜色:
/// 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)
}
}
Run Code Online (Sandbox Code Playgroud)
使用起来相对简单:
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
Run Code Online (Sandbox Code Playgroud)
当然,您也可以将它用作任何其他 Swift 可编码对象,例如在具有自动合成可编码一致性的结构中:
struct Foo: Codable {
let color: CodableColor
init(color: UIColor) {
self.color = CodableColor(color: color)
}
}
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
这也适用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 { }
Run Code Online (Sandbox Code Playgroud)
核实
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()
Run Code Online (Sandbox Code Playgroud)
该解决方案仅需要 9 个字节用于数据存储,而更通用的解决方案将需要大约 500 个字节。
我使用UIColor子类
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)
}
}
Run Code Online (Sandbox Code Playgroud)
因此,不需要每个类或结构都实现Decodable协议的功能。在我看来,这是最方便的方法,特别是当一个类或结构中可以有许多颜色参数时。如果需要的话,也可以Encodable用同样的方式来实现。
| 归档时间: |
|
| 查看次数: |
2766 次 |
| 最近记录: |