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

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布局
解决思路
  1. 修复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
    }
}
  1. 强制触发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()
}
  1. 处理UITableViewCell复用状态重置
    在prepareForReuse中清空Cell状态,避免复用旧数据导致的布局异常:
override func prepareForReuse() {
    super.prepareForReuse()
    dayEmptySeat = nil
    labelDate.text = ""
    labelWeekday.text = ""
    collectionViewEmptySeats.reloadData()
    // 重置CollectionView滚动位置,避免残留偏移
    collectionViewEmptySeats.contentOffset = .zero
}
  1. 明确配置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")
}
  1. 关闭UITableView的预估高度(若开启)
    如果UITableView设置了estimatedRowHeight,会导致Cell高度计算偏差,直接关闭预估:
// 在ViewController的viewDidLoad中设置
tableView.estimatedRowHeight = 0
tableView.rowHeight = UITableView.automaticDimension

内容的提问来源于stack exchange,提问作者dev.ex

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 21:07:07