Swift 3中CollectionView间距设置与单元格布局问题咨询
嘿,我来帮你搞定这几个Swift 3里CollectionView的布局问题,都是日常开发里常遇到的,直接上实用方案:
1. 如何设置CollectionView的间距?
在Swift 3中,咱们主要靠UICollectionViewFlowLayout来控制各类间距,核心属性有三个:
minimumInteritemSpacing:同一行内单元格之间的水平间距minimumLineSpacing:不同行之间的垂直间距sectionInset:整个section的内边距(上下左右四个方向)
直接上代码示例,你可以在ViewController的viewDidLoad里设置:
override func viewDidLoad() { super.viewDidLoad() // 获取当前CollectionView的FlowLayout(如果默认用的是这个的话) guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } // 水平单元格间距设为10pt flowLayout.minimumInteritemSpacing = 10 // 垂直行间距设为15pt flowLayout.minimumLineSpacing = 15 // Section内边距:上下左右各10pt flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) }
如果你的CollectionView用了自定义布局,那得在UICollectionViewLayout子类里重写相关布局方法,但大部分场景用FlowLayout就足够了。
2. 三个单元格靠左显示,如何让它们居中?怎么设置每个单元格四边间距一致?
默认FlowLayout是左对齐排列的,要实现居中且四边间距一致,有两种靠谱的方法:
方法一:自定义FlowLayout(灵活适配不同场景)
创建一个继承自UICollectionViewFlowLayout的子类,重写布局属性的计算逻辑,让每行的单元格自动居中:
class CenteredFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let originalAttributes = super.layoutAttributesForElements(in: rect) else { return nil } var adjustedAttributes = [UICollectionViewLayoutAttributes]() var currentRowY: CGFloat = -1.0 var currentRowAttributes = [UICollectionViewLayoutAttributes]() // 先把同一行的单元格归为一组 for attr in originalAttributes { if attr.frame.origin.y != currentRowY { // 处理上一行的单元格居中 if !currentRowAttributes.isEmpty { adjustRowAttributes(currentRowAttributes) adjustedAttributes.append(contentsOf: currentRowAttributes) currentRowAttributes.removeAll() } currentRowY = attr.frame.origin.y } currentRowAttributes.append(attr.copy() as! UICollectionViewLayoutAttributes) } // 处理最后一行 if !currentRowAttributes.isEmpty { adjustRowAttributes(currentRowAttributes) adjustedAttributes.append(contentsOf: currentRowAttributes) } return adjustedAttributes } private func adjustRowAttributes(_ attributes: [UICollectionViewLayoutAttributes]) { guard let collectionView = collectionView else { return } // 计算当前行所有单元格的总宽度(包含间距) let totalCellWidth = attributes.reduce(0) { $0 + $1.frame.width } let totalSpacing = minimumInteritemSpacing * CGFloat(attributes.count - 1) let rowTotalWidth = totalCellWidth + totalSpacing // 计算需要偏移的距离,让行居中 let inset = (collectionView.frame.width - rowTotalWidth - sectionInset.left - sectionInset.right) / 2 guard inset > 0 else { return } // 调整每个单元格的X坐标 var currentX = sectionInset.left + inset for attr in attributes { attr.frame.origin.x = currentX currentX += attr.frame.width + minimumInteritemSpacing } } }
然后给你的CollectionView设置这个自定义布局:
override func viewDidLoad() { super.viewDidLoad() let centeredLayout = CenteredFlowLayout() centeredLayout.minimumInteritemSpacing = 10 centeredLayout.minimumLineSpacing = 10 centeredLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) // 设置单元格固定大小,比如宽100高100 centeredLayout.itemSize = CGSize(width: 100, height: 100) collectionView.collectionViewLayout = centeredLayout }
方法二:动态计算单元格宽度(适合固定3列的场景)
如果确定每行只显示3个单元格,那可以直接计算单元格宽度,让它们加上间距后刚好填满屏幕,自然就居中了:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let padding: CGFloat = 10 // 左右内边距和单元格间距都用10pt let totalPadding = padding * 2 + padding * 2 // 左右内边距 + 3个单元格之间的2个间距 let availableWidth = collectionView.frame.width - totalPadding let itemWidth = availableWidth / 3 return CGSize(width: itemWidth, height: 100) // 高度按需调整 }
别忘了在viewDidLoad里设置FlowLayout的基础属性:
override func viewDidLoad() { super.viewDidLoad() guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } flowLayout.minimumInteritemSpacing = 10 flowLayout.minimumLineSpacing = 10 flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) }
这样三个单元格会均匀分布,四边间距完全一致,不会再靠左啦。
3. 显示“no results found”的单元格如何居中并占满整个屏幕?
这个是数据源为空时的占位单元格场景,要解决左上角截断的问题,得从两个层面入手:
第一步:让单元格占满整个CollectionView
在CollectionView的代理方法里,判断是占位单元格时,返回CollectionView的宽高作为单元格大小,同时去掉section内边距:
// 数据源为空时,只显示一个占位单元格 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return dataSource.isEmpty ? 1 : dataSource.count } // 动态设置单元格大小 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { if dataSource.isEmpty { // 占位单元格占满整个CollectionView return CGSize(width: collectionView.frame.width, height: collectionView.frame.height) } else { // 正常单元格大小 return CGSize(width: 100, height: 100) } } // 动态调整section内边距 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return dataSource.isEmpty ? UIEdgeInsets.zero : UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) }
第二步:让单元格内部的文字居中显示
自定义一个占位单元格,把显示“no results found”的Label约束铺满整个单元格,并且文字居中:
class NoResultsCollectionViewCell: UICollectionViewCell { private let messageLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupUI() } required init?(coder: NSCoder) { super.init(coder: coder) setupUI() } private func setupUI() { messageLabel.text = "no results found" messageLabel.textAlignment = .center messageLabel.textColor = .gray messageLabel.font = UIFont.systemFont(ofSize: 16) messageLabel.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(messageLabel) // 约束Label铺满整个单元格内容区 NSLayoutConstraint.activate([ messageLabel.topAnchor.constraint(equalTo: contentView.topAnchor), messageLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), messageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), messageLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) ]) } }
这样设置后,占位单元格就会完全铺满屏幕,文字也会居中显示,再也不会出现左上角截断的问题了。
内容的提问来源于stack exchange,提问作者Brett Taylor

