如何使 UIImage 符合 Codable?
How to conform UIImage to Codable?
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 个错误
- 'required' 初始化器必须直接在 class 'UIImage' 中声明(不在扩展中)
- 不可失败的初始化器不能委托给用'init?'
编写的可失败的初始化器'init(data:)'
解决方法是使用包装器。但是还有其他方法吗?
解决方案:推出符合 Codable 的自己的包装器 class。
一个解决方案,因为 UIImage
的扩展已经结束,将图像包装在您拥有的新 class 中。否则,您的尝试基本上是直接的。我看到 Hyper Interactive 在一个名为 Cache.
的缓存框架中完美地完成了这项工作
虽然您需要访问该库以深入了解依赖项,但您可以通过查看它们的 ImageWrapper
class 来获得灵感,它们的构建目的如下:
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
这是他们的包装纸 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)
}
}
我很想听听你最终使用了什么。
更新:结果是 OP 编写了我引用的代码(Swift 4.0 缓存更新)解决问题。当然,代码应该放在上面,但我也不会编辑我的话,因为这一切都具有戏剧性的讽刺意味。 :)
传递 UIImage 的一种方法是将其转换为符合 Codable 的内容,例如 String。
将UIImage转换成String里面func encode(to encoder: Encoder) throws
:
let imageData: Data = UIImagePNGRepresentation(image)!
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
try container.encode(strBase64, forKey: .image)
在required init(from decoder: Decoder) throws
中将String转换回UIImage:
let strBase64: String = try values.decode(String.self, forKey: .image)
let dataDecoded: Data = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)!
image = UIImage(data: dataDecoded)
您可以使用非常优雅的解决方案,使用扩展名 KeyedDecodingContainer
和 KeyedEncodingContainer
classes:
enum ImageEncodingQuality {
case png
case jpeg(quality: CGFloat)
}
extension KeyedEncodingContainer {
mutating func encode(
_ value: UIImage,
forKey key: KeyedEncodingContainer.Key,
quality: ImageEncodingQuality = .png
) throws {
let imageData: Data?
switch quality {
case .png:
imageData = value.pngData()
case .jpeg(let quality):
imageData = value.jpegData(compressionQuality: quality)
}
guard let data = imageData else {
throw EncodingError.invalidValue(
value,
EncodingError.Context(codingPath: [key], debugDescription: "Failed convert UIImage to data")
)
}
try encode(data, forKey: key)
}
}
extension KeyedDecodingContainer {
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 DecodingError.dataCorrupted(
DecodingError.Context(codingPath: [key], debugDescription: "Failed load UIImage from decoded data")
)
}
}
}
PS: 您可以使用这种方式将 Codable
应用到任何 class 类型
正确地,最简单的方法就是将其设为 Data
而不是 UIImage
:
public struct SomeImage: Codable {
public let photo: Data
public init(photo: UIImage) {
self.photo = photo.pngData()!
}
}
反序列化图像:
UIImage(data: instanceOfSomeImage.photo)!
还有一个在图像上使用惰性变量的简单解决方案:
var mainImageData: Data {
didSet { _ = mainImage }
}
lazy var mainImage: UIImage = {
UIImage(data: mainImageData)!
}()
这样,在对象初始化和分配给 mainImageData
期间,它的 didSet
将启动,然后启动 UIImage
.
的初始化
由于 UIImage
初始化占用大量资源,我们将它们结合在一起。
只是要注意,整个初始化都会在后台线程进行。
现有答案似乎都不正确。如果将反序列化后的图像与原始图像进行比较,您会发现它们可能在任何意义上都不相等。这是因为答案都在丢弃 scale 信息。
您必须对图像 scale
及其 pngData()
进行编码。然后,当您解码 UIImage 时,通过调用 init(data:scale:)
.
将数据与比例组合起来
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 个错误
- 'required' 初始化器必须直接在 class 'UIImage' 中声明(不在扩展中)
- 不可失败的初始化器不能委托给用'init?' 编写的可失败的初始化器'init(data:)'
解决方法是使用包装器。但是还有其他方法吗?
解决方案:推出符合 Codable 的自己的包装器 class。
一个解决方案,因为 UIImage
的扩展已经结束,将图像包装在您拥有的新 class 中。否则,您的尝试基本上是直接的。我看到 Hyper Interactive 在一个名为 Cache.
虽然您需要访问该库以深入了解依赖项,但您可以通过查看它们的 ImageWrapper
class 来获得灵感,它们的构建目的如下:
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
这是他们的包装纸 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)
}
}
我很想听听你最终使用了什么。
更新:结果是 OP 编写了我引用的代码(Swift 4.0 缓存更新)解决问题。当然,代码应该放在上面,但我也不会编辑我的话,因为这一切都具有戏剧性的讽刺意味。 :)
传递 UIImage 的一种方法是将其转换为符合 Codable 的内容,例如 String。
将UIImage转换成String里面func encode(to encoder: Encoder) throws
:
let imageData: Data = UIImagePNGRepresentation(image)!
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
try container.encode(strBase64, forKey: .image)
在required init(from decoder: Decoder) throws
中将String转换回UIImage:
let strBase64: String = try values.decode(String.self, forKey: .image)
let dataDecoded: Data = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)!
image = UIImage(data: dataDecoded)
您可以使用非常优雅的解决方案,使用扩展名 KeyedDecodingContainer
和 KeyedEncodingContainer
classes:
enum ImageEncodingQuality {
case png
case jpeg(quality: CGFloat)
}
extension KeyedEncodingContainer {
mutating func encode(
_ value: UIImage,
forKey key: KeyedEncodingContainer.Key,
quality: ImageEncodingQuality = .png
) throws {
let imageData: Data?
switch quality {
case .png:
imageData = value.pngData()
case .jpeg(let quality):
imageData = value.jpegData(compressionQuality: quality)
}
guard let data = imageData else {
throw EncodingError.invalidValue(
value,
EncodingError.Context(codingPath: [key], debugDescription: "Failed convert UIImage to data")
)
}
try encode(data, forKey: key)
}
}
extension KeyedDecodingContainer {
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 DecodingError.dataCorrupted(
DecodingError.Context(codingPath: [key], debugDescription: "Failed load UIImage from decoded data")
)
}
}
}
PS: 您可以使用这种方式将 Codable
应用到任何 class 类型
正确地,最简单的方法就是将其设为 Data
而不是 UIImage
:
public struct SomeImage: Codable {
public let photo: Data
public init(photo: UIImage) {
self.photo = photo.pngData()!
}
}
反序列化图像:
UIImage(data: instanceOfSomeImage.photo)!
还有一个在图像上使用惰性变量的简单解决方案:
var mainImageData: Data {
didSet { _ = mainImage }
}
lazy var mainImage: UIImage = {
UIImage(data: mainImageData)!
}()
这样,在对象初始化和分配给 mainImageData
期间,它的 didSet
将启动,然后启动 UIImage
.
由于 UIImage
初始化占用大量资源,我们将它们结合在一起。
只是要注意,整个初始化都会在后台线程进行。
现有答案似乎都不正确。如果将反序列化后的图像与原始图像进行比较,您会发现它们可能在任何意义上都不相等。这是因为答案都在丢弃 scale 信息。
您必须对图像 scale
及其 pngData()
进行编码。然后,当您解码 UIImage 时,通过调用 init(data:scale:)
.