swift 4具有编码,而且很棒。但是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个错误
- '必需的'初始化器必须在类'uiimage'中直接声明(不在扩展中)
- 一个非易货初始器不能将失败的初始化器'init(数据:)'写成'init?' 编写
解决方法是使用包装器。但是还有其他方法吗?
正确的方法是使属性Data
而不是这样的UIImage
:
public struct SomeImage: Codable {
public let photo: Data
public init(photo: UIImage) {
self.photo = photo.pngData()!
}
}
值得注意的图像:
UIImage(data: instanceOfSomeImage.photo)!
解决方案:滚动自己的包装类别符合代码。
一个解决方案,因为对UIImage
的扩展已输出,就是将图像包裹在您拥有的新类中。否则,您的尝试基本上是直接的。我在超级互动的缓存框架中看到了这一点,嗯,cache。
尽管您需要访问图书馆才能深入研究依赖项,但您可以从查看其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)
}
}
我很想听听您最终使用的内容。
更新: OP写下了我引用的代码(Swift 4.0更新了CACHE)来解决问题。当然,该代码值得在这里出现,但是我也为此而留下的言语却没有编辑。:)
您可以使用KeyedDecodingContainer
和KeyedEncodingContainer
类的扩展名使用非常优雅的解决方案:
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
采用到任何类型类型
传递uiimage的一种方法是将其转换为符合编码(例如字符串)的东西。
将UIIMAGE转换为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
内部的UIIMAGE:
let strBase64: String = try values.decode(String.self, forKey: .image)
let dataDecoded: Data = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)!
image = UIImage(data: dataDecoded)
现有答案似乎都是不正确的。如果将避风式图像与原件进行比较,您会发现它们在任何意义上都可能并不相等。这是因为答案都丢弃了 scale 信息。
您必须编码图像scale
及其pngData()
。然后,当您解码UIIMAGE时,通过调用init(data:scale:)
。
最好的解决方案是使用自定义属性包装器
- 该物业仍然可变
- 无需代码交替,只需添加
@CodableImage
前缀
用法
class MyClass: Codable {
@CodableImage var backgroundImage1: UIImage?
@CodableImage var backgroundImage2: UIImage?
@CodableImage var backgroundImage3: UIImage?
将此代码添加到项目:
@propertyWrapper
public struct CodableImage: Codable {
var image: UIImage?
public enum CodingKeys: String, CodingKey {
case image
}
public init(image: UIImage?) {
self.image = image
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let b = try? container.decodeNil(forKey: CodingKeys.image), b {
self.image = nil
} else {
let data = try container.decode(Data.self, forKey: CodingKeys.image)
guard let image = UIImage(data: data) else {
throw DecodingError.dataCorruptedError(forKey: CodingKeys.image, in: container, debugDescription: "Decoding image failed")
}
self.image = image
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let data = image?.pngData() {
try container.encode(data, forKey: CodingKeys.image)
} else {
try container.encodeNil(forKey: CodingKeys.image)
}
}
public init(wrappedValue: UIImage?) {
self.init(image: wrappedValue)
}
public var wrappedValue: UIImage? {
get { image }
set {
image = newValue
}
}
}
也有一个简单的解决方案使用图像上的懒惰var:
var mainImageData: Data {
didSet { _ = mainImage }
}
lazy var mainImage: UIImage = {
UIImage(data: mainImageData)!
}()
以这种方式,在对象初始化和分配给mainImageData
期间,其didSet
将启动,然后启动UIImage
的初始化。
由于UIImage
初始化资源很重,因此我们将它们融合在一起。只要注意整个初始化将在背景线程上。
swift 5.4
// MARK: - ImageWrapper
public struct ImageWrapper: Codable {
// Enums
public enum CodingKeys: String, CodingKey {
case image
}
// Properties
public let image: UIImage
// Inits
public init(image: UIImage) {
self.image = image
}
// Methods
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let data = try container.decode(Data.self, forKey: CodingKeys.image)
if let image = UIImage(data: data) {
self.image = image
} else {
// Error Decode
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let imageData: Data = image.pngData() {
try container.encode(imageData, forKey: .image)
} else {
// Error Encode
}
}
}