SwiftUI使用PHPickerController选图访问vm.images报索引越界错误
问题描述
学习SwiftUI框架过程中,通过PHPickerController从系统相册选取多张图片、逐张渲染对应图片展示视图时,只要访问vm.images的任意索引就会触发Fatal error: Index out of range致命错误,需要定位并解决该问题,涉及的项目源码附在解决方案部分。
解决方案
问题根源是vm.images的加载时序不符合预期:原本逻辑假设.onChange修饰符会在所有图片完全存入vm.images数组后才触发执行,但实际图片是异步逐张加载写入数组的,第一张图片写入时就会触发vm.images的状态变更,此时数组尚未完成所有图片的加载写入,直接渲染视图访问索引就会触发越界错误。
修复方式为在调用PickerTabView时增加前置判断逻辑,仅当vm.images非空且数组元素数量大于0时才渲染对应视图,新增代码如下:
// 新增判断代码 if let images = vm.images { if images.count > 0 { PickerTabView() } }
修复后各组件完整实现代码如下:
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) } }
ImagesPicker 图片选择器封装
struct ImagesPicker: UIViewControllerRepresentable { @Binding var selectedImages: [UIImage]? 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) } } } } } } } }
PickerHomeView 选择器首页
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 }) } } }
PickerSettingView 单图设置页
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") } } } } }
ViewModel 视图模型
class ViewModel: ObservableObject { @Published var images: [UIImage]? @Published var showPicker = false }
内容的提问来源于stack exchange,提问作者Ryan Byoun
相关产品推荐
相关产品推荐

