我们都知道UICollectionViewFlowLayout有一个minimumInteritemSpacing属性可以控制cell之间水平的间距,但是这个属性并不是你设置成多少它的间距一定是多少,从这个单词的字面意思就可以看出来它指的是cell之间的最小间距。也就是说cell的间距是大大于或者等于这个属性的。于是乎,最近就碰到了测试丢过来的问题-UICollectionViewCell之间的布局。
问题重现
关键代码:
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 8.0
layout.minimumInteritemSpacing = 8.0
layout.sectionInset = UIEdgeInsets(top: 25, left: 15, bottom: 30, right: 15)
let collection = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collection.backgroundColor = UIColor.white
collection.delegate = self
collection.dataSource = self
collection.register(UINib(nibName: "CollectionCell", bundle: Bundle.main),forCellWithReuseIdentifier: "CollectionCell")
self.view.addSubview(collection)
以上关键代码是layout.minimumInteritemSpacing = 8.0,把cell间距设置成8之后,本以为就可以高枕无忧了,但是载入数据之后数据效果如下图:如图中红框内的间距明显不是我们想要。
明确需求
现在想要达到的效果是,cell全部向左对齐,间距一定要控制在8.0,不够就换一行显示。
如何解决
要达到这样的效果就只能是修改布局了。先来看一下布局的实现:
//
// DVMaximumSpacingLayout.swift
// Amall
// 一个可以设置cell之间最大间距的布局,用于商品详情的属性
// Created by David Yu on 2018/4/26.
// Copyright © 2018年 David. All rights reserved.
//
import UIKit
// MARK:- DVMaximumSpacingLayout代理
@objc protocol DVMaximumSpacingLayoutDelegate {
func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
@objc optional func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
@objc optional func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, referenceSizeForFooterInSection section: Int) -> CGSize
}
class DVMaximumSpacingLayout: UICollectionViewLayout {
/// cell最大水平间距
var MaximumSpacing: CGFloat = 0.0
/// cell竖直间距
var minimumLineSpacingForSection: CGFloat = 0.0
/// 间距
var sectionEdgeInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
/// cell布局
var cellAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
/// 头视图布局
var headerAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
/// 尾视图布局
var footerAttributes = [IndexPath: UICollectionViewLayoutAttributes]()
/// 代理
var delegate: DVMaximumSpacingLayoutDelegate?
/// 当前Y坐标
var currentY : CGFloat = 0
// MARK:- prepareLayout是一个必须要实现的方法,该方法的功能是为布局提供一些必要的初始化参数
override func prepare() {
super.prepare()
cellAttributes.removeAll()
headerAttributes.removeAll()
footerAttributes.removeAll()
currentY = 0
// 一共有多少section
let sectionNum = self.collectionView?.numberOfSections ?? 0
for i in 0..<sectionNum {
let supplementaryViewIndex = IndexPath(row: 0, section: i)
// 计算设置每个header的布局对象
let headerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: supplementaryViewIndex)
let headerSize = delegate?.collectionView?(collectionView, layout: self, referenceSizeForHeaderInSection: i) ?? CGSize(width: 0, height: 0)
headerAttribute.frame = CGRect(origin: CGPoint(x: 0, y: currentY), size: headerSize)
headerAttributes[supplementaryViewIndex] = headerAttribute
currentY = headerAttribute.frame.maxY + sectionEdgeInsets.top
// 计算设置每个cell的布局对象
// 该section一共有多少row
let rowNum = self.collectionView?.numberOfItems(inSection: i) ?? 0
var currentX = sectionEdgeInsets.left
for j in 0..<rowNum {
let cellIndex = IndexPath(row: j, section: i)
let cellAttribute = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
let cellSize = delegate?.collectionView(collectionView, layout: self, sizeForItemAt: cellIndex) ?? CGSize(width: 0, height: 0)
if currentX + cellSize.width + sectionEdgeInsets.right > collectionView?.frame.width ?? 0 {
// 超过collectview换行,并且collectionview的高度增加
currentX = sectionEdgeInsets.left
currentY = currentY + cellSize.height + minimumLineSpacingForSection
}
cellAttribute.frame = CGRect(origin: CGPoint(x: currentX, y: currentY), size: cellSize)
currentX = currentX + cellSize.width + MaximumSpacing
cellAttributes[cellIndex] = cellAttribute
if j == rowNum - 1 {
currentY = currentY + cellSize.height + sectionEdgeInsets.bottom
}
}
// 计算每个footer的布局对象
let footerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, with: supplementaryViewIndex)
let footerSize = delegate?.collectionView?(collectionView, layout: self, referenceSizeForFooterInSection: i) ?? CGSize(width: 0, height: 0)
footerAttribute.frame = CGRect(origin: CGPoint(x: 0, y: currentY), size: footerSize)
footerAttributes[supplementaryViewIndex] = footerAttribute
}
}
// MARK:- 当前屏幕可见的cell、header、footer的布局
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = [UICollectionViewLayoutAttributes]()
// 添加当前屏幕可见的cell的布局
for element in cellAttributes.values {
if rect.contains(element.frame) {
attributes.append(element)
}
}
// 添加当前屏幕可见的头视图的布局
for element in headerAttributes.values {
if rect.contains(element.frame) {
attributes.append(element)
}
}
// 添加当前屏幕可见的尾部的布局
for element in footerAttributes.values {
if rect.contains(element.frame) {
attributes.append(element)
}
}
return attributes
}
// MARK:- 该方法是为每个Cell返回一个对应的Attributes,我们需要在该Attributes中设置对应的属性,如Frame等
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttributes[indexPath]
}
// MARK:- 该方法是为每个头和尾返回一个对应的Attributes,我们需要在该Attributes中设置对应的属性,如Frame等
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
var attr: UICollectionViewLayoutAttributes?
if elementKind == UICollectionElementKindSectionHeader {
attr = headerAttributes[indexPath]
} else {
attr = footerAttributes[indexPath]
}
return attr
}
// MARK:- 设置滚动范围
override var collectionViewContentSize: CGSize {
let width = collectionView?.frame.width ?? 0
return CGSize(width: width, height: currentY)
}
}
然后使用几乎和原先差不多,只是布局的代理方法需要更换成自定义布局的代理方法
//
// ViewController.swift
// UICollectionLayout
//
// Created by David Yu on 2018/4/27.
// Copyright © 2018年 David Yu. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
let titles = ["测试测试测试测试","测试测试测试测试","试测试试测试试测试试测试","试测试试测试试测试试测试试测试试测试","试测试试测试试测试试测试","试测试试测试试测试试测试试测试","试测试试测试试测试试测试试测试试测试","试测试试测试试测试试测试","试测试试测试试测试","试测试试测试试测试试测试试测试","试测试试测试试测试试测试","试测试试测试试测试","试测试","试测试试测试试测试试测试试测试试测试试测试","试测试试测试试测试试测试试测试","试测试试测试试测试"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setView()
}
func setView() {
self.view.backgroundColor = UIColor.white
let layout = DVMaximumSpacingLayout()
layout.MaximumSpacing = 12.0
layout.minimumLineSpacingForSection = 8.0
layout.sectionEdgeInsets = UIEdgeInsets(top: 12, left: 15, bottom: 12, right: 15)
layout.delegate = self
let collection = UICollectionView(frame: CGRect(x: 0, y: 64, width: self.view.frame.width, height: self.view.frame.height-64), collectionViewLayout: layout)
collection.backgroundColor = UIColor.white
collection.delegate = self
collection.dataSource = self
collection.register(UINib(nibName: "CollectionCell", bundle: Bundle.main), forCellWithReuseIdentifier: "CollectionCell")
self.view.addSubview(collection)
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, DVMaximumSpacingLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return titles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
cell.title = titles[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView?, layout collectionViewLayout: DVMaximumSpacingLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let title = titles[indexPath.row]
let size = title.getSize(UIFont.systemFont(ofSize: 17), size: CGSize(width: UIScreen.main.bounds.width, height: 26))
return CGSize(width: size.width + 20, height: 26)
}
}
来看看运行效果图:
确实是达到了想要的效果,希望可以帮助到有同样需求的同学。