Swift iOS实现水平滚动三行动态宽度等间距UICollectionView布局
3行水平滚动自适应宽度UICollectionView左对齐布局修复方案
问题现象
需要实现3行、水平滚动、单元格宽度自适应、单元格等间距、布局左对齐的类安卓水平列表效果,当前实现存在单元格无法左对齐的布局异常:
原有问题代码
集合视图初始化代码
func setUpCollectionView() { let layout = UICollectionViewFlowLayout() layout.scrollDirection = UICollectionView.ScrollDirection.horizontal layout.minimumInteritemSpacing = 16 layout.minimumLineSpacing = 12 layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize collectionView.semanticContentAttribute = UISemanticContentAttribute.forceLeftToRight collectionView.contentInsetAdjustmentBehavior = .always collectionView.collectionViewLayout = layout }
代理实现代码
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let extraSpace : CGFloat = 50.0 return CGSize(width: (self.viewModels?.car_problems?[indexPath.item].problem?.size(withAttributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 17)]).width ?? 34) + extraSpace, height: 32) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 16 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 12 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 5, left: 16, bottom: 0, right: 16) }
预期效果

问题原因
- 原生
UICollectionViewFlowLayout在水平滚动模式下,默认会自动填充每行剩余空间,将单元格做居中/两端分散对齐,没有内置左对齐逻辑 - 同时设置
estimatedItemSize = UICollectionViewFlowLayout.automaticSize和实现sizeForItemAt返回准确尺寸,两套尺寸计算逻辑冲突,会加剧布局错位 - 原有代码中布局初始化和代理方法返回的间距参数数值不一致,也会导致布局计算异常
修复步骤
- 第一步:删除
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize配置,已经通过sizeForItemAt返回准确单元格尺寸,不需要自动估算 - 第二步:自定义左对齐FlowLayout子类,重写布局属性计算逻辑,强制所有单元格左对齐,保持设定的等间距
- 第三步:用自定义布局替换原有的原生FlowLayout初始化,统一配置间距、内边距参数,删除重复的间距代理方法避免数值冲突
自定义左对齐布局代码
class LeftAlignedHorizontalFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil } // 按y轴坐标分组,同一midY的元素属于同一行 var rowGroups = [CGFloat: [UICollectionViewLayoutAttributes]]() for attr in attributes { let rowKey = attr.frame.midY if rowGroups[rowKey] == nil { rowGroups[rowKey] = [] } rowGroups[rowKey]?.append(attr) } // 每行从左到右按固定间距重新排列 for (_, rowAttrs) in rowGroups { var currentX: CGFloat = sectionInset.left for attr in rowAttrs.sorted(by: { $0.frame.minX < $1.frame.minX }) { var frame = attr.frame frame.origin.x = currentX attr.frame = frame currentX = frame.maxX + minimumInteritemSpacing } } return attributes } }
修改后的集合视图初始化代码
func setUpCollectionView() { let layout = LeftAlignedHorizontalFlowLayout() layout.scrollDirection = .horizontal // 垂直方向相邻行的间距 layout.minimumLineSpacing = 12 // 同一行内水平方向相邻单元格的间距 layout.minimumInteritemSpacing = 16 // 统一配置section内边距,和原有需求一致 layout.sectionInset = UIEdgeInsets(top: 5, left: 16, bottom: 0, right: 16) collectionView.semanticContentAttribute = .forceLeftToRight collectionView.contentInsetAdjustmentBehavior = .always collectionView.collectionViewLayout = layout }
注意:如果需要支持多section场景,只需要在自定义布局里补充section的x轴偏移计算逻辑即可,当前代码适配单section场景完全符合需求。
内容的提问来源于stack exchange,提问作者Kumar
相关产品推荐
相关产品推荐

