Array Indexing occurs Fatal error: Index out of range

Viewed 85

Problem

Hello. I'm studying SwiftUI.

I've tried to pick multiple photos from gallery using PHPickerController, and show up multiple views which represents each photo one by one. However, Fatal error occurs whenever I try to access any index of vm.images. How could I solve this issue?

My source code is as follows

Solved

The problem comes from vm.images I thought that .onChange modifier operate after all images are saved into vm.images. But it didn't.

I solved this matter by adding if statement when calling PickerTabView; Quite Easy

// Added code
if let images = vm.images {
    if images.count > 0 {
        PickerTabView()
    }
}

struct PickerTabView: View {
    @EnvironmentObject var vm: ViewModel
    
    var body: some View {
        TabView {
            if let images = vm.images{
                ForEach(images, id: \.self) { image in
                    PickerSettingView(image: image)
                }
            }
        }
        .tabViewStyle(.page)
    }
}
struct ImagesPicker: UIViewControllerRepresentable {
    @Binding var selectedImages: [UIImage]?
    
    //var selectionLimit: Int
    //var filter: PHPickerFilter?
    var itemProvider: [NSItemProvider] = []
    
    func makeUIViewController(context: Context) -> some PHPickerViewController {
        var configuration = PHPickerConfiguration()
        configuration.selectionLimit = 20
        configuration.filter = .images
        
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        
    }
    
    func makeCoordinator() -> Coordinator {
        return ImagesPicker.Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, PHPickerViewControllerDelegate, UINavigationControllerDelegate {
        var parent: ImagesPicker
        
        init(parent: ImagesPicker) {
            self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            picker.dismiss(animated: true)
            
            if !results.isEmpty {
                parent.itemProvider = []
                parent.selectedImages = []
            }
            
            parent.itemProvider = results.map(\.itemProvider)
            loadImage()
        }
        
        private func loadImage() {
            for itemProvider in parent.itemProvider {
                if itemProvider.canLoadObject(ofClass: UIImage.self) {
                    itemProvider.loadObject(ofClass: UIImage.self) { image, error in
                        DispatchQueue.main.sync {
                            if let image = image as? UIImage {
                                self.parent.selectedImages?.append(image)
                            }
                        }
                    }
                }
            }
        }
        
        
    }
}

struct PickerHomeView: View {
    @EnvironmentObject var vm: ViewModel
    @State private var isSelected = false
    
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink("Tab View", isActive: $isSelected) {
                    PickerTabView()
                }
                .hidden()
                
                HStack {
                    Button {
                        vm.showPicker()
                    } label: {
                        ButtonLabel(symbolName: "photo.fill", label: "Photos")
                    }
                }
                Spacer()
            }
            .sheet(isPresented: $vm.showPicker) {
                ImagesPicker(selectedImages: $vm.images)
                    .ignoresSafeArea()
            }
            .onChange(of: vm.images, perform: { _ in
                isSelected = true
            })
        }
    }
}
struct PickerSettingView: View {
    @EnvironmentObject var vm: ViewModel
    var image: UIImage
    let myImage = MyImage(category: Category.unCategorized)
    @State private var selectedCategory: Category = Category.unCategorized
    
    var body: some View {
        VStack {
            Image(uiImage: image)
                .resizable()
                .scaledToFit()
                .frame(minWidth: 0, maxWidth:.infinity)
            
            SwiftUI.Picker("Category Picker", selection: $selectedCategory) {
                Text("Formal").tag(Category.formal)
                Text("Casual").tag(Category.casual)
                Text("Semi Formal").tag(Category.semiFormal)
            }
                .pickerStyle(.segmented)
                .padding([.leading, .trailing], 16)
            
            HStack {
                Button {
                    if vm.selectedImage == nil {
                        vm.addMyImage(category: selectedCategory, image: image)
                    } else {
                        vm.updateSelected()
                    }
                } label: {
                    ButtonLabel(symbolName: vm.selectedImage == nil ? "square.and.arrow.down.fill" :
                                    "square.and.arrow.up.fill",
                                label: vm.selectedImage == nil ? "Save" : "Update")
                }
            }
        }
    }
}
class ViewModel: ObservableObject {
    @Published var images: [UIImage]?
    @Published var showPicker = false
}
1 Answers

I think the image in your loadImage, is not being added to the self.parent.selectedImages when it is nil, that is, when images in your ViewModel is nil, as it is at the start. So whenever you try to access any index of the images array in your vm.images, the app crashes.

You could try this in your loadImage (note also .async) to append the images:

 DispatchQueue.main.async {
      if let image = image as? UIImage {
          if self.parent.selectedImages == nil { self.parent.selectedImages = [] }
          self.parent.selectedImages!.append(image)
      }
  }
Related