UITableViewCell内嵌垂直UICollectionView显示异常(UIKit&Storyboard)
问题背景
- 现有情况:UITableViewCell内嵌水平UICollectionView可正常工作
- 需求:将布局改为UITableViewCell内嵌垂直UICollectionView,对齐Android版本应用的样式
已尝试操作
- 在Storyboard中将CollectionView布局切换为垂直方向
- 将CollectionViewCell的Estimated Size设为None
- 禁用CollectionView的滚动功能
- 自定义
DynamicHeightCollectionView类实现高度自适应 - 编写
DayEmptySeatTableViewCell的业务代码:
class DynamicHeightCollectionView: UICollectionView { override func layoutSubviews() { super.layoutSubviews() if bounds.size != intrinsicContentSize { self.invalidateIntrinsicContentSize() } } override var intrinsicContentSize: CGSize { return collectionViewLayout.collectionViewContentSize } }
class DayEmptySeatTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { @IBOutlet weak var labelDate: UILabel! @IBOutlet weak var labelWeekday: UILabel! @IBOutlet weak var collectionViewEmptySeats: UICollectionView! var dayEmptySeat: DayEmptySeat? { didSet { setDayEmptySeat() } } var delegate: DayEmptySeatCellDelegate? override func awakeFromNib() { super.awakeFromNib() labelDate.text = "" labelWeekday.text = "" collectionViewEmptySeats.dataSource = self collectionViewEmptySeats.delegate = self collectionViewEmptySeats.register(UINib(nibName: "EmptySeatCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "EmptySeatCollectionViewCell") } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } func setDayEmptySeat() { guard let dayEmptySeat else { return } labelDate.text = dayEmptySeat.date labelWeekday.text = dayEmptySeat.weekDay collectionViewEmptySeats.reloadData() } func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: 80, height: 80) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return dayEmptySeat?.emptySeats.count ?? 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionViewEmptySeats.dequeueReusableCell(withReuseIdentifier: "EmptySeatCollectionViewCell", for: indexPath) as? EmptySeatCollectionViewCell else { return UICollectionViewCell() } guard let dayEmptySeat else { return UICollectionViewCell() } let emptySeat = dayEmptySeat.emptySeats[indexPath.row] cell.emptySeat = emptySeat return cell } }
异常现象
- UICollectionView下一行布局错乱
- 首次打开ViewController显示异常,再次打开显示效果变化
- 随机影响其他UITableViewCell中的UICollectionView布局
解决思路
- 修复CollectionView高度计算逻辑
原DynamicHeightCollectionView的高度判断逻辑会频繁触发布局刷新,改成只在内容尺寸真的变化时才更新:
class DynamicHeightCollectionView: UICollectionView { private var lastContentSize: CGSize = .zero override func layoutSubviews() { super.layoutSubviews() let currentContentSize = collectionViewLayout.collectionViewContentSize if currentContentSize != lastContentSize { lastContentSize = currentContentSize invalidateIntrinsicContentSize() } } override var intrinsicContentSize: CGSize { return collectionViewLayout.collectionViewContentSize } }
- 强制触发Cell布局更新
在setDayEmptySeat方法中,刷新CollectionView后强制触发布局,避免复用残留的旧布局:
func setDayEmptySeat() { guard let dayEmptySeat else { return } labelDate.text = dayEmptySeat.date labelWeekday.text = dayEmptySeat.weekDay collectionViewEmptySeats.reloadData() // 强制刷新CollectionView布局,确保高度计算正确 collectionViewEmptySeats.layoutIfNeeded() // 通知TableView更新当前Cell的高度 self.setNeedsLayout() self.layoutIfNeeded() }
- 处理UITableViewCell复用状态重置
在prepareForReuse中清空Cell状态,避免复用旧数据导致的布局异常:
override func prepareForReuse() { super.prepareForReuse() dayEmptySeat = nil labelDate.text = "" labelWeekday.text = "" collectionViewEmptySeats.reloadData() // 重置CollectionView滚动位置,避免残留偏移 collectionViewEmptySeats.contentOffset = .zero }
- 明确配置CollectionViewFlowLayout
在awakeFromNib中显式设置垂直滚动方向,并补充间距配置,避免布局歧义:
override func awakeFromNib() { super.awakeFromNib() labelDate.text = "" labelWeekday.text = "" collectionViewEmptySeats.dataSource = self collectionViewEmptySeats.delegate = self // 显式设置垂直布局 if let flowLayout = collectionViewEmptySeats.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.scrollDirection = .vertical flowLayout.minimumLineSpacing = 10 flowLayout.minimumInteritemSpacing = 10 } collectionViewEmptySeats.register(UINib(nibName: "EmptySeatCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "EmptySeatCollectionViewCell") }
- 关闭UITableView的预估高度(若开启)
如果UITableView设置了estimatedRowHeight,会导致Cell高度计算偏差,直接关闭预估:
// 在ViewController的viewDidLoad中设置 tableView.estimatedRowHeight = 0 tableView.rowHeight = UITableView.automaticDimension
内容的提问来源于stack exchange,提问作者dev.ex
相关产品推荐
相关产品推荐

