How to align UIImage with Codable?

Swift 4 has Codable , and it's awesome. But UIImage does not match it by default. How can we do this?

I tried with singleValueContainer and unkeyedContainer

 extension UIImage: Codable { // 'required' initializer must be declared directly in class 'UIImage' (not in an extension) public required init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let data = try container.decode(Data.self) guard let image = UIImage(data: data) else { throw MyError.decodingFailed } // A non-failable initializer cannot delegate to failable initializer 'init(data:)' written with 'init?' self.init(data: data) } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() guard let data = UIImagePNGRepresentation(self) else { return } try container.encode(data) } } 

I get 2 errors

  • 'required' initializer must be declared directly in the 'UIImage' class (not in extension)
  • An initializer without failure cannot delegate to the initializer of the initializer the init (data :) ', written using' init? ''

The workaround is to use a wrapper. But are there any other ways?

+14
source share
4 answers

Solution: Minimize your own wrapper class corresponding to Codable.

One solution, since there are no extensions for UIImage , is to wrap the image in a new native class. Otherwise, your attempt is mostly direct. I saw that this was perfectly done in the Hyper Interactive caching environment, which is called, well, Cache .

Although you need to visit the library to delve deeper into dependencies, you can get an idea of ​​their ImageWrapper class, which is built for use as follows:

 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 shell 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 would love to hear what you end up using.

UPDATE: it turns out that the OP wrote the code I was referring to (Swift 4.0 update for Cache) to solve this problem. Of course, the code deserves to be here, but I will also leave my words unedited for the dramatic irony of it all. :)

+13
source

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

 enum ImageEncodingQuality: CGFloat { case png = 0 case jpegLow = 0.2 case jpegMid = 0.5 case jpegHigh = 0.75 } extension KeyedEncodingContainer { mutating func encode(_ value: UIImage, forKey key: KeyedEncodingContainer.Key, quality: ImageEncodingQuality = .png) throws { var imageData: Data! if quality == .png { imageData = value.pngData() } else { imageData = value.jpegData(compressionQuality: quality.rawValue) } 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 a 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 this method of making Codable for any type of class

+10
source

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

To convert a UIImage to a 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 a 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) 
+7
source

Actually, the easiest way is to simply do this 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)! 
+4
source

Source: https://habr.com/ru/post/1271760/


All Articles