Search code examples
core-dataswiftuiimagepicker

Is there any way I can associate the image selected from imagePicker with the field that it’s saved when in Edit mode?


I have the Detail view below which displays the birthday card of a selected record from previous listView and when click into Edit button, the fields are displayed and we can edit the person data including the profile picture. The point is that all the fields are correctly updated/saved when clicking Done after edited except the image. I believe that it is because Edit mode tracks changes into viewContext and the imagePicker is handling the image into a separate var (pic). Is there any way I can associate the image (var pic) from the imagePicker with the birthday.pic field that it’s saved when done editing ?

DetailView:

import SwiftUI

struct BirthdaysDetailView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.editMode) private var editMode
    @Environment(\.dismiss) private var dismiss
    
    @ObservedObject var birthday: Nivers
    
    @State private var isEditing: Bool = false
    @State private var isShowPhotoLibrary = false
    @State private var pic: Data = Data(count: 0)
    
    var body: some View {
        VStack {
            if editMode?.wrappedValue.isEditing == true {
                VStack {
                    ScrollView {
                        VStack {
                            if pic.count != 0 {
                                ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
                                    Image(uiImage: UIImage(data: pic)!)
                                        .resizable()
                                        .aspectRatio(contentMode: .fill)
                                        .mask(Circle())
                                        .overlay(Circle().stroke(Color.text, lineWidth: 2))
                                        .frame(width: 150, height: 150)
                                        .padding(.bottom, 5)
                                    
                                    // Cancel Button...
                                    Button(action: { birthday.pic = Data(count: 0) }) {
                                        Image(systemName: "xmark.circle")
                                            .foregroundColor(Color.red)
                                            .padding(6)
                                            .clipShape(Circle())
                                    }
                                }
                                .padding(.bottom)
                                .opacity(1)
                            } else {
                                Image(systemName: "person.crop.circle")
                                    .font(.system(size: 50).weight(.ultraLight))
                                    .foregroundColor(Color.text)
                                    .clipped()
                            }
                            Button(action: {
                                self.isShowPhotoLibrary = true
                            }) {
                                HStack {
                                    Image(systemName: "photo")
                                        .font(.system(size: 10))
                                    Text("Photo library")
                                        .font(.caption2)
                                }
                                .frame(minWidth: 100, minHeight: 20)
                                .background(Color.text)
                                .foregroundColor(.white)
                                .cornerRadius(5)
                            }
                            .padding(.bottom, 10)
                            HStack {
                                TextField("Name", text: $birthday.name, onEditingChanged: { self.isEditing = $0 })
                                TextField("Last Name", text: $birthday.lastName, onEditingChanged: { self.isEditing = $0 })
                                Button {
                                    birthday.isFavorite = true
                                } label: {
                                    VStack {
                                        if birthday.isFavorite {
                                            Image(systemName: "star.fill")
                                                .foregroundColor(Color.yellow)
                                                .font(Font.system(.caption2, design: .default).weight(.light))
                                        } else {
                                            Image(systemName: "star")
                                                .foregroundColor(Color.yellow)
                                                .font(Font.system(.caption2, design: .default).weight(.light))
                                        }
                                    }
                                    .padding(5)
                                }
                            }
                            .font(.title)
                            VStack {
                                DatePicker(selection: $birthday.birthDate, displayedComponents: [.date], label: { Text("Date") })
                                    .labelsHidden()
                                    .datePickerStyle(.graphical)
                                    .frame(width: 250)
                                TextField("Phone", text: $birthday.phone, onEditingChanged: { self.isEditing = $0 })
                                    .textFieldStyle(.roundedBorder)
                                    .padding(.bottom, 5)
                                TextField("Email", text: $birthday.email, onEditingChanged: { self.isEditing = $0 })
                                    .textFieldStyle(.roundedBorder)
                                    .padding(.bottom, 50)
                            }
                            .foregroundColor(Color.text)
                            Spacer()
                        }
                        .padding()
                        .background(Color.background)
                    }
                    .sheet(isPresented: $isShowPhotoLibrary) {
                        ImagePicker(sourceType: .photoLibrary, selectedImage: $pic)
                    }
                }
                .navigationBarColor(.systemGray5)
            } else {
                ZStack {
                    LottieView(name: "confetti", loopMode: .loop)
                        .frame(width: 230, height: 250)
                    VStack {
                        if let image = birthday.image {
                            ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
                                image
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                                    .mask(Circle())
                                    .frame(width: 150, height: 150)
                                    .clipped()
                                    .padding(.bottom, 5)
                            }
                            .padding(.bottom)
                            .opacity(1)
                        }
                        HStack {
                            Text("\(birthday.name) \(birthday.lastName)")
                                .font(Font.system(.title, design: .rounded).weight(.heavy))
                                .padding()
                                .foregroundColor(Color.primary)
                            if birthday.isFavorite {
                                Image(systemName: "star.fill")
                                    .foregroundColor(Color.yellow)
                                    .font(Font.system(.callout, design: .default).weight(.medium))
                            }
                        }
                        Text("\(age(date: birthday.birthDate)) years old")
                            .font(Font.system(.body, design: .rounded).weight(.ultraLight))
                            .padding(.bottom, 5)
                            .foregroundColor(Color.primary)
                        RoundedRectangle(cornerRadius: 30, style: .continuous)
                            .fill(Color.black.opacity(0.5))
                            .frame(height: 40)
                            .clipped()
                            .padding(.horizontal)
                            .overlay(Text("\(birthday.birthDate, formatter: itemFormatter)")
                                        .font(Font.system(.body, design: .rounded).weight(.semibold))
                                        .foregroundColor(Color.white), alignment: .center)
                        if checkBirthdayDates(date: birthday.birthDate) {
                            Text("TODAY")
                                .font(Font.system(.caption, design: .rounded).weight(.bold))
                                .padding()
                                .foregroundColor(Color.primary)
                        } else {
                            Text("\((relativeBirthDate(date: birthday.birthDate)))")
                                .font(Font.system(.caption, design: .rounded).weight(.light))
                                .padding()
                                .foregroundColor(Color.primary)
                        }
                    }
                }
                .padding()
                .background(Color.background)
                .cornerRadius(20)
                .padding(10)
                .navigationBarColor(.systemGray5)
            }
        }
        .navigationBarBackButtonHidden(true)
        .edgesIgnoringSafeArea(.bottom)
        .toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                Button(action: {
                    dismiss()
                }) {
                    Image(systemName: "arrow.backward.square.fill")
                        .font(.body)
                        .foregroundColor(Color.text)
                }
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                EditButton()
                    .font(.body)
                    .foregroundColor(Color.text)
            }
        }
        .onReceive(birthday.objectWillChange) { _ in
            if viewContext.hasChanges {
                try?  viewContext.save()
            }
        }
    }
}

The code above shows correctly the data and image saved when creating the record, it changes the picture when in edit mode by selecting another image also but when clicking done, all changed data is saved except the new image. I did a huge research and tried many other ways but nothing yet to solve this.

Any ideas ?

ImagePicker:

import UIKit
import SwiftUI

struct ImagePicker: UIViewControllerRepresentable {
    var sourceType: UIImagePickerController.SourceType = .photoLibrary
    
    @Binding var selectedImage: Data
    @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>) {
        
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    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.jpegData(compressionQuality: 0.1)!
            }
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
}

Solution

  • So instead of the binding I would do it like this:

          import UIKit
      import SwiftUI
    
      struct ImagePicker: UIViewControllerRepresentable {
         var sourceType: UIImagePickerController.SourceType = .photoLibrary
         
         // @Binding var selectedImage: Data
         let completion: (_ selectedImage: UIImage?) -> Void
         @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>) {
            
         }
         
         func makeCoordinator() -> Coordinator {
            Coordinator(self)
         }
         
         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 {
                        self.parent.completion(image.jpegData(compressionQuality: 0.1)!)
                  }
                  parent.presentationMode.wrappedValue.dismiss()
            }
         }
      }
    

    In the view you can use it like this:

        ImagePicker(sourceType: .photoLibrary) {newPicture in
                                    
                                    //Do what you want here
                                    self.pic = newPicture
                                }