Hello everyone, Is there a better solution than my approach out there to convert an image to data and back?
@Model
class User {
var name: String
@Attribute(.externalStorage) var image: Data?
var createdAt: Date
init(name: String, image: Data, createdAt: Date = .now) {
self.name = name
self.image = image
self.createdAt = createdAt
}
}
if let selectedPhotoData = imageData,
let uiImage = UIImage(data: selectedPhotoData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 300, height: 300, alignment: .center)
.clipShape(Circle())
}
.task(id: selectedPhoto) {
if let data = try? await selectedPhoto?.loadTransferable(type: Data.self) {
imageData = data
}
}
No there isn't I'm afraid. You have to convert to Data.
I would caution against using loadTransferable
as the Transferable
type is intended to short term storage (copy and paste, drag and drop...etc), and there is no guarantee how that API may change in the future. If you're storing data for extended periods, you should try and ensure consistency of the kind of data stored.
In our app, we have a generic SwiftData entity for storing an image. Any other entity that wants to store an image can maintain a relationship to one of these entities. It saves us having to add logic everywhere we want to store images. The generic image entity takes an NSImage
, UIImage
or CGImage
and grabs the data in PNG format and stores it in the model. Then extensions on those types to initialise them directly from the SwiftData entity. It makes things a little cleaner, but it's essentially the same thing, especially considering we store images in multiple places in our model.
An extension on NSImage
to get the PNG data.
extension NSImage {
/// Returns the PNG data for the `NSImage` as a Data object.
///
/// - Returns: A data object containing the PNG data for the image, or nil
/// in the event of failure.
///
public func pngData() -> Data? {
guard let cgImage = self.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
return nil
}
let bitmapRepresentation = NSBitmapImageRep(cgImage: cgImage)
return bitmapRepresentation.representation(using: .png, properties: [:])
}
}
The basic data model for our image. We store a type and some data which is the PNG data.
@Model
final class ImageModel {
/// The type of the image.
///
/// We use different images for different things, so storing an image type
/// lets us differentiate use.
///
var type: ImageType = ImageType.unknown
/// The image data, stored as a PNG.
///
/// It is tagged with externalStorage to allow the large binary data to be stored
/// externally.
///
@Attribute(.externalStorage) var pngData: Data? = nil
/// Initialize the image model.
///
/// - Parameters:
/// - type: The type of image the image model represents.
/// - pngData: The image data in png format.
///
init(type: ImageType, pngData: Data) {
self.type = type
self.pngData = pngData
}
#if canImport(AppKit)
import AppKit
/// Initialize the image model from an `NSImage`.
///
/// - Parameters:
/// - type: The type of the image the image model represents.
/// - image: The `NSImage` to store in the image model.
///
convenience init(type: ImageType, image: NSImage) throws {
guard let pngData = image.pngData() else {
throw GenericError.failed("Unable to get PNG data for image")
}
self.init(type: type, pngData: pngData)
}
#elseif canImport(UIKit)
import UIKit
/// Initialize the image model from a `UIImage`.
///
/// - Parameters:
/// - type: The type of the image the image model represents.
/// - image: The `UIImage` to store in the image model.
///
convenience init(type: ImageType, image: UIImage) throws {
guard let pngData = image.pngData() else {
throw GenericError.failed("Unable to get PNG data for image")
}
self.init(type: type, pngData: pngData)
}
#endif
}
Given an ImageModel, initialises a UIImage
or NSImage
from the data stored in the model.
#if canImport(UIKit)
import UIKit
extension UIImage {
/// Initialize a new `UIImage` using data from an `ImageModel`.
///
/// - Parameters:
/// - model: The image model to load the image from.
///
convenience init?(loadingDataFrom model: ImageModel) {
guard let data = model.pngData,
data.isEmpty == false
else {
return nil
}
self.init(data: data)
}
}
#elseif canImport(AppKit)
import AppKit
extension NSImage {
/// Initialize a new `NSImage` using data from an `ImageModel`.
///
/// - Parameters:
/// - model: The image model to load the image from.
///
convenience init?(loadingDataFrom model: ImageModel) {
guard let data = model.pngData,
data.isEmpty == false
else {
return nil
}
self.init(data: data)
}
}
#endif