You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 19:12:19