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

UITableView嵌套UICollectionView数据异常与索引越界问题排查

问题分析与修复方案

我来帮你梳理下代码里的核心问题,正是这些问题导致了数组为空、索引越界以及每个CollectionView无法展示不同数据的情况:


1. 静态数组误用+数据填充逻辑错误

你在ViewController和TableViewCell里分别定义了两个独立的静态movies数组,这直接导致数据无法在两者间共享。更关键的是,在解析网络数据时,你循环每个电影结果都把整个分类模型重复添加到数组里,这会让数组充满重复的同一分类数据,完全违背了“每个section对应一个分类数据”的需求。

修复步骤:

  • 删除TableViewCell里的static var movies: [MovieModel] = [],不需要这个冗余的静态数组。
  • 把ViewController里的静态数组改成实例数组(全局静态在这里没必要):
    var movies: [MovieModel] = []
    
  • 修正解析方法的数组填充逻辑:每个分类解析完成后,只添加一次完整的分类模型,而非循环每个电影都添加:
    func parsePopularMovies() {
        let jsonUrlString = "URLwithMyAPIkey"
        guard let url = URL(string: jsonUrlString) else { return }
        URLSession.shared.dataTask(with: url) { (data, response, err) in
            guard let data = data else { return }
            do {
                var tempMovies = try JSONDecoder().decode(MovieModel.self, from: data)
                // 用forEach统一处理海报路径,避免强制解包崩溃
                tempMovies.results?.forEach { movie in
                    if let posterPath = movie.poster_path {
                        movie.poster_path = "https://image.tmdb.org/t/p/w500" + posterPath
                    }
                    if let backdropPath = movie.backdrop_path {
                        movie.backdrop_path = "https://image.tmdb.org/t/p/w500" + backdropPath
                    }
                }
                self.popularMovies = tempMovies
                // 只添加一次完整的分类模型到数组
                self.movies.append(tempMovies)
                // 数据准备好后再刷新表格
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            } catch let jsonErr {
                print("Error serializing json:", jsonErr)
            }
        }.resume()
    }
    
    用同样的逻辑修改parseNowPlayingMovies方法,删除循环添加的代码,改为只添加一次完整的分类模型。

2. TableViewCell未接收对应分类的数据

在ViewController的tableView(_:cellForRowAt:)方法里,你只是创建了cell,但完全没有把当前section对应的电影数据传给cell,导致cell里的CollectionView根本拿不到要展示的内容。

修复步骤:

  • 给TableViewCell添加一个接收数据的属性,并删除冗余的tableView数据源方法(这个方法不属于TableViewCell):
    class HomeScreenCategoriesTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
        @IBOutlet var collectionView: UICollectionView!
        // 保存当前分类的电影数据
        var categoryMovies: MovieModel?
    
        override func awakeFromNib() {
            super.awakeFromNib()
            self.collectionView.delegate = self
            self.collectionView.dataSource = self
            // 这里不需要立刻刷新,等数据赋值后再操作
        }
    
        // 删除这个多余的方法:它是UITableView的数据源方法,不该出现在Cell里
        // func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { ... }
    
        // 其他方法保持不变,后面再修改数据源逻辑
    }
    
  • 在ViewController的cellForRowAt里,把对应section的数据传给cell并刷新CollectionView:
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "HomeScreenMovieTableViewCell", for: indexPath) as? HomeScreenCategoriesTableViewCell else {
            return UITableViewCell()
        }
        // 安全传递对应section的数据
        if indexPath.section < self.movies.count {
            cell.categoryMovies = self.movies[indexPath.section]
            cell.collectionView.reloadData()
        }
        return cell
    }
    

3. CollectionView数据源方法错误

你之前试图访问静态数组获取数据,现在已经改成通过categoryMovies接收数据,所以要修改CollectionView的数据源方法,同时不要写死item数量(避免索引越界)。

修复步骤:

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    // 返回当前分类的实际电影数量,空数据时返回0避免崩溃
    return categoryMovies?.results?.count ?? 0
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeScreenMovieCell", for: indexPath) as? HomeScreenCategoriesCollectionViewCell else {
        return UICollectionViewCell()
    }
    // 安全解包数据,避免索引越界和空值崩溃
    if let movie = categoryMovies?.results?[indexPath.row], let title = movie.title {
        cell.test.text = title
    } else {
        cell.test.text = "Unknown"
    }
    return cell
}

4. 网络请求后的刷新时机错误

你在viewDidLoad里调用完解析方法后立刻刷新表格,但网络请求是异步的,此时数据还没解析完成,刷新完全无效。现在我们已经把刷新逻辑放到了每个解析方法的回调里,确保数据准备好后再刷新。

如果想等两个请求都完成后统一刷新,可以用DispatchGroup优化:

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.rowHeight = 130
    tableView.tableFooterView = UIView()

    let dispatchGroup = DispatchGroup()

    dispatchGroup.enter()
    parsePopularMovies {
        dispatchGroup.leave()
    }

    dispatchGroup.enter()
    parseNowPlayingMovies {
        dispatchGroup.leave()
    }

    dispatchGroup.notify(queue: .main) {
        self.tableView.reloadData()
    }
}

// 给解析方法添加完成回调
func parsePopularMovies(completion: @escaping () -> Void) {
    let jsonUrlString = "URLwithMyAPIkey"
    guard let url = URL(string: jsonUrlString) else {
        completion()
        return
    }
    URLSession.shared.dataTask(with: url) { (data, response, err) in
        // ... 解析代码 ...
        self.movies.append(tempMovies)
        completion()
    }.resume()
}

// 同样修改parseNowPlayingMovies方法,添加completion参数

5. 其他小问题修正

  • 你设置numberOfSections返回2,但moviesCategories有5个元素,这会导致后面的分类无法显示,改成返回分类数组的长度:
    func numberOfSections(in tableView: UITableView) -> Int {
        return moviesCategories.count
    }
    
  • 解析时的!!强制解包非常危险,一旦接口返回空值就会崩溃,全部替换成可选绑定的方式(就像上面修复里的那样)。

内容的提问来源于stack exchange,提问作者Alex Giatrakis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:24:48