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
相关产品推荐
相关产品推荐

