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

滚动UICollectionView时可变高度Cell尺寸计算异常求助

UICollectionView可变高度Cell滚动时内容与Cell高度不匹配的解决方法

各位下午好,我有一个包含可变高度Cell的UICollectionView,滚动时内容高度与Cell高度不匹配,求解决方法。

相关代码

1. CollectionView创建代码

// MARK: - UI Fabric.
private extension CollectionViewController {
    func createCollectionView() -> UICollectionView {
        let layout = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = 16
        layout.minimumLineSpacing = 16
        layout.scrollDirection = .vertical
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.showsVerticalScrollIndicator = false
        collectionView.translatesAutoresizingMaskIntoConstraints = false

        return collectionView
    }
}

2. Cell尺寸代理代码

// MARK: - CollectionView Flow Layout.
extension CollectionViewController: UICollectionViewDelegateFlowLayout {
    /// Setting cell sizes.
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAt indexPath: IndexPath
    ) -> CGSize {
        delegateSettingCell.calculateCellSize(screenWidth: view.bounds.width, index: indexPath.row)
    }
}

3. Cell尺寸计算代码

func calculateCellSize(screenWidth: CGFloat, index: Int) -> CGSize {
    let item = modelForDisplay[index].addonDetail.count
    let heightCell = CGFloat((item * 44) + 44)
    let sizeCellWidth = (view.bounds.width - 32)
    return CGSize(width: sizeCellWidth, height: heightCell)
}

4. Cell实现代码

final class AddonCreatorCell: UICollectionViewCell {

    // MARK: - Dependencies
    var delegate: IHandlerAddonCreatorCellDelegate?

    // MARK: - Public properties
    static var reuseIdentifier: String = "AddonCreatorCell.cell"

    // MARK: - Private properties
    private lazy var textFields: [UITextField] = []
    private lazy var viewDie = createView()
    private lazy var gradientViewDie = createGradient(GradientColors.yellowGradient)
    private lazy var labelTitle = createUILabel()
    private lazy var switchShow = createSwitch()
    private lazy var vStack = createStack()
    private lazy var headerStack = createStack()

    // MARK: - Initializator
    override init(frame: CGRect) {
        super.init(frame: frame)
        addUIView()
        setupConfiguration()
        setupLayout()
    }

    convenience init(handlerAddonCreatorCellDelegate: IHandlerAddonCreatorCellDelegate?) {
        self.init(frame: CGRect.zero)
        self.delegate = handlerAddonCreatorCellDelegate
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    // MARK: - Public methods
    func reloadFrame(size: CGSize) {
        self.gradientViewDie.frame.size = size

    }

    func reloadData(model: CreateAddon) {
    // Here I delete unnecessary UIStask with content
        vStack.subviews.forEach {
            if $0 != headerStack {
                $0.removeFromSuperview()
            }
        }

        labelTitle.text = "\(model.title) \(model.addonDetail.count)"

        model.isActive ? activeSwitch() : deactivateSwitch()
        switchShow.setOn(model.isActive, animated: false)

        for (index, element) in model.addonDetail.enumerated() {
            createContent(item: element, index: index)
        }
    }

    private func createContent(item: AddonDetail, index: Int) {
        let convertToString = String(item.count)
        let label = createUILabel()
        let textField = createTextField(id: index, text: convertToString)
        let hStack = createStack()

        label.text = item.title
        textFields.append(textField)
        textField.delegate = self
        textField.addTarget(self, action: #selector(self.myTextFieldChanged(_:)), for: .editingChanged)

        hStack.axis = .horizontal
        hStack.alignment = .center
        hStack.distribution = .fillProportionally
        hStack.addArrangedSubview(label)
        hStack.addArrangedSubview(textField)
        vStack.addArrangedSubview(hStack)
        addConstraintTextField(textField)
    }
}

补充说明

我做了点击Cell显示尺寸的功能,滚动后尺寸并未改变。


解决方法

1. 替换Frame布局为AutoLayout

你的reloadFrame方法直接修改gradientViewDie的frame,会和AutoLayout冲突。删除该方法,给gradientViewDie添加约束绑定到Cell的contentView:

// 在setupLayout方法中添加以下代码
gradientViewDie.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    gradientViewDie.topAnchor.constraint(equalTo: contentView.topAnchor),
    gradientViewDie.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
    gradientViewDie.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
    gradientViewDie.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])

确保所有子视图都添加到contentView上,而非直接添加到Cell本身。

2. 确保内容更新后布局生效

在Cell的reloadData方法末尾添加layoutIfNeeded(),确保动态添加的子视图布局计算完成:

func reloadData(model: CreateAddon) {
    // 原有更新代码...
    
    // 添加这行代码
    layoutIfNeeded()
}

3. 通知CollectionView重新计算尺寸

当数据源变化导致Cell高度改变时,调用以下方法让CollectionView重新计算布局:

// 更新数据源后调用
collectionView.performBatchUpdates(nil, completion: nil)
// 或者针对单个Cell更新
collectionView.reloadItems(at: [indexPath])

4. 优化高度计算逻辑

固定数值计算高度容易出现偏差,改用真实Cell预计算高度:

func calculateCellSize(screenWidth: CGFloat, index: Int) -> CGSize {
    let model = modelForDisplay[index]
    let cell = AddonCreatorCell()
    cell.reloadData(model: model)
    cell.bounds = CGRect(x: 0, y: 0, width: screenWidth - 32, height: .greatestFiniteMagnitude)
    cell.layoutIfNeeded()
    let realHeight = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
    return CGSize(width: screenWidth - 32, height: realHeight)
}

注意:如果Cell数量较多,建议缓存计算后的高度避免重复计算影响性能。

5. 设置FlowLayout预估尺寸

给FlowLayout设置estimatedItemSize,帮助布局系统更准确预估Cell高度,减少滚动跳动:

let layout = UICollectionViewFlowLayout()
// 使用自动预估尺寸
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
// 或者设置一个接近实际的预估高度
// layout.estimatedItemSize = CGSize(width: screenWidth - 32, height: 100)

内容的提问来源于stack exchange,提问作者Steven Kirke

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 07:54:56