How to conform UIImage to Codable?

后端 未结 4 833
长发绾君心
长发绾君心 2020-12-09 02:28

Swift 4 has Codable and it\'s awesome. But UIImage does not conform to it by default. How can we do that?

I tried with singleValueContainer

相关标签:
4条回答
  • A solution: roll your own wrapper class conforming to Codable.

    One solution, since extensions to UIImage are out, is to wrap the image in a new class you own. Otherwise, your attempt is basically straight on. I saw this done beautifully in a caching framework by Hyper Interactive called, well, Cache.

    Though you'll need to visit the library to drill down into the dependencies, you can get the idea from looking at their ImageWrapper class, which is built to be used like so:

    let wrapper = ImageWrapper(image: starIconImage)
    try? theCache.setObject(wrapper, forKey: "star")
    
    let iconWrapper = try? theCache.object(ofType: ImageWrapper.self, forKey: "star")
    let icon = iconWrapper.image
    

    Here is their wrapper class:

    // Swift 4.0
    public struct ImageWrapper: Codable {
      public let image: Image
    
      public enum CodingKeys: String, CodingKey {
        case image
      }
    
      // Image is a standard UI/NSImage conditional typealias
      public init(image: Image) {
        self.image = image
      }
    
      public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let data = try container.decode(Data.self, forKey: CodingKeys.image)
        guard let image = Image(data: data) else {
          throw StorageError.decodingFailed
        }
    
        self.image = image
      }
    
      // cache_toData() wraps UIImagePNG/JPEGRepresentation around some conditional logic with some whipped cream and sprinkles.
      public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        guard let data = image.cache_toData() else {
            throw StorageError.encodingFailed
        }
    
        try container.encode(data, forKey: CodingKeys.image)
      }
    }
    

    I'd love to hear what you end up using.

    UPDATE: It turns out the OP wrote the code that I referenced (the Swift 4.0 update to Cache) to solve the problem. The code deserves to be up here, of course, but I'll also leave my words unedited for the dramatic irony of it all. :)

    0 讨论(0)
  • 2020-12-09 03:03

    One way to pass an UIImage is to convert it to something that conforms to Codable, like String.

    To convert the UIImage to String inside func encode(to encoder: Encoder) throws:

    let imageData: Data = UIImagePNGRepresentation(image)!
    let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
    try container.encode(strBase64, forKey: .image)
    

    To convert the String back to UIImage inside required init(from decoder: Decoder) throws:

    let strBase64: String = try values.decode(String.self, forKey: .image)
    let dataDecoded: Data = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)!
    image = UIImage(data: dataDecoded)
    
    0 讨论(0)
  • 2020-12-09 03:06

    Properly most easiest way is to just make it Data instead of UIImage:

    public struct SomeImage: Codable {
    
        public let photo: Data
    
        public init(photo: UIImage) {
            self.photo = photo.pngData()!
        }
    }
    

    Deserialize:

    UIImage(data: instanceOfSomeImage.photo)!
    
    0 讨论(0)
  • 2020-12-09 03:11

    You can use very elegant solution using extension for KeyedDecodingContainer and KeyedEncodingContainer classes:

    enum ImageEncodingQuality {
        case png
        case jpeg(quality: Double)
    }
    
    extension KeyedEncodingContainer {
        
        mutating func encode(_ value: UIImage,
                             forKey key: KeyedEncodingContainer.Key,
                             quality: ImageEncodingQuality = .png) throws {
            var imageData: Data!
            switch quality {
            case .png:
                imageData = value.pngData()
            case .jpeg(let quality)
                imageData = value.jpegData(compressionQuality: quality)
            }
            try encode(imageData, forKey: key)
        }
        
    }
    
    extension KeyedDecodingContainer {
        
        public func decode(_ type: UIImage.Type, forKey key: KeyedDecodingContainer.Key) throws -> UIImage {
            let imageData = try decode(Data.self, forKey: key)
            if let image = UIImage(data: imageData) {
                return image
            } else {
                throw SDKError.imageConversionError
            }
        }
        
    }
    

    Here is an usage example:

    class DocumentScan: Codable {
    
        private enum CodingKeys: String, CodingKey {
            case scanDate
            case image
        }
    
        let scanDate: Date
        let image: UIImage
    
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            scanDate = try container.decode(Date.self, forKey: .scanDate)
            image = try container.decode(UIImage.self, forKey: .image)
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(scanDate, forKey: .scanDate)
            try container.encode(image, forKey: .image, quality: .png)
        }
    }
    

    PS: You can use such way to adopt Codable to any class type

    0 讨论(0)
提交回复
热议问题