web-dev-qa-db-fra.com

Comment conformer UIImage à Codable?

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

  1. l'initialiseur 'requis' doit être déclaré directement dans la classe 'UIImage' (pas dans une extension)
  2. Un initialiseur non disponible ne peut pas déléguer à l'initialiseur disponible 'init (data :)' écrit avec 'init?'

Une solution de contournement consiste à utiliser le wrapper. Mais existe-t-il d'autres moyens?

14
onmyway133

Une solution: lancez votre propre classe wrapper conformément à Codable.

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

Voici leur classe wrapper:

// 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. :)

13
AmitaiB

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

10
Vitaliy Gozhenko

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)
7
oddRaven

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)!
4
J. Doe