【问题标题】:Make UIColor Codable使 UIColor 可编码
【发布时间】:2018-11-28 09:39:48
【问题描述】:
struct Task: Codable {
    var content: String
    var deadline: Date
    var color: UIColor
...
}

有警告说“类型'Task'不符合协议'Decodable'”和“类型'Task'不符合协议'Encodable'”。查了一下发现这是因为UIColor不符合Codable。但我不知道如何解决这个问题。所以...

如何使 UIColor 可编码?

【问题讨论】:

  • 我认为您不需要 UIColor 可编码。为什么不存储您拥有的颜色数据并从方法返回颜色对象。
  • @Desdenova 但是我需要保存数据
  • @vadian 我以前试过。它对我不起作用。
  • 经过大量搜索后,我发现此链接对于使符合 NSCoding 的类型符合 Codable(在您的情况下为 UIColor)非常有用。它解释了它并提供了像魅力一样工作的示例代码。我已经使用了 UIColor 和 CGPoint 等类型。 hackingwithswift.com/example-code/language/…

标签: swift4 xcode9


【解决方案1】:

如果您只关心 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
...
}

【讨论】:

  • 哇,我的意思是太好了。将不同的 (Color) 结构值存储为 Task' 的可编码协议的 UIColor 属性是个好主意。
【解决方案2】:

这是一个解决方案which I've published as a GitHub gist,它适用于任何颜色空间中的任何颜色:

/// 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

【讨论】:

  • 非常有帮助。我没有看到方法 codable() 所以代替 color.codable() 我正在使用 CodableColor(color: color)
  • 感谢您提醒我,@ScottCarter!我忘了用扩展方法来完成这个。但是,是的,你是对的;这是在我刚刚进行编辑之前使用它的正确方法。
  • 你知道这是否适用于“动态”颜色吗?那么使用init(dynamicProvider:) 初始化程序创建的颜色呢?
  • @fruitcoder 不知道!我可以稍后测试,但如果您在我开始之前有任何结果要分享,请在此处发布?
  • @BenLeggiero 动态颜色在编码/解码过程中丢失,并且为两个 userInterfaceStyles 返回浅色:) 很明显这不起作用,但很高兴知道
【解决方案3】:

我使用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)
    }
}

因此,不需要每个类或结构都实现Decodable 协议的功能。在我看来,这是最方便的方法,尤其是当一个类或结构中可能有许多颜色参数时。 如果有必要,你可以用同样的方式实现Encodable

【讨论】:

  • 这似乎在 Xcode 10.2 中被破坏了。不确定 Swift 5 是否是原因。有什么想法吗?
  • 我正在使用带有 Swift 5 的 Xcode 11.3.1,这对我来说似乎也很糟糕 - 当我尝试解码时,我得到了 DecodingError.valueNotFound
  • 由于类集群,这不起作用。 UIColor 实际上只是一种伪装,代表了我们无法使用的许多不同的内部类。
【解决方案4】:

我使用允许自动符合可编码的自定义类解决了这个问题。这是有益的,因为它可以防止将自定义一致性写入可编码。它还可以更轻松地使用 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)
    }
}

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多