4
votes

I'm trying to implement Codable for a class which contains a NSAttributedString, but I get errors at compile time:

try container.encode(str, forKey: .str)

error ambiguous reference to member 'encode(_:forKey:)'

and

str = try container.decode(NSMutableAttributedString.self, forKey: .str)

error: No 'decode' candidates produce the expected contextual type 'NSAttributedString'

I can get around it by using the NSData from the string, but thought this should work I would have thought

class Text : Codable {
    var str : NSAttributedString

    enum CodingKeys: String, CodingKey {
        case str
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(str, forKey: .str). <-- error ambiguous reference to member 'encode(_:forKey:)'
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        str = try container.decode(NSMutableAttributedString.self, forKey: .str)  <-- error: No 'decode' candidates produce the expected contextual type 'NSAttributedString'
    }
}
2
NS(Mutable)AttributedString does not conform to Codablevadian

2 Answers

5
votes

NSAttributedString doesn’t conform to Codable, so you can’t do this directly.

If you just want to store the data you can implement a simple wrapper around your attributed string that conforms to Codable and use that in your data class. This is rather easy since you can convert the attributed string to Data using data(from:documentAttributes) when encoding. When decoding you first read the data and then initialize your attributed string using init(data:options:documentAttributes:). It supports various data formats including HTML, RTF and Microsoft Word.

Resist the temptation to add a Codable conformance to NSAttributedString in an extension. This will cause trouble when Apple adds this conformance or you add another library that does this.

If, on the other hand, you need this to communicate with a server or some other program, you need to exactly match the required data format. If you only need a plain string you probably should not use NSAttributedString at all. If it is some other format like markdown you could implement a wrapper that does the necessary transforms.

0
votes

If you want to decode&encode the textual content only and convert it back from/to NS(Mutual)AttributedString you can try something like that:

class Text : Codable {
var str : NSMutableAttributedString?

enum CodingKeys: String, CodingKey {
    case str
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try? container.encode(str?.string, forKey: .str)
}

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    if let content =  try? container.decode(String.self, forKey: .str){
        str = NSMutableAttributedString(string: content)
        // ADD any attributes here if required...
    }
  }
}