UICollectionView渲染异常:跳转后无法正常显示布局求助
问题描述
点击UITableView单元格后跳转至SmiliarViewController,该控制器内的UICollectionView无法正常渲染布局,预期的横向滚动列表无法显示。
跳转逻辑代码
extension MoviesViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let movie = viewModel.state.movies[indexPath.row] let viewModel = MoviesDetailsViewModel(movie: movie, apiManager: APIManager()) let viewController = SmiliarViewController(viewModel: viewModel) viewModel.fetchSimilarMovie() self.navigationController?.pushViewController(viewController, animated: true) } }
SmiliarViewController 代码
import UIKit class SmiliarViewController: UIViewController { private let viewModel: MoviesDetailsViewModel init(viewModel: MoviesDetailsViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) navigationItem.largeTitleDisplayMode = .never } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } fileprivate let collectionView:UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.translatesAutoresizingMaskIntoConstraints = false cv.register(SimilierMovieCell.self, forCellWithReuseIdentifier: "CompanyCell") cv.backgroundColor = .brown return cv }() override func viewDidLoad() { super.viewDidLoad() setUpUI() self.viewModel.updatedState = {[weak self] in DispatchQueue.main.async { self?.collectionView.reloadData() } } viewModel.fetchSimilarMovie() } private func setUpUI() { view.addSubview(collectionView) let safeArea = view.safeAreaLayoutGuide collectionView.topAnchor.constraint(equalTo: view.bottomAnchor).isActive = true collectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true collectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true collectionView.heightAnchor.constraint(equalToConstant: 80).isActive = true } } extension SmiliarViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: collectionView.frame.width/2.5, height: collectionView.frame.width/2) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let items = viewModel.moviePage.count return items } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SimilierMovieCell.identifier, for: indexPath) as? SimilierMovieCell let listMovie = viewModel.moviePage[indexPath.row] print(listMovie) cell?.configure(listMovie) return cell ?? SimilierMovieCell() } }
问题截图

问题分析与修复方案
1. 约束错误导致CollectionView移出屏幕
setUpUI中设置CollectionView顶部约束时,错误地将其与view.bottomAnchor对齐,导致整个CollectionView被移出屏幕视野。
修复:
将顶部约束改为与安全区域顶部对齐:
collectionView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
2. 未设置CollectionView的DataSource和Delegate
代码中实现了UICollectionViewDataSource协议,但未将collectionView.dataSource和collectionView.delegate设置为当前控制器。另外,sizeForItemAt属于UICollectionViewDelegateFlowLayout协议,需要额外遵守该协议。
修复:
在viewDidLoad的setUpUI之后添加:
collectionView.dataSource = self collectionView.delegate = self
同时更新协议声明:
extension SmiliarViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { // ... 原有代码 }
3. Cell复用标识符不匹配
注册Cell时使用的标识符是"CompanyCell",但dequeue时使用的是SimilierMovieCell.identifier,如果两者不一致会导致无法正确复用Cell,返回默认空Cell。
修复:
统一复用标识符,建议在SimilierMovieCell中定义静态属性:
static let identifier = "SimilierMovieCell"
然后修改注册代码:
cv.register(SimilierMovieCell.self, forCellWithReuseIdentifier: SimilierMovieCell.identifier)
4. 重复调用接口请求
跳转逻辑中已经调用了viewModel.fetchSimilarMovie(),在viewDidLoad中又重复调用,可能导致重复请求和数据混乱。
修复:
删除viewDidLoad中的viewModel.fetchSimilarMovie()调用,保留跳转时的一次调用即可。
修复后的完整SmiliarViewController代码
import UIKit class SmiliarViewController: UIViewController { private let viewModel: MoviesDetailsViewModel init(viewModel: MoviesDetailsViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) navigationItem.largeTitleDisplayMode = .never } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } fileprivate let collectionView:UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.translatesAutoresizingMaskIntoConstraints = false cv.register(SimilierMovieCell.self, forCellWithReuseIdentifier: SimilierMovieCell.identifier) cv.backgroundColor = .brown return cv }() override func viewDidLoad() { super.viewDidLoad() setUpUI() collectionView.dataSource = self collectionView.delegate = self self.viewModel.updatedState = {[weak self] in DispatchQueue.main.async { self?.collectionView.reloadData() } } } private func setUpUI() { view.addSubview(collectionView) let safeArea = view.safeAreaLayoutGuide collectionView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true collectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true collectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true collectionView.heightAnchor.constraint(equalToConstant: 80).isActive = true } } extension SmiliarViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: collectionView.frame.width/2.5, height: collectionView.frame.width/2) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return viewModel.moviePage.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SimilierMovieCell.identifier, for: indexPath) as? SimilierMovieCell else { return SimilierMovieCell() } let listMovie = viewModel.moviePage[indexPath.row] cell.configure(listMovie) return cell } }
内容的提问来源于stack exchange,提问作者user23454452

