如何实现双行横向滚动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] } }
使用方法
- 初始化布局时设置滚动方向为横向,原有间距、内边距配置保持不变即可:
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
- 你现有的
sizeForItem、间距相关代理代码无需修改,可直接复用。
可选调整
如果需要实现「第一行尽可能排满再排第二行」的排版逻辑,只需修改prepare方法内的行分配规则:每次判断当前cell是否能放进第一行剩余空间,能则放第一行,否则放第二行即可。
内容的提问来源于stack exchange,提问作者Umer Khan
相关产品推荐
相关产品推荐

