Home > OS >  How can I fetch a CoreData Image of type - Binary Data as UIImage in SwiftUI
How can I fetch a CoreData Image of type - Binary Data as UIImage in SwiftUI

Time:01-14

Requirement/Goal: I want to fetch an Image I stored as BinaryData and display it as an Image from CoreData.

I've 4 files:
1. ImagePicker - Uses a button which opens the photo library.
2. DealDetailsView - Image Picker is used here to select the Image and store it in CoreData.
3. DealDetailsViewModel - Uses a function to convert image type to data and save it in CoreData.
4. ExistingDealDetailsView - This is where I want the Image to be fetched and displayed from CoreData.

I'm storing the image using an Image Picker in DealDetailsView and want to fetch it in ExistingDealDetailsView. The UI of both the views are same, except I want to be get the image from CoreData in ExistingDealDetailsView when the View Appears.

Deal is a CoreData Entity Class and deal.propertyImage(BinaryData) is what I want to fetch and display as an Image.

ImagePicker:

import UIKit
import SwiftUI


struct ImagePicker: UIViewControllerRepresentable {
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
}


var sourceType: UIImagePickerController.SourceType = .photoLibrary
@Binding var selectedImage: UIImage
@Environment(\.presentationMode) private var presentationMode

func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {

    let imagePicker = UIImagePickerController()
    imagePicker.allowsEditing = false
    imagePicker.sourceType = sourceType
    imagePicker.delegate = context.coordinator
    return imagePicker
}

func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {

}
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
 
    var parent: ImagePicker
 
    init(_ parent: ImagePicker) {
        self.parent = parent
    }
 
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
 
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            parent.selectedImage = image
        }
 
        parent.presentationMode.wrappedValue.dismiss()
    }
}
}

DealDetailsView:

import SwiftUI
import Combine

struct DealDetailsView: View {

@StateObject var detailVM = DealDetailsViewModel()
@Environment(\.dismiss) var dismiss

@State var deal = Deal()

@State var isShowPhotoLibrary = false

var body: some View {
// Deal Property Image
                VStack {
                    Text("Property Image")
                        .foregroundColor(.gray)
                        .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                        .padding()
                    
                    HStack {
                        
                        Button(action: {
                            isShowPhotoLibrary = true
                        }) {
                            VStack {
                                Image(systemName: "square.and.arrow.up")
                                    .font(.system(size: 20))
                                
                                Text("Upload Photo")
                                    .font(.body)
                                
                            }
                            .frame(width: 100, height: 100, alignment: .center)
                            .background(.gray)
                            .foregroundColor(.white)
                            .cornerRadius(10)
                        }
                        
                        Image(uiImage: detailVM.image)
                            .resizable()
                            .scaledToFill()
                            .frame(width: 100, height: 100, alignment: .trailing)
                            .cornerRadius(10)
                    }
                }
                .sheet(isPresented: $isShowPhotoLibrary) {
                    ImagePicker(sourceType: .photoLibrary, selectedImage: $detailVM.image)
                }
                .padding()
                .padding(.leading, -10)
}

DealDetailsViewModel:

@Published var image = UIImage() // The image used to  display.

func saveDeals() {
    
    let deal = Deal(context: CoreDataManager.shared.viewContext)
    let pickedImage = image.jpegData(compressionQuality: 0.80) // This is how I convert Image to type Data.
    deal.propertyImage = pickedImage
    
    LoggedInUser.shared.currentUser?.addToDeals(deal)
    
    CoreDataManager.shared.save()
}

ExistingDealDetailsView:

import SwiftUI
import Combine

struct ExistingDealDetailsView: View {

@StateObject var existingDealDetailsVM = ExistingDealDetailsViewModel()
@StateObject var detailVM = DealDetailsViewModel()
@Environment(\.dismiss) var dismiss

@State var deal = Deal()

@State var isShowPhotoLibrary = false

var body: some View {
//Deal Property Image
                VStack {
                    Text("Property Image")
                        .foregroundColor(.gray)
                        .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                        .padding()
                    
                    HStack {
                        
                        Button(action: {
                            isShowPhotoLibrary = true
                        }) {
                            VStack {
                                Image(systemName: "square.and.arrow.up")
                                    .font(.system(size: 20))
                                
                                Text("Upload Photo")
                                    .font(.body)
                                
                            }
                            .frame(width: 100, height: 100, alignment: .center)
                            .background(.gray)
                            .foregroundColor(.white)
                            .cornerRadius(10)
                        }
                        
                        Image(uiImage: deal.propertyImage) // Return Error: "Cannot convert value of type 'Data?' to expected argument type 'UIImage'"
                            .resizable()
                            .scaledToFill()
                            .frame(width: 100, height: 100, alignment: .trailing)
                            .cornerRadius(10)
                    }
                }
                .sheet(isPresented: $isShowPhotoLibrary) {
                    ImagePicker(sourceType: .photoLibrary, selectedImage: $detailVM.image)
                }
                .padding()
                .padding(.leading, -10)

}

CodePudding user response:

Storing image as a binary data is not an efficient approach you can save image into disk and store its url in the database but anyway let me explain how you can fetch binary data and convert it for using in the imageView with a simple example. Suppose you have an entity called Users and in that entity you have image field that you store your image file as a binary data. Below code can get binary stored images from the Users entity and show it in an imageview. Just apply this code according to your code.

let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext

let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
fetchRequest.returnsObjectsAsFaults = false

do {
    let results = try context.fetch(fetchRequest)
    for result in results as! [NSManagedObject] {
    if let imageData = result.value(forKey:"image") as? Data {
        let image = UIImage(data: imageData)
        yourImageView.image = image
    }
} catch  {
    print("error")
}

CodePudding user response:

You can do this very easily by using the Transformer variables in the Data Model Inspector

Paste the below code anywhere in your project

@objc(ImageToDataTransformer)
class ImageToDataTransformer: NSSecureUnarchiveFromDataTransformer {
    public static let name = NSValueTransformerName(rawValue: String(describing: ImageToDataTransformer.self))
    override class func allowsReverseTransformation() -> Bool {
        return true
    }
    
    override class func transformedValueClass() -> AnyClass {
        return UIImage.self
    }
    
    override class var allowedTopLevelClasses: [AnyClass] {
        return [UIImage.self]
    }

    override func transformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else {
            fatalError("Wrong data type: value must be a Data object; received \(type(of: value))")
        }
        return super.transformedValue(data)
    }
    
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let image = value as? UIImage else {
            fatalError("Wrong data type: value must be a UIImage object; received \(type(of: value))")
        }
        return super.reverseTransformedValue(image)
    }
    //Register before CoreData setup starts
    @objc dynamic
    public static func register() {

        let transformer = ImageToDataTransformer()
        ValueTransformer.setValueTransformer(transformer, forName: name)
    }
}

Then place

ImageToDataTransformer.register()

Just below

container = NSPersistentCloudKitContainer(name: "YourAppName")

before doing anything to the store

Then change a few things on your variable

enter image description here

Now you can access the propertyImage as a UIImage?, there is no need for any jpegData or Data handling at all. CoreData does all the work.

https://developer.apple.com/documentation/coredata/handling_different_data_types_in_core_data

You can also save to file with something like this. (I haven't tested the code but you should get a clear picture)

Add a propertyImageURL: URL attribute to your Entity

extension Deal{
    //Use this variable to access the image directly instead of propertyImage
    var documentsProperty: UIImage?{
        get{
            if let propertyImageURL = propertyImageURL{
                return UIImage(contentsOfFile: URL(fileURLWithPath: propertyImageURL.absoluteString).path)
            }else{
                return nil
            }
        }
        set{
            if let jpgData = newValue?.jpegData(compressionQuality: 0.5){
                
                let mgr = FileManager.default
                
                let documents = mgr.urls(for: .documentDirectory, in: .userDomainMask).first
                if let fileURL = documents?.appendingPathComponent("Property\(someIdentifier).jpg"){
                    
                    do{
                        try jpgData.write(to: fileURL)
                        //Save the URL
                        self.propertyImageURL = fileURL
                    }catch{
                        print(error)
                    }
                }else{
                    print("Invalid fileURL")
                }
            }else{
                self.propertyImageURL = nil
                print("newValue == nil")
            }
        }
    }
}

With the above code you an access the UIImage with something like deal.documentsProperty where it comes from is invisible to the rest of the app.

Something your should note too is that all CoreData objects are ObservableObjects so they should be wrapped in @ObservedObject instead of @State

  •  Tags:  
  • Related