Swift iOS实现CollectionView单元格左对齐、等间距及动态宽度
解决CollectionView左对齐、等间距+单元格动态宽度的问题
1. 自定义WrapFlowLayout核心实现
替换你现有的WrapFlowLayout,以下实现会正确计算每个item的动态宽度,同时保证左对齐、行内item等间距并自动换行:
import UIKit class CustomWrapFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil } var lineAttributes: [UICollectionViewLayoutAttributes] = [] var previousY: CGFloat = -1 for attribute in attributes { // 触发新行逻辑 if attribute.frame.origin.y != previousY { // 处理上一行的间距分配 if !lineAttributes.isEmpty { adjustLineSpacing(for: lineAttributes) lineAttributes.removeAll() } previousY = attribute.frame.origin.y } lineAttributes.append(attribute) } // 处理最后一行 if !lineAttributes.isEmpty { adjustLineSpacing(for: lineAttributes) } return attributes } // 调整行内item间距,实现左对齐+等间距 private func adjustLineSpacing(for attributes: [UICollectionViewLayoutAttributes]) { guard let collectionView = collectionView else { return } // 计算行内所有item的总宽度 let totalItemWidth = attributes.reduce(0) { $0 + $1.frame.width } // 剩余可分配空间 = 容器宽度 - 左右内边距 - 总item宽度 let remainingSpace = collectionView.bounds.width - sectionInset.left - sectionInset.right - totalItemWidth // 平均分配剩余空间为item间距(单个item时间距为0) let spacing = attributes.count > 1 ? remainingSpace / CGFloat(attributes.count - 1) : 0 var previousX = sectionInset.left for attribute in attributes { attribute.frame.origin.x = previousX previousX = attribute.frame.maxX + spacing } } // 布局随容器尺寸变化自动更新 override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } }
2. 修复TrendingViewCell的宽度计算逻辑
确保Cell能根据内容字符串准确计算自身宽度,修改TrendingViewCell代码:
class TrendingViewCell: UICollectionViewCell { static let identifier = "TrendingViewCell" private let titleLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.systemFont(ofSize: 14) label.textColor = .black label.numberOfLines = 1 return label }() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(titleLabel) setupConstraints() // 按需添加样式(圆角、背景色等) contentView.backgroundColor = .lightGray contentView.layer.cornerRadius = 12 } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupConstraints() { NSLayoutConstraint.activate([ titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12), titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8) ]) } func configure(with title: String) { titleLabel.text = title } // 核心:计算Cell的动态宽度(内容宽度+内边距) func calculateCellWidth() -> CGFloat { guard let text = titleLabel.text else { return 0 } let labelSize = titleLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: contentView.bounds.height)) // 加上Cell左右内边距(对应leading和trailing的12+12) return labelSize.width + 24 } }
3. 在TrendingView中配置CollectionView
修改你的TrendingView代码,绑定自定义布局并实现动态尺寸返回:
class TrendingView: UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { private let collectionView: UICollectionView private let data: [String] = ["SwiftUI", "iOS开发", "Objective-C", "Flutter", "React Native", "WWDC2024"] override init(frame: CGRect) { // 初始化自定义布局,设置行间距和内边距 let layout = CustomWrapFlowLayout() layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) layout.minimumLineSpacing = 12 collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) super.init(frame: frame) setupCollectionView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupCollectionView() { collectionView.translatesAutoresizingMaskIntoConstraints = false addSubview(collectionView) collectionView.dataSource = self collectionView.delegate = self collectionView.register(TrendingViewCell.self, forCellWithReuseIdentifier: TrendingViewCell.identifier) collectionView.backgroundColor = .white NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: topAnchor), collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), collectionView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } // MARK: - UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return data.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TrendingViewCell.identifier, for: indexPath) as? TrendingViewCell else { return UICollectionViewCell() } cell.configure(with: data[indexPath.item]) return cell } // MARK: - UICollectionViewDelegateFlowLayout func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let cell = TrendingViewCell() cell.configure(with: data[indexPath.item]) // 返回动态宽度+固定高度(根据设计图调整) let width = cell.calculateCellWidth() return CGSize(width: width, height: 32) } }
关键注意事项
- 自定义布局的
adjustLineSpacing方法是核心逻辑:通过计算行内总宽度,将剩余空间平均分配为item间距,实现左对齐+等间距效果。 - Cell的
calculateCellWidth必须包含内容宽度和内边距,确保sizeForItemAt返回准确尺寸。 - 若Cell包含额外视图(如图标),需在宽度计算中加入对应视图的宽度。
内容的提问来源于stack exchange,提问作者Abhishek C. Gidde
相关产品推荐
相关产品推荐

