Как сделать encode и decode для UIImage?


#1

Хочу сохранить данные в архивный JSON файл на диск, но UIImage не подписывается под Codable. Нашел на Stackoverflow хорошее решение, простое и короткое, вполне возможно кому-то еще пригодится, оно в конце кода. Encode с ним сделал, а вот Decode попытался реализовать, но не получается где и как сделать обратную конвертацию из Data в UIImage.

Подскажите, как это можно сделать?

import Foundation
import UIKit

class Bouquet: Codable {
    var name: String
    var image: SomeImage
    
    init(name: String, image: SomeImage) {
        self.name = name
        self.image = image
    }
    
    // MARK: - Saving Data to Archive File===
    
    // 1. Make a path to save and upload data.
    
    static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    static let archiveUrl = documentsDirectory.appendingPathComponent("carts").appendingPathExtension("plist")
    
    // 2. Encoding and saving data.
    static func saveToFile(carts: [Bouquet]) {
        
        let propertyListEncoder = PropertyListEncoder()
        let encodeCarts = try? propertyListEncoder.encode(carts)
        try? encodeCarts?.write(to: archiveUrl, options: .noFileProtection)
        
    }
    // 3. Deconding and load data.
    static func loadFromFile() -> [Bouquet] {
        let propertyListDecoder = PropertyListDecoder()
        
        // My code
        var someImage = SomeImage(photo: ???)
        UIImage(data: someImage.photo)!
        
        var carts = [Bouquet]()
        if let retrivedCartsData = try? Data(contentsOf: archiveUrl), let decodeCarts = try? propertyListDecoder.decode(Array<Bouquet>.self, from: retrivedCartsData) {
            carts = decodeCarts
        }
        return carts
    }
    
}

// Solution to Codable an UIImage

public struct SomeImage: Codable {
    
    public let photo: Data
    
    public init(photo: UIImage) {
        self.photo = photo.pngData()!
        
    }
}

//Deserialize:

UIImage(data: instanceOfSomeImage.photo)!

#2

let image: UIImage = UIImage(data: data)


#3

Сэнкс. И еще вопрос - а где должна быть эта строка в коде, в функции Decode или где-то в другом месте?


#4

где угодно. в тот момент когда вам нужна картинка и вы достаете Data, конвертируете Data в UIImage.


#5

Спасибо, пойду пробовать.


#6

Можно сделать так:

public protocol ImageCodable: Codable {}
extension UIImage: ImageCodable {}

extension ImageCodable where Self: UIImage {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.init(data: try container.decode(Data.self))!
    }
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self.pngData()!)
    }
}

struct User: Codable {
    let name: String
    let photo: UIImage
}
let user = User(name: "Swiftbook", photo:  UIImage(named: "img")!)
let json = try! JSONEncoder().encode(user)

Вообще, хранить картинки в json плохая идея.


#7

Так же как и в базе.
Используйте любую библиотеку для загрузки картинок, практически все умеют их кешировать.


#8

как хранить картинки в БД (Реалм)? base64?


#9

В реалме же там чуть не 16 метров на все про все?)) особо с картинками не разбежишься)) и выше написали, что в бд хранить не стоит

уточнила - 16 метров на файл типа nsdata, не так запомнилось


#10

впервые слышу про 16 метров)
а где хранить если приложение должно работать без сети? в ассетсах?


#11

Все что относится к вашему приложение - в ассетах.
Все что загружает юзер - в Documents, а ссылку в базе.


#12

а если будет задача сделать, например, некий справочник, с кучей картинок, текста и тд., офлайн.
все пихать в ассеты?


#13

да (_____ 20 симолов)


#14

А в чём смысл пихать объекты в базу? Ведь вам нужно всего лишь ссылка на эти объекты, в данном случае имена файлов и всё ок.


#15

Иногда просто надо из Data получить UIImage в Combine :slight_smile:

Пример:
URLSession.shared.dataTaskPublisher(for: url)
.map(.data)
.decode(type: UIImage.self, decoder: JSONDecoder())

Если не использовать Ваш код, то нельзя так сделать так как UIImage не поддерживает Codable :slight_smile:


#16

А если так?

URLSession.shared.dataTaskPublisher(for: url).map(\.data).compactMap(UIImage.init)

#17

Так тоже можно, но не круто :))))


#18

Как раз наоборот, json там нет.


#19

Туплю уже ))) Говорят же в отпуске нельзя работать ))))