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

UICollectionView中cellForItemAt触发index out of range数组越界问题

数组越界(index out of range)问题排查与解决

问题原因

  1. UICollectionView数据源逻辑完全倒置

    • numberOfItemsInSection方法的作用是返回当前section下的item总数,但你现在返回的是cellTypes.count(也就是section的总数量)。这会导致每个section被分配了远大于实际子数组长度的item数,当indexPath.row超过cellTypes[indexPath.section].count时,直接触发越界错误。
    • 同时缺失numberOfSections方法,UICollectionView默认section数量为1,和你cellTypes的多section结构不匹配,进一步加剧索引混乱。
  2. 单元格配置的索引匹配风险
    在configureTorontoCell中直接用indexPath.row访问torPhotos,如果section和城市类型的对应关系出错,或者row索引与目标数组长度不匹配,也会触发越界。

  3. 异步数据加载的时序问题
    若photoViewModel.getPhotos()是异步操作,fillCellTypes执行时各城市照片数组可能还未被正确填充,子数组长度为0,后续数据源方法返回的item数与实际数据不匹配,引发索引错误。

解决办法

1. 修正UICollectionView数据源方法

补充并修正核心数据源方法,确保section和item的数量逻辑正确:

extension WeatherDetailVC: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    // 返回section总数,对应cellTypes的子数组数量
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return cellTypes.count
    }
    
    // 返回当前section下的item数量,对应cellTypes子数组的长度
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        guard section < cellTypes.count else { return 0 }
        return cellTypes[section].count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard indexPath.section < cellTypes.count, indexPath.row < cellTypes[indexPath.section].count else {
            return UICollectionViewCell() // 返回空cell避免崩溃
        }
        let cellType = cellTypes[indexPath.section][indexPath.row]
        switch cellType {
        case .tor:
            return configureCityCell(photos: photoViewModel.torPhotos, indexPath: indexPath)
        case .vanc:
            return configureCityCell(photos: photoViewModel.vancPhotos, indexPath: indexPath)
        case .calg:
            return configureCityCell(photos: photoViewModel.calgPhotos, indexPath: indexPath)
        case .ott:
            return configureCityCell(photos: photoViewModel.ottPhotos, indexPath: indexPath)
        case .mon:
            return configureCityCell(photos: photoViewModel.monPhotos, indexPath: indexPath)
        }
    }
    
    // 通用单元格配置方法,避免重复代码
    private func configureCityCell(photos: [PhotoModel], indexPath: IndexPath) -> PhotoCollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCollectionViewCell.identifier, for: indexPath) as! PhotoCollectionViewCell
        guard indexPath.row < photos.count else {
            return cell // 索引越界时返回空配置的cell
        }
        cell.setup(photos[indexPath.row])
        return cell
    }
}

2. 确保数据加载完成后再刷新UI

如果photoViewModel.getPhotos()是异步操作,需要在数据加载完成的回调中调用fillCellTypes():

func configCollectionView() {
    photoViewModel.getPhotos { [weak self] in
        guard let self = self else { return }
        self.photos = self.photoViewModel.photos
        self.vancPhotos = self.photoViewModel.vancPhotos
        self.calgPhotos = self.photoViewModel.calgPhotos
        self.ottPhotos = self.photoViewModel.ottPhotos
        self.torPhotos = self.photoViewModel.torPhotos
        self.monPhotos = self.photoViewModel.monPhotos
        self.fillCellTypes() // 数据加载完成后再填充并刷新
    }
}

(需确保getPhotos方法提供回调闭包,或通过Delegate/Combine等方式通知数据加载完成)

3. 优化cellTypes的构建逻辑(可选)

简化cellTypes填充逻辑,同时过滤空数组避免无效section:

func fillCellTypes() {
    cellTypes = [
        Array(repeating: CellType.vanc, count: vancPhotos.count),
        Array(repeating: CellType.calg, count: calgPhotos.count),
        Array(repeating: CellType.mon, count: monPhotos.count),
        Array(repeating: CellType.ott, count: ottPhotos.count),
        Array(repeating: CellType.tor, count: torPhotos.count)
    ].filter { !$0.isEmpty }
    collectionView.reloadData()
}

内容的提问来源于stack exchange,提问作者Mert Köksal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 10:39:33