如何使UIIMAGE与编码相符



swift 4具有编码,而且很棒。但是UIImage默认情况下不符合它。我们该怎么做?

我尝试了singleValueContainerunkeyedContainer

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'中直接声明(不在扩展中)
  2. 一个非易货初始器不能将失败的初始化器'init(数据:)'写成'init?'
  3. 编写

解决方法是使用包装器。但是还有其他方法吗?

正确的方法是使属性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)来解决问题。当然,该代码值得在这里出现,但是我也为此而留下的言语却没有编辑。:)

您可以使用KeyedDecodingContainerKeyedEncodingContainer类的扩展名使用非常优雅的解决方案:

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
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新