3

Hello everyone I am kind of frustrated, so I hope to get some help. My Project is in SwiftUI. I want to use an image Picker to save the image to Core Data. I achieved to get the ImagePicker to work, but im struggling to convert Image --> UIImage --> Data to then save it. The Project is running without errors, but it doesn't save the image. To know where the prob is I implemented 3 print Statements. The image = gives a value, The UIImage (inputImage) = nil, the Data of course is nil.

import SwiftUI


struct AddNewBean: View {
    
    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Bean.entity(), sortDescriptors: []) var beans: FetchedResults<Bean>
    @State var BeanRoaster: String = ""
    @State var BeanName: String = ""
    @State var BeanStyle: String = "Dark"
    
    @State private var RoastDate = Date()
        var dateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        return formatter }
    
    @State private var showImagePicker : Bool = false
    @State private var image : Image?
    @State private var inputImage: UIImage?
    @State var imageAlt: Data = .init(count: 0)
    
    let RStyles = ["Dark", "Medium", "Light"]
    
    func loadImage() {
        guard let inputImage = inputImage else { return }
        image = Image(uiImage: inputImage)
    }
    
    var body: some View {
        
        NavigationView {
            VStack {
                Form {
                    VStack {
                        image?.resizable().scaledToFit().aspectRatio(contentMode: .fit)
                        HStack {
                            Spacer()
                            Button("Open Camera"){
                                self.showImagePicker = true
                            }.padding(5)
                                .foregroundColor(Color.white)
                                .background(Color.accentColor)
                                .cornerRadius(10)
                            Spacer()
                        }.sheet(isPresented: self.$showImagePicker, onDismiss: loadImage){
                            PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image)
                        }
                    }
                    TextField("Röster", text: $BeanRoaster)
                    TextField("Name der Bohne", text: $BeanName)
                    Picker("Roestung", selection: $BeanStyle) {
                        ForEach(RStyles, id: \.self) {
                            Text($0)
                        }
                    }
                    DatePicker(selection: $RoastDate, in: ...Date(), displayedComponents: .date) {Text("Röstdatum")}
   
                    HStack {
                        Spacer()
                        if BeanRoaster != "" && BeanName != "" {
                            Button(action: {
                                //....
                                let pickedImage = self.inputImage?.jpegData(compressionQuality: 1.0)
                                                                print("image, inputimage, pickedImage")
                                print(self.image as Any) // prints:  Optional(SwiftUI.Image(provider: SwiftUI.ImageProviderBox<__C.UIImage>))
                                print(self.inputImage as Any) // prints: nil
                                print(pickedImage as Any) // prints: nil
                                //.....
                                let bean = Bean(context: self.moc)
                                bean.id = UUID()
                                bean.roaster = "\(self.BeanRoaster)"
                                bean.name = "\(self.BeanName)"
                                bean.roastStyle = "\(self.BeanStyle)"
                                bean.roastDate = self.RoastDate
                                bean.active = true
                                bean.img = pickedImage
                                
                                try? self.moc.save()
                                self.presentationMode.wrappedValue.dismiss()
                            }) {
                                Image(systemName: "tray")
                                    .foregroundColor(.blue)
                                    .font(.largeTitle)
                                    .padding(.vertical)
                            }
                            Text("Save").foregroundColor(.blue)
                            Spacer()
                        } else {
                            HStack {
                                Spacer()
                                Text("Trage Bohnendaten ein!")
                                Spacer()
                            }
                        } 
                    } 

                    Section {
                        HStack {
                            Spacer()
                            Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
                                Image(systemName: "")
                                    .foregroundColor(.red)
                                    .font(.largeTitle)
                                    .padding(.vertical)
                            }
                            Text("Dismiss").foregroundColor(.red)
                            Spacer()
                        }
                    }
                }.navigationBarTitle("New Bean")
            }
        }
    }
}
Andre
  • 41
  • 1
  • 2
  • SwiftUI `Image` is-a View, you get `UIImage` from image picker, keep it as model and use in both places: and to show in `Image` and to store in database. – Asperi Jul 10 '20 at 10:10
  • Hello Asperi, thank you for your answer. The ImagePicker i´m using is handling the image as an Image. Im not quite sure how to handle it. I want that the selected Image is shown directly. Can you give me some more Info? Thank you. – Andre Jul 10 '20 at 10:36
  • ok, Thank you, I used another ImagePicker: https://www.hackingwithswift.com/forums/swiftui/saving-images-with-core-data-in-swiftui/1241 – Andre Jul 10 '20 at 11:45
  • possible easy way -> https://stackoverflow.com/questions/65614931/uploading-an-image-instead-of-an-uiimage-to-firestore/65616306#65616306 – YodagamaHeshan Jan 08 '21 at 00:42

2 Answers2

4

Below is a few lines of code from my existing app, I have simplified it. My approach was to simply get back the image data from the Image Picker, instead of an UIImage. This worked out for me.

ContentView

struct ContentView: View {
    @Environment(\.managedObjectContext) var viewContext
    
    @FetchRequest(entity: Images.entity(), sortDescriptors: [], animation: Animation.easeInOut)
    var images: FetchedResults<Images>
    
    @State private var showImagePicker = false
    @State private var image = Image(systemName: "person.circle.fill")
    @State private var data: Data?
    @State private var optionalData = UIImage(systemName: "person.circle.fill")?.jpegData(compressionQuality: 1)
    
    var body: some View {
        VStack {
            ForEach(images) { image in
                Image(uiImage: UIImage(data: (image.imageData ?? optionalData)!)!)
                    // your modifiers
            }
            
            image
                .resizable()
                .frame(width: 150, height: 150)
            
            Button(action: {
                showImagePicker = true
            }) {
                Text("Select")
            }
            
            Button(action: {
                let newImage = Images(context: viewContext)
                newImage.imageData = data

                do {
                    try viewContext.save()
                    print("Image Saved!")
                } catch {
                    print(error.localizedDescription)
                }
            }) {
                Text("Save")
            }
        }
        .sheet(isPresented: $showImagePicker, onDismiss: loadImage) {
            ImagePicker(data: $data)
        }
    }
    
    func loadImage() {
        guard let data = data else { return }
        
        image = Image(uiImage: UIImage(data: data) ?? UIImage(systemName: "person.circle.fill")!)
    }
}

ImagePicker

struct ImagePicker: UIViewControllerRepresentable {
    @Environment(\.presentationMode) var presentationMode
    @Binding var data: Data?
    
    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 uiImage = info[.originalImage] as? UIImage {
                parent.data = uiImage.jpegData(compressionQuality: 1)
            }
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.allowsEditing = true
        picker.sourceType = .photoLibrary
        picker.delegate = context.coordinator
        
        return picker
    }
    
    func makeCoordinator() -> Coordinator { Coordinator(self) }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {  }
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
Praduyt Sen
  • 137
  • 1
  • 8
1

There are some mistakes in the Code above. I recreated the solution, which contains changing the ImagePicker aswell.

What your ImagePicker does, it changes the @State of an Image. You display that Image, and your picked Image will be shown. Everything fine here. However, you won't be able to get the UIImage of an Image. It's not possible if you look at the interface of Image class.

Let your Image Picker return an UIImage

I am not sure, which ImagePicker you used. However, you can simply modify the version here to return an UIImage.

Lets only store a Boolean if the imagePicker is shown and the UIImage.

@State private var showImagePicker : Bool = false
@State private var image : UIImage?

Whenever, the UIImage changes you can update your Image. Simply use:

if (self.image != nil)
{
    Image(uiImage: self.image!).resizable().scaledToFit().aspectRatio(contentMode: .fit)
}

Now, when it comes to storing your UIImage to your Database, you need to convert it to Data object. This is simple.

Button(action: {
    
    //Not nil
    print(self.image)

    //PNG Representation as Data
    let imageData = self.image!.pngData()
    
    //Data type here --> Data
    print(type(of: imageData))

Here you see, it is from object Data and Data can simply stored in CoreData.

The only thing you still need to fix, is that you are not passing a Binding of Image to your ImagePicker, instead pass a Binding as UIImage.

Will look something like that:

struct CaptureImageView {
    
    @Binding var isShown: Bool
    @Binding var image: UIImage?
    
    func makeCoordinator() -> Coordinator {
      return Coordinator(isShown: $isShown, image: $image)
    }
}

And in the didFinishPickingMediaWithInfo, you simply set the UIImage.

func imagePickerController(_ picker: UIImagePickerController,
            didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
 guard let unwrapImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
 imageInCoordinator = unwrapImage
 isCoordinatorShown = false
}

Again, that's not my ImagePicker. I just modified it for you to make clear what's wrong and what you need to fix. I got a working solution here, and it is possible. Just make sure what you are using, not Image, instead UIImage to keep reference of it.

davidev
  • 7,694
  • 5
  • 21
  • 56