Swift 4には Codable があり、素晴らしいです。ただし、UIImage
はデフォルトではそれに準拠していません。どうすればそれができますか?
singleValueContainer
と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)
}
}
2つのエラーが表示されます
回避策は、ラッパーを使用することです。しかし、他の方法はありますか?
解決策の1つは、UIImage
の拡張機能が公開されているため、所有している新しいクラスで画像をラップすることです。そうでなければ、あなたの試みは基本的にまっすぐです。 Cache と呼ばれるHyper Interactiveのキャッシングフレームワークで、これが見事に行われているのを見ました。
ライブラリにアクセスして依存関係をドリルダウンする必要がありますが、次のように使用するために構築されたImageWrapper
クラスを調べることでアイデアを得ることができます。
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)
}
}
私はあなたが最終的に使用するものを聞きたいです。
UPDATE:判明OPはコードを書いた私が参照した(Swift 4.0をCacheにアップデートして問題を解決します。もちろん、コードはここにあるべきですが、すべての劇的な皮肉のために私の言葉は未編集のままにします。
KeyedDecodingContainer
および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
}
}
}
以下に使用例を示します。
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:Codable
を任意のクラス型に採用するために、このような方法を使用できます
UIImageを渡す1つの方法は、StringなどのCodableに準拠するものに変換することです。
func encode(to encoder: Encoder) throws
内でUIImageをStringに変換するには:
_let imageData: Data = UIImagePNGRepresentation(image)!
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
try container.encode(strBase64, forKey: .image)
_
required init(from decoder: Decoder) throws
内で文字列をUIImageに戻すには:
_let strBase64: String = try values.decode(String.self, forKey: .image)
let dataDecoded: Data = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)!
image = UIImage(data: dataDecoded)
_
適切に最も簡単な方法は、Data
の代わりにUIImage
にすることです:
public struct SomeImage: Codable {
public let photo: Data
public init(photo: UIImage) {
self.photo = photo.pngData()!
}
}
逆シリアル化:
UIImage(data: instanceOfSomeImage.photo)!