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) { }
}