制作UIColor Codable

Lam*_*lex 7 swift4 xcode9

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种颜色分量转换ColorUIColor,反之亦然.

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)


Sup*_*tar 8

这是我作为 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

  • 你知道这是否适用于“动态”颜色?那么使用 `init(dynamicProvider:)` 初始化器创建的颜色呢? (2认同)

Pau*_*l B 7

我们可以制作 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 个字节。


Nik*_*ner 5

我使用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用同样的方式来实现。