UIImage in SwiftUI

Hi, I have a problem with the following code:

     struct AddView: View {
  @State var text:String = ""
  @State private var image : UIImage?
  @State private var showingPicker = false
  @Binding var listItems: [Item]
  @State private var inputImage : UIImage?
  var body: some View {
    VStack{
      TextField("Name", text: $text).padding().background(Color(.systemGray5)).frame(width: 400,alignment: .center).cornerRadius(20)
      Image(systemName: "person.circle")
        .frame(width: 170, height: 170, alignment: .center)
        .clipShape(Circle())
        .shadow(radius: 10)
        .overlay(Circle()
        .stroke(Color.blue))
        .position(x: 209, y: 100)
        .onTapGesture {
          self.showingPicker = true
             
        }.sheet(isPresented: $showingPicker, onDismiss: loadImage){
          ImagePicker(image: self.$inputImage)
        }
       
       
      Button(action: {
        listItems.append(Item(name: text, image: inputImage))
        UserDefaults.standard.saveItems(listItems)
        print(listItems)
        
         
         
         
      }, label: {
        Text("Add")
      })
    }
  }
  func loadImage(){
    guard let inputImage1 = inputImage else {
      image = inputImage
      return
    }
  }
}
extension UserDefaults {
  func saveItems(_ items: [Item]) {
    let encoder = JSONEncoder()
    if let data = try? encoder.encode(items) {
      set(data, forKey: "Items")
    }
  }
}
struct ImagePicker : UIViewControllerRepresentable{
   
  class Coordinator : NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate{
    let 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.image = uiImage
      }
      parent.presentaitionMode.wrappedValue.dismiss()
    }
  }
   
  @Environment(\.presentationMode) var presentaitionMode
  @Binding var image : UIImage?
   
  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
   
   
   
  func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController{
    let picker = UIImagePickerController()
    picker.delegate = context.coordinator
    return picker
  }
  func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
     
  }
}

struct Item : Identifiable, Codable{
  var id = UUID()
  var name : String
  var image : String
   
}

When I want to append an Image to the listItems, I get the following Error:

Cannot convert value of type 'UIImage?' to expected argument type 'String'

I don't know what I can do to change it, because when I change the Type of image in Item to UIImage, I get these errors:

Type 'Item' does not conform to protocol 'Decodable'
Type 'Item' does not conform to protocol 'Identifiable'

What can I do?

For Item to automatically be Codable, you need to make sure that each element (id, name, and image) is Codable. If you can ensure that, the compiler will automatically implement necessary functions for you. See Encoding and Decoding Custom Types.

However, UIImage is not Codable (you can't put an image in a JSON, for one), so the compiler can't create the necessary part for you.


Now, you're trying to treat Item as a JSON object judging from UserDefaults.saveItems you implemented, so it'd make sense for Item to be codable.

On how to store the images as JSON object, it depends on what those images are. As said earlier, JSON doesn't have an image object.

  • If you can identify the image with something else, like a name, you can instead store the name of the image.
  • You can also store image as a separate file, and use the URL to that file when saving to UserDefault.
  • You can also convert the images to the raw bytes of data, and then store them with some string encoding, like base64 for which Data has a convenient conversion function–Data. base64EncodedString(options:).*

That said, this is getting into iOS territory about how to persistently store image. If you have further question in that direction, I'd suggest that you ask over http://developer.apple.com/forums/ instead.

* I actually wouldn't recommend storing large amount of data, like an entire image in the UserDefault. It'll likely slow down your application significantly.

Thank You

Codable is used to encoder data to json, and decoder data from json, generally we store basic data with json, like string, bool, number and so on.
If you want to store image, you'd better store the image path or image name in your database, then store image files in a cache folder, like ' FileManager.SearchPathDirectory.cachesDirectory', when you want to read your image, get it from your folder with image name.

ImagePicker in SwiftUI | Apple Developer Forums

Can you help me with this?

Unfortunately, no. I'm not too well-versed in the UIImagePicker.

Though I can suggest a few things:

  • Try to remove unnecessary parts. Not a lot of people are willing to read a 100-line code just to tell you what's wrong.

  • Try to be more descriptive about what you'd expect, and what happened.

    I see that you print inputImage at line 49, but I need to read the source code to see that. And I still don't see what's wrong, it must be nil there. We wouldn't be in the else case otherwise.

Here's a cleaned up code:

struct AddView: View {
    @State private var showingPicker = false
    @State private var inputImage : String?
    
    var body: some View {
        Button("Pick Image") {
            self.showingPicker = true
        }.sheet(isPresented: $showingPicker, onDismiss: loadImage) {
            ImagePicker(image: self.$inputImage)
        }
    }
    
    func loadImage() {
        print(inputImage) // Shouldn't be `nil` here
    }
}

struct ImagePicker : UIViewControllerRepresentable{
    class Coordinator : NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate{
        @Binding var image: String?
        
        init(image: Binding<String?>) {
            _image = image
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: String]) {
            image = info[.originalImage]
            picker.dismiss(animated: true)
        }
    }
    
    @Binding var image: String?
    
    func makeCoordinator() -> Coordinator { .init(image: $image) }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController{
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { }
}
Terms of Service

Privacy Policy

Cookie Policy