UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How do I get my UIImage that I receive from a custom Camera/Photo Picker stored in SwiftData?

Forums > SwiftUI

Hello

Hobbyist coder here, my livelihood does NOT depend on coding, so if somoene has the time and patience to help me, I would much appreciate it.

After much playing around, I now have a really nice custom Camera / Photo Picker (Picker - A) that works as expected in my App apart from saving to SwiftData. I also have (for testing purposes and my own education/sanity) implimented the PhotosUI / PhotosPicker (Picker - B) on the same view, but I am after the camera capability of Picker - A.

I can save my my Picker - B image to SwiftData as a var : Data? as loads of people recommend. This all works and gets saved and read as required.

Picker - A however currently finishes with a var : UIImage? and no matter how much I try, I have not found a solution to converting the UIImage datatype to Data datatype so that it can be saved in SwiftData. I have read about creating the following extension(s) for UIImage, but hav eno idea as to how to impliment one of them.

Thank you in advance for your assistance, if someone can help me :)

My code is currently as follows...

//
//  EditProductView.swift
//  Sardine
//
//  Created by Simon RANSOM on 13/02/2024.
//

import PhotosUI
import SwiftData
import SwiftUI

struct EditProductView: View {
    @Environment(\.dismiss) private var dismiss

    @State private var name = ""
    @State private var showVendors = false

// Used by Picker - B
    @State private var selectedProductImage: PhotosPickerItem?
    @State private var selectedProductImageData: Data?

// Used by Picker - A
    @State private var image: UIImage?
    @State private var imageData: Data? // I added this var
    @State private var isConfirmationDialogPresented: Bool = false
    @State private var isImagePickerPresented: Bool = false
    @State private var sourceType: SourceType = .camera

    enum SourceType {
        case camera
        case photoLibrary
    }

    let product: Product

    var body: some View {
    NavigationStack {
// Picker - A
        VStack {

            ZStack{
                if let image = image {
                    CircularImageView(image: image)
                }else{
                    PlaceholderView()
                }

            }
            .onTapGesture{
                isConfirmationDialogPresented = true
            }
            .confirmationDialog("Choose an option", isPresented: $isConfirmationDialogPresented) {
                Button("Camera"){
                    sourceType = .camera
                    isImagePickerPresented = true
                }
                Button("Photo Library"){
                    sourceType = .photoLibrary
                    isImagePickerPresented = true
                }

            }
            .sheet(isPresented: $isImagePickerPresented) {
                if sourceType == .camera{
                    ImagePickerA(isPresented: $isImagePickerPresented, image: $image, sourceType: .camera)
                }else{
                    PhotoPickerA(selectedImage: $image)
                }
            }
// Picker - A end

// Picker - B
            HStack {
                PhotosPicker(
                    selection: $selectedProductImage,
                    matching: .images,
                    photoLibrary: .shared()) {
                        Group {
                            if let selectedProductImageData,
                               let uiImage = UIImage(data: selectedProductImageData) {
                                Image(uiImage: uiImage)
                                    .resizable()
                                    .scaledToFit()
                            } else {
                                Image(systemName: "photo")
                                    .resizable()
                                    .scaledToFit()
                                    .tint(.primary)
                            }
                        }
                        .frame(width: 100, height: 100)
                        .overlay(alignment: .bottomTrailing) {
                            if selectedProductImage != nil {
                                Button {
                                    selectedProductImage = nil
                                    selectedProductImageData = nil
                                } label: {
                                    Image(systemName: "x.circle.fill")
                                        .foregroundStyle(.red)
                                }
                            }
                        }
                    }
                }
            }
        }
        .navigationTitle($name)
        .navigationBarTitleDisplayMode(.inline)
        .toolbarRole(.editor)
        .toolbar {
            if changed {
                Button {
                    product.name = name
                    product.productImage = selectedProductImageData
//                    product.profileImage = image
                    dismiss()
                } label: {
                    Image(systemName: "pencil.circle.fill")
                        .imageScale(.large)
                }
            }
        }

        .onAppear {
            name = product.name
            selectedProductImageData = product.productImage
//            image = product.profileImage

        }

        .task(id: selectedProductImage) {
            if let data = try? await selectedProductImage?.loadTransferable(type: Data.self) {
                selectedProductImageData = data
            }
        }
//        .task(id: image) {
//            if let data = try? await image?.loadTransferable(type: Data.self) {
//                imageData = data
//            }
//        }
    }

    var changed: Bool {
        name != product.name
        || selectedProductImageData != product.productImage
//        || image != product.profileImage
    }
}

struct CircularImageView: View {
    var image: UIImage

    var body: some View {
        Image(uiImage: image)
            .resizable().scaledToFill()
            .frame(width: 200, height: 200)
            .clipShape(Circle())

    }
}
struct PlaceholderView: View {
    var body: some View {
        ZStack {
            Circle()
                .stroke(lineWidth: 5)
                .foregroundColor(.gray)
                .frame(width: 200, height: 200)
            Image(systemName: "plus")
                .font(.system(size: 50))
                .foregroundColor(.gray)
        }
    }
}

// ImagePicker struct is a SwiftUI wrapper around UIImagePickerController
struct ImagePickerA: UIViewControllerRepresentable {
    @Binding var isPresented: Bool  // Binding to control the presentation of the image picker
    @Binding var image: UIImage?    // Binding to hold the selected image
    var sourceType: UIImagePickerController.SourceType// Specifies the source type for the image picker (camera or photo library)

    // This method creates a Coordinator instance which handles the delegation of UIImagePickerController
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    // This method creates a UIImagePickerController instance and sets its delegate and source type
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator  // Set the delegate to handle image picker events
        picker.sourceType = sourceType // Set the source type (camera or photo library)
         return picker
    }

    // This method is used to update the UIImagePickerController when SwiftUI view updates, but not used in this example
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { }

    // Coordinator class to handle the delegate methods of UIImagePickerController
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        var parent:ImagePickerA  // Reference to the parent ImagePicker struct
        init(parent: ImagePickerA) {
            self.parent = parent
        }

        // This delegate method is called when an image is selected
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let uiImage = info[.originalImage] as? UIImage {// Retrieve the selected image
                parent.image = uiImage
            }
            parent.isPresented = false// Dismiss the image picker
        }

        // This delegate method is called when the image picker is cancelled
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.isPresented = false // Dismiss the image picker
        }
    }
}

// PhotoPicker struct is a SwiftUI wrapper around PHPickerViewController, which is a modern replacement for UIImagePickerController for picking photos from the library
struct PhotoPickerA: UIViewControllerRepresentable {
    class Coordinator: NSObject, PHPickerViewControllerDelegate {
        var parent:PhotoPickerA // Reference to the parent PhotoPicker struct

        init(parent: PhotoPickerA) {
            self.parent = parent
        }

        // This delegate method is called when an image is selected
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            if let result = results.first{
                result .itemProvider.loadObject(ofClass: UIImage.self) { object, error in
                    if let uiImage = object as? UIImage {
                        DispatchQueue.main.async {
                            self.parent.selectedImage = uiImage // Assign the selected image to the parent's `selectedImage` property

                        }
                    }
                }
            }
            picker.dismiss(animated: true,completion: nil) // Dismiss the photo picker

        }
    }

    @Binding var selectedImage: UIImage?  // Binding to hold the selected image

    // This method creates a Coordinator instance which handles the delegation of PHPickerViewController
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)

    }

    // This method creates a PHPickerViewController instance with specified configurations
    func makeUIViewController(context: Context) -> PHPickerViewController {
        var configuration = PHPickerConfiguration()

        configuration.selectionLimit = 1// Limiting selection to one image
        configuration .filter = .images // Filtering for images only
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator // Set the delegate to handle photo picker events
        return picker

    }

    // This method is used to update the PHPickerViewController when SwiftUI view updates, but not used in this example
    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
}

1      

And the @Model for my Product is as follows:

@Model
class Product {
    var uuid = UUID()
    var name: String
    var shoppingLists: [ShoppingList]?
    @Relationship(inverse: \Vendor.products)
    var vendors: [Vendor]?
    @Relationship(inverse: \ProductCategory.products)
    var category: ProductCategory?

    @Attribute(.externalStorage)
    var productImage: Data?

    @Attribute(.externalStorage)
    var profileImage: Data?

    init(
        uuid: UUID = UUID(),
        name: String,
        category: ProductCategory? = nil
    ) {
        self.uuid = uuid
        self.name = name
        self.shoppingLists = []
        self.vendors = []
        self.category = category
    }
}

1      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.