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

如何实现双行横向滚动UICollectionView动态宽度单元格左对齐

问题原因

你原先使用的左对齐布局逻辑是针对纵向滚动流布局设计的,当滚动方向改为横向时,系统原生UICollectionViewFlowLayout的排版规则变为「先按列填充高度,超出后横向扩展」,原有按Y坐标判断新行的逻辑失效,因此无法实现左对齐效果。

解决方案

使用适配横向双行场景的自定义流布局即可实现需求,完整代码如下:

class HorizontalTwoRowLeftAlignedFlowLayout: UICollectionViewFlowLayout {
    // 缓存所有单元格布局属性
    private var cachedAttributes: [UICollectionViewLayoutAttributes] = []
    // 滚动内容总宽度
    private var totalContentWidth: CGFloat = 0
    
    override var collectionViewContentSize: CGSize {
        return CGSize(
            width: totalContentWidth,
            height: collectionView?.bounds.height ?? 0
        )
    }
    
    override func prepare() {
        super.prepare()
        guard let collectionView = collectionView else { return }
        cachedAttributes.removeAll()
        totalContentWidth = 0
        
        let itemCount = collectionView.numberOfItems(inSection: 0)
        guard itemCount > 0 else { return }
        
        // 初始化两行的X偏移、Y坐标
        var rowXOffsets: [CGFloat] = [sectionInset.left, sectionInset.left]
        let rowYOffsets: [CGFloat] = [
            sectionInset.top,
            sectionInset.top + itemSize.height + minimumLineSpacing
        ]
        // 当前分配行索引,交替分配到两行
        var currentRow = 0
        
        for index in 0..<itemCount {
            let indexPath = IndexPath(item: index, section: 0)
            // 从代理获取动态单元格宽度
            let itemSize: CGSize
            if let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
                itemSize = delegate.collectionView?(collectionView, layout: self, sizeForItemAt: indexPath) ?? self.itemSize
            } else {
                itemSize = self.itemSize
            }
            
            // 生成布局属性
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = CGRect(
                x: rowXOffsets[currentRow],
                y: rowYOffsets[currentRow],
                width: itemSize.width,
                height: itemSize.height
            )
            cachedAttributes.append(attributes)
            
            // 更新当前行X偏移
            rowXOffsets[currentRow] += itemSize.width + minimumInteritemSpacing
            // 更新内容总宽度,取两行最大偏移值
            totalContentWidth = max(totalContentWidth, rowXOffsets[currentRow])
            // 切换到下一行
            currentRow = currentRow == 0 ? 1 : 0
        }
        // 加上右侧内边距
        totalContentWidth += sectionInset.right
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return cachedAttributes.filter { $0.frame.intersects(rect) }
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cachedAttributes[indexPath.item]
    }
}

使用方法

  1. 初始化布局时设置滚动方向为横向,原有间距、内边距配置保持不变即可:
let layout = HorizontalTwoRowLeftAlignedFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = CellFrame.padding
layout.minimumLineSpacing = CellFrame.padding
layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) // 按需调整内边距
collectionView.collectionViewLayout = layout
  1. 你现有的sizeForItem、间距相关代理代码无需修改,可直接复用。

可选调整

如果需要实现「第一行尽可能排满再排第二行」的排版逻辑,只需修改prepare方法内的行分配规则:每次判断当前cell是否能放进第一行剩余空间,能则放第一行,否则放第二行即可。


内容的提问来源于stack exchange,提问作者Umer Khan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 15:48:04