Swift 4 a Codable et c'est génial. Mais UIImage
ne s'y conforme pas par défaut. Comment pouvons-nous faire cela?
J'ai essayé avec singleValueContainer
et 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)
}
}
Je reçois 2 erreurs
Une solution de contournement consiste à utiliser le wrapper. Mais existe-t-il d'autres moyens?
Une solution, étant donné que les extensions de UIImage
sont sorties, consiste à encapsuler l'image dans une nouvelle classe que vous possédez. Sinon, votre tentative est simple. J'ai vu cela fait magnifiquement dans un cadre de mise en cache par Hyper Interactive appelé, bien, Cache .
Bien que vous deviez visiter la bibliothèque pour explorer les dépendances, vous pouvez vous faire une idée en regardant leur classe ImageWrapper
, qui est conçue pour être utilisée comme suit:
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
// 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)
}
}
J'adorerais entendre ce que vous finissez par utiliser.
MISE À JOUR: Il s'avère l'OP a écrit le code que j'ai référencé (la mise à jour Swift 4.0 vers Cache) pour résoudre le problème. Le code mérite d'être ici, bien sûr, mais je laisserai également mes mots inédits pour l'ironie dramatique de tout cela. :)
Vous pouvez utiliser une solution très élégante en utilisant l'extension pour les classes KeyedDecodingContainer
et 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
}
}
}
Voici un exemple d'utilisation:
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: Vous pouvez utiliser cette méthode pour adopter Codable
à n'importe quel type de classe
Une façon de passer un UIImage est de le convertir en quelque chose qui est conforme à Codable, comme String.
Pour convertir l'UIImage en chaîne dans func encode(to encoder: Encoder) throws
:
let imageData: Data = UIImagePNGRepresentation(image)!
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
try container.encode(strBase64, forKey: .image)
Pour reconvertir la chaîne en UIImage dans 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)
Le moyen le plus simple est de faire simplement Data
au lieu de UIImage
:
public struct SomeImage: Codable {
public let photo: Data
public init(photo: UIImage) {
self.photo = photo.pngData()!
}
}
Désérialiser:
UIImage(data: instanceOfSomeImage.photo)!