Swift Codable protocol… encoding / decoding NSCoding classes
Asked Answered
A

1

7

I have the following struct…

struct Photo: Codable {

    let hasShadow: Bool
    let image: UIImage?

    enum CodingKeys: String, CodingKey {
        case `self`, hasShadow, image
    }

    init(hasShadow: Bool, image: UIImage?) {
        self.hasShadow = hasShadow
        self.image = image
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        hasShadow = try container.decode(Bool.self, forKey: .hasShadow)

        // This fails
        image = try container.decode(UIImage?.self, forKey: .image) 
    }

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

        // This also fails
        try container.encode(image, forKey: .image)
    }
}

Encoding a Photo fails with …

Optional does not conform to Encodable because UIImage does not conform to Encodable

Decoding fails with…

Key not found when expecting non-optional type Optional for coding key \"image\""))

Is there a way to encode Swift objects that include NSObject subclass properties that conform to NSCoding (UIImage, UIColor, etc)?

Aim answered 26/7, 2017 at 8:53 Comment(1)
You have to write custom encode / decode code to archive / unarchive the objects to and from Data. Please read Encoding and Decoding Custom TypesCombustion
A
9

Thanks to @vadian pointing me in the direction of encoding/decoding Data

class Photo: Codable {

    let hasShadow: Bool
    let image: UIImage?

    enum CodingKeys: String, CodingKey {
        case hasShadow, imageData
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        hasShadow = try container.decode(Bool.self, forKey: .hasShadow)

        if let imageData = try container.decodeIfPresent(Data.self, forKey: .imageData) {
            image = NSKeyedUnarchiver.unarchiveObject(with: imageData) as? UIImage
        } else {
            image = nil
        }
    }

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

        if let image = image {
            let imageData = NSKeyedArchiver.archivedData(withRootObject: image)
            try container.encode(imageData, forKey: .imageData)
        }
    }
}
Aim answered 26/7, 2017 at 9:28 Comment(4)
So in the end Codable doesn't really make anything easier, when using "custom types"? :-|Caniff
Well - it allows you to encode / decode non-NSObject subclasses (enums & structs)Aim
@AshleyMills, I am getting this error "Type 'Photo' does not conform to protocol 'Decodable'" while copying this code in my file.Inductive
@GopalDevra Please ask this as another question.Aim

© 2022 - 2024 — McMap. All rights reserved.