swiftswiftuiswiftui-foreachswiftui-image

ForEach Loop requires that Image Conform to 'Hashable' when trying to show an array of Images?


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.


Solution

  • 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()
    }