原文作者:艾歐艾斯之手
原文鏈接:http://www.itdecent.cn/p/2ffca6f93c1c
我們都知道UICollectionViewFlowLayout有一個minimumInteritemSpacing屬性可以控制cell之間水平的間距,但是這個屬性并不是你設置成多少它的間距一定是多少,從這個單詞的字面意思就可以看出來它指的是cell之間的最小間距。也就是說cell的間距是大大于或者等于這個屬性的。于是乎,最近就碰到了測試丟過來的問題-UICollectionViewCell之間的布局。
問題重現(xiàn)
關鍵代碼:
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之后,本以為就可以高枕無憂了,但是載入數(shù)據(jù)之后數(shù)據(jù)效果如下圖:

如圖中紅框內(nèi)的間距明顯不是我們想要。
明確需求
現(xiàn)在想要達到的效果是,cell全部向左對齊,間距一定要控制在8.0,不夠就換一行顯示。
如何解決
要達到這樣的效果就只能是修改布局了。先來看一下布局的實現(xiàn):
//
// 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是一個必須要實現(xiàn)的方法,該方法的功能是為布局提供一些必要的初始化參數(shù)
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
currentY = currentY + footerSize.height
}
}
// 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)
}
}
來看看運行效果圖:

確實是達到了想要的效果,希望可以幫助到有同樣需求的同學。