I'm trying to create a view, that allows the user to upload multiple images and display them in a HStack. I'm able to display a singular image by simply putting:
image?.resizable().scaledToFit()
So in order to display multiple images, I thought to append it to an array loadImages()
, and use a ForEach loop in order to display the images, by defining a
@State private var images: [Image]? = []
, and using images?.append(image)
to add it to the images
array.
Since it's an optional, in my ForEach loop, I unwrapped it with an empty array, but this is where I get the error Referencing initializer 'init(_:id:content:)' on 'ForEach' requires that 'Image' conform to 'Hashable'
. To my understanding, by adding id: \.self
should allow it to conform to Hashable already - is there any special properties that Image contains that prevents it from conforming to Hashable
, or is there something fundamental I'm missing here?
For reference, the full code is:
import SwiftUI
struct ImagePickerTestView: View {
@State private var images: [Image]? = []
@State private var image: Image?
@State private var isShowingImagePicker = false
@State private var inputImage: UIImage?
var body: some View {
NavigationView {
VStack {
ZStack {
Rectangle()
.fill(Color.secondary)
Text("Tap To Select Photos")
.foregroundColor(.white)
.font(.headline)
}
.onTapGesture {
isShowingImagePicker = true
}
HStack {
ForEach(images ?? [], id: \.self) { img in
img
.resizable()
.scaledToFit()
}
}
/*
//insert code to display images here
//old implementation was
image?
.resizable()
.scaledToFit()
*/
}
.padding([.horizontal, .bottom])
.navigationTitle("Select Images")
.onChange(of: inputImage) { _ in
loadImages()
}
.sheet(isPresented: $isShowingImagePicker) {
ImagePicker(image: $inputImage)
}
}
}
func loadImages() {
guard let inputImage = inputImage else { return }
let image = Image(uiImage: inputImage)
images?.append(image)
}
}
#Preview {
ImagePickerTestView()
}
and the imagePicker is:
import PhotosUI
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
@Binding var image: UIImage?
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration()
config.filter = .images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let provider = results.first?.itemProvider else { return }
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, _ in
self.parent.image = image as? UIImage
}
}
}
}
}
, which simply allows you to select a photo.
A better solution might be to wrap each image with an Identifiable
wrapper. For example:
struct IdentifiableImage: Identifiable {
let id = UUID()
let image: Image
}
If the array is then changed to be [IdentifiableImage]
, the ForEach
can be simplified to the following:
ForEach(images) { img in
img.image
.resizable()
.scaledToFit()
}