前言:之前用Swift 3.0进行实现,现在更新到了Swift 5,添加了多个分组、头部视图、尾部视图、装饰视图的计算,添加了跟随模式,代码没有仔细打磨,大家看看就好啦。
如果觉得文章的描写不够清晰, 可以去看看项目的 Demo
需要对齐cell的原因:
UICollectionView是常用的一个应用于流布局的类, 但是很可惜, 有时候苹果官方提供的方法并不能优美地展示你的UI, 毕竟每个人需要展示的效果可能都不一致. 下面是具体的解析:
首先看官方的对齐方式
创建一个新的项目, 在storyboard的ViewController中拽入UICollectionView定好约束, 然后拽入一个UICollectionViewCell并对Cell进行注册
Cell的代码如下:
class TextCell: UICollectionViewCell {
@IBOutlet weak var textLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
//设置文本标签背景颜色
textLabel.backgroundColor = .hexString("eeeeee")
//设置超出的部分隐藏
textLabel.layer.masksToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
//设置四角的弧度, 设置为高度的一般即是左右为半圆, 效果看下面的效果图
textLabel.layer.cornerRadius = frame.height/2
}
}
修改ViewController
中的代码
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
//文字数据
var dataSource: [String] = ["made","in","China","UIView","UITableView","UICollectionView"]
override func viewDidLoad() {
super.viewDidLoad()
//初始化一个AlignFlowLayout实例
let flowLayout = UICollectionViewFlowLayout()
//设置行间距
flowLayout.minimumLineSpacing = 10
//设置列间距
flowLayout.minimumInteritemSpacing = 10
//设置边界的填充距离
flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
//给collectionView设置布局属性, 也可以通过init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)方法来创建一个UICollectionView对象
collectionView.collectionViewLayout = flowLayout
//设置代理
collectionView.delegate = self
collectionView.dataSource = self
}
}
由于UICollectionViewDelegateFlowLayout
这个协议提供了collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
这个方法, 我们可以实现该方法返回item的size达成cell大小自适应. 下面是协议实现:
//MARK: - UICollectionViewDataSource
//返回对应组中item的个数, Demo中只有一个分组, 所以直接返回个数
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
//返回每个item
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TextCell", for: indexPath) as! TextCell
//设置cell中展示的文字
cell.textLabel.text = dataSource[indexPath.row]
return cell
}
//MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
//返回每个item的size
let text = dataSource[indexPath.row]
let width = text.width(with: .systemFont(ofSize: 14), size: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)) + 30
return CGSize(width: width, height: 30)
}
下面是现在代码的运行效果:
标记图如下, 标记的单位是px, 20px相当于代码中的10:
通过观察可以发现, 在collectionView第一行显示不下
UITableView
这个cell, 自动把它移动到第二行,第一行的cell平铺开, 我们一开始设置的列间距minimumInteritemSpacing = 10
在第一行变得无效了, 因为在官方代码的计算中sectionInset
的优先级比minimumInteritemSpacing
高, 实际显示的列间距变成了(collectionView.frame.size.width-(同一行各item的宽度之和)-sectionInset.left-sectionInset.right)/(item的个数-1)
, 然而这样的显示效果有时候不是我们想要的, 有时候我们就想要minimumInteritemSpacing
这个列间距生效所有cell向左看齐, 在我目前的摸索中还没有发现方便快捷的属性可以直接改变系统默认的对齐方式, 希望有这方面知识的朋友可以指点我.
对齐的实现(宽度一致)
我参考很多优秀的开源代码, 给了我很好的思路, 现在我用Swift 3.0 语言来把自己的想法通过比较简单的代码展现出来.
创建一个枚举, 用于区分对齐方向
/// 对齐方向的枚举, 可拓展, 命名可根据自己喜好
enum AlignDirection: Int {
case start = 0, //左对齐
end, //右对齐 左起显示
dataEnd, //右对齐 右起显示
center, //中间对齐
auto //自动对齐,系统默认效果
}
创建一个类AlignFlowLayout
继承于UICollectionViewFlowLayout
, 该类内部的代码如下:
AlignDelegateFlowLayout协议
protocol AlignDelegateFlowLayout: UICollectionViewDelegateFlowLayout {
//代理方法, 返回collectionView内容的尺寸
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, collectionViewContentSize contentSize: CGSize)
}
给AlignFlowLayout
类创建以下属性
class AlignFlowLayout: UICollectionViewFlowLayout {
/// 默认自动对齐
var direction: AlignDirection = .auto
/// 是否靠边对齐
var isFollow: Bool = false
/// 所有cell的布局属性
private var layoutAttributes: [UICollectionViewLayoutAttributes] = []
/// 每一行cell的布局属性
private var layoutLine: [UICollectionViewLayoutAttributes] = []
/// 滚动范围
private var contentSize: CGSize = CGSize.zero
override var collectionViewContentSize: CGSize {
return contentSize
}
}
继承并修改prepare
方法
override func prepare() {
super.prepare()
// 清空之前的布局属性
layoutAttributes.removeAll()
layoutLine.removeAll()
if let collection = collectionView {
// 获取分组个数
let sections = collection.numberOfSections
// 遍历分组
for i in 0..<sections {
// 获取组内元素个数
let rows = collection.numberOfItems(inSection: i)
// 获取Header的布局属性,‘UICollectionView.elementKindSectionHeader’ 是注册头部视图的字符串,可以替换成自己注册的
if let layoutAttr = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath.init(row: 0, section: i)) {
layoutAttributes.append(layoutAttr)
}
// 遍历获取组内每个元素的布局属性
for j in 0..<rows {
if let layoutAttr = layoutAttributesForItem(at: IndexPath(row: j, section: i)) {
layoutAttributes.append(layoutAttr)
}
}
// 获取Footer的布局属性,‘UICollectionView.elementKindSectionFooter’ 是注册脚部视图的字符串,可以替换成自己注册的
if let layoutAttr = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: i)) {
layoutAttributes.append(layoutAttr)
}
// 添加装饰视图支持,内容需要自定义
// MARK: DecorationView Example
// if let layoutAttr = layoutAttributesForDecorationView(ofKind: kCustomDecorationViewKind, at: IndexPath(row: 0, section: i)) {
// layoutAttributes.append(layoutAttr)
// }
}
}
if let collection = self.collectionView {
var contentWidth: CGFloat = 0, contentHeight: CGFloat = 0
// 逆向遍历
for item in layoutAttributes.reversed() {
// 判断最后一个元素是Footer还是Item, 忽略DecorationView
if item.representedElementCategory == .cell {
contentWidth = item.frame.maxX + sectionInset.right
contentHeight = item.frame.maxY + sectionInset.bottom
break
} else if item.representedElementCategory == .supplementaryView {
contentWidth = item.frame.maxX
contentHeight = item.frame.maxY
break
}
}
// 设置内容尺寸
switch scrollDirection {
case .horizontal:
contentSize = CGSize(width: contentWidth, height: collection.bounds.height)
case .vertical:
contentSize = CGSize(width: collection.bounds.width, height: contentHeight)
default:
contentSize = collection.bounds.size
}
if let alignDelegate = collection.delegate as? AlignDelegateFlowLayout {
alignDelegate.collectionView(collection, layout: self, collectionViewContentSize: contentSize)
}
}
}
继承并修改layoutAttributesForItem(at indexPath: IndexPath)
方法
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if let attribute = super.layoutAttributesForItem(at: indexPath) {
// 判断滚动方向
switch scrollDirection {
case .vertical:
// 竖直滚动,判断对齐方向
switch direction {
case .start, .end, .center:
// 左对齐、右对齐、中间对齐有部分相同的计算步骤
if let last = layoutAttributes.last, let collection = self.collectionView {
if last.representedElementCategory == .cell {
if last.frame.maxX + minimumInteritemSpacing + attribute.frame.width + sectionInset.right < collection.bounds.width {
// 同一行显示
attribute.ex_x = last.frame.maxX + minimumInteritemSpacing
} else {
// 下一行显示
attribute.ex_x = sectionInset.left
}
// 判断是否处于贴紧状态
if isFollow {
// 获取同组的布局属性
let filter = layoutAttributes.filter { (item) -> Bool in
return item.indexPath.section == attribute.indexPath.section && item.representedElementCategory == .cell
}
if filter.isEmpty {
// 如果没有同组布局属性,则现有布局属性为该组的第一个布局属性
attribute.ex_y = last.frame.maxY + self.sectionInset.top
} else {
// 如果有同组布局属性,遍历同组cell,获取cell的最小maxY,记录该cell的minX值,获取同组cell的maxX值
var minY: CGFloat = -1, minX: CGFloat = 0, maxX: CGFloat = 0
_ = filter.map({ (item) in
if item.frame.maxY < minY || minY == -1 {
let sameX = filter.filter { (sameItem) -> Bool in
return sameItem != item && sameItem.frame.minX == item.frame.minX && sameItem.frame.maxY > item.frame.maxY
}
if sameX.isEmpty {
minY = item.frame.maxY
minX = item.frame.minX
}
}
if item.frame.maxX > maxX {
maxX = item.frame.maxX
}
})
// 判断直接添加此cell到collectionView的下方是否越界
if maxX + minimumInteritemSpacing + attribute.frame.width + sectionInset.right > collection.bounds.width {
// 越界,采用贴紧坐标
attribute.ex_x = minX
attribute.ex_y = minY + minimumLineSpacing
} else {
// 不越界,右方直接添加
attribute.ex_x = last.frame.maxX + minimumInteritemSpacing
attribute.ex_y = last.frame.minY
}
}
} else {
// 不处于贴紧状态下,切换行时会调整上一列的布局属性
if attribute.frame.minX < last.frame.minX {
reloadlayoutAttributes()
}
}
} else if last.representedElementCategory == .supplementaryView {
attribute.ex_x = self.sectionInset.left
// 如果上一个布局属性是属于头部或者尾部的,当前布局属性需要增加组内填充距离
if isFollow {
attribute.ex_y = last.frame.maxY + self.sectionInset.top
}
}
} else if isFollow {
// 没有上一个布局属性,当前是第一个cell,直接设置x坐标
attribute.ex_y = self.sectionInset.top
}
// 添加进当前行
layoutLine.append(attribute)
// 判断当前元素是当前组的最后一个元素,重置当前行的布局属性
if let items = collectionView?.numberOfItems(inSection: indexPath.section), indexPath.row == items - 1 {
reloadlayoutAttributes()
}
case .dataEnd:
// 右起显示需要获取collectionView显示区域的宽度
if let collection = collectionView {
if isFollow {
if let last = layoutAttributes.last {
if last.representedElementCategory == .cell {
var minY: CGFloat = -1, minX: CGFloat = 0, leftX: CGFloat = -1
_ = layoutAttributes.map { (item) in
if item.frame.maxY < minY || minY == -1 {
let sameX = layoutAttributes.filter { (sameItem) -> Bool in
return sameItem != item && sameItem.frame.minX == item.frame.minX && sameItem.frame.maxY > item.frame.maxY
}
if sameX.isEmpty {
minX = item.frame.minX
minY = item.frame.maxY
}
}
if item.frame.minX < leftX || leftX == -1 {
leftX = item.frame.minX
}
}
if leftX - minimumInteritemSpacing - attribute.frame.width - sectionInset.left < 0 {
// 越界,切换行显示
attribute.ex_x = minX
attribute.ex_y = minY + minimumInteritemSpacing
} else {
// 不越界,左边方直接添加
attribute.ex_x = last.frame.minX - minimumInteritemSpacing - attribute.frame.width
attribute.ex_y = last.frame.minY
}
} else if last.representedElementCategory == .supplementaryView {
attribute.ex_x = collection.bounds.width - sectionInset.right - attribute.frame.width
attribute.ex_y = last.frame.maxY + sectionInset.top
}
} else {
attribute.ex_x = collection.bounds.width - sectionInset.right - attribute.frame.width
attribute.ex_y = sectionInset.top
}
} else {
if let last = layoutAttributes.last, attribute.frame.minY == last.frame.minY {
// 同行显示,向左逐步显示
attribute.ex_x = last.frame.minX - minimumInteritemSpacing - attribute.frame.width
} else {
// 下一行显示
attribute.ex_x = collection.bounds.width - sectionInset.right - attribute.frame.width
}
}
}
default:
break
}
case .horizontal:
switch direction {
case .start, .center, .end:
// 获取上一个布局属性
if let last = layoutAttributes.last, let collection = self.collectionView {
// 判断是不是cell
if last.representedElementCategory == .cell {
if last.frame.maxY + minimumInteritemSpacing + attribute.frame.height + sectionInset.bottom < collection.bounds.height {
// 同一列显示
attribute.ex_y = last.frame.maxY + minimumInteritemSpacing
} else {
// 下一列显示
attribute.ex_y = sectionInset.top
}
// 判断是否处于贴紧状态
if isFollow {
// 获取同组的布局属性
let filter = layoutAttributes.filter { (item) -> Bool in
return item.indexPath.section == attribute.indexPath.section && item.representedElementCategory == .cell
}
if filter.isEmpty {
// 如果没有同组布局属性,则现有布局属性为该组的第一个布局属性
attribute.ex_x = last.frame.maxX + self.sectionInset.left
} else {
// 如果有同组布局属性,遍历同组cell,获取cell的最小maxX,记录该cell的minY值,获取同组cell的maxY值
var minX: CGFloat = -1, minY: CGFloat = 0, maxY: CGFloat = 0
_ = filter.map({ (item) in
if item.frame.maxX < minX || minX == -1 {
let sameY = filter.filter { (sameItem) -> Bool in
return sameItem != item && sameItem.frame.minY == item.frame.minY && sameItem.frame.maxX > item.frame.maxX
}
if sameY.isEmpty {
minX = item.frame.maxX
minY = item.frame.minY
}
}
if item.frame.maxY > maxY {
maxY = item.frame.maxY
}
})
// 判断直接添加此cell到collectionView的下方是否越界
if maxY + minimumInteritemSpacing + attribute.frame.height + sectionInset.bottom > collection.bounds.height {
// 越界,采用贴紧坐标
attribute.ex_x = minX + minimumLineSpacing
attribute.ex_y = minY
} else {
// 不越界,下方直接添加
attribute.ex_x = last.frame.minX
attribute.ex_y = last.frame.maxY + minimumInteritemSpacing
}
}
} else {
// 不处于贴紧状态下,切换列时会调整上一列的布局属性
if attribute.frame.minX <= last.frame.minX {
reloadlayoutAttributes()
}
}
} else if last.representedElementCategory == .supplementaryView {
attribute.ex_y = self.sectionInset.top
// 如果上一个布局属性是属于头部或者尾部的,当前布局属性需要增加组内填充距离
if isFollow {
attribute.ex_x = last.frame.maxX + self.sectionInset.left
}
}
} else if isFollow {
// 没有上一个布局属性,当前是第一个cell,直接设置x坐标
attribute.ex_x = self.sectionInset.left
}
// 添加进当前列
layoutLine.append(attribute)
// 判断当前元素是当前组的最后一个元素,重置当前列的布局属性
if let items = collectionView?.numberOfItems(inSection: indexPath.section), indexPath.row == items - 1 {
reloadlayoutAttributes()
}
case .dataEnd:
// 下起显示需要获取collectionView显示区域的高度
if let collection = collectionView {
if isFollow {
if let last = layoutAttributes.last {
if last.representedElementCategory == .cell {
var minX: CGFloat = -1, minY: CGFloat = 0, topY: CGFloat = -1
_ = layoutAttributes.map { (item) in
if item.frame.maxX < minX || minX == -1 {
let sameY = layoutAttributes.filter { (sameItem) -> Bool in
return sameItem != item && sameItem.frame.minY == item.frame.minY && sameItem.frame.maxX > item.frame.maxX
}
if sameY.isEmpty {
minX = item.frame.maxX
minY = item.frame.minY
}
}
if item.frame.minY < topY || topY == -1 {
topY = item.frame.minY
}
}
if topY - minimumInteritemSpacing - attribute.frame.height - sectionInset.top < 0 {
// 越界,切换列显示
attribute.ex_x = minX + minimumLineSpacing
attribute.ex_y = minY
} else {
// 不越界,上方直接添加
attribute.ex_x = last.frame.minX
attribute.ex_y = last.frame.minY - minimumInteritemSpacing - attribute.frame.height
}
} else if last.representedElementCategory == .supplementaryView {
attribute.ex_x = last.frame.maxX + sectionInset.left
attribute.ex_y = collection.bounds.height - sectionInset.bottom - attribute.frame.height
}
} else {
attribute.ex_x = sectionInset.left
attribute.ex_y = collection.bounds.height - sectionInset.bottom - attribute.frame.height
}
} else {
if let last = layoutAttributes.last, last.representedElementCategory == .cell, attribute.frame.minX < last.frame.maxX {
// 同列显示,向上逐步显示
attribute.ex_y = last.frame.minY - minimumInteritemSpacing - attribute.frame.height
} else {
// 下一行显示
attribute.ex_y = collection.bounds.height - sectionInset.bottom - attribute.frame.height
}
}
}
default:
break
}
default:
break
}
// 返回新的布局属性
return attribute
}
// 默认布局,返回原始布局属性
return super.layoutAttributesForItem(at: indexPath)
}
继承并修改layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath)
方法
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// 获取头部或尾部视图的布局属性
let attribute = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath)
if elementKind == UICollectionView.elementKindSectionHeader {
//TODO: 第一组的头部暂时用不到修改属性,有需要可以自己修改
if indexPath.section > 0 {
var max: CGFloat = 0
// 获取上一组的布局属性
let filter = layoutAttributes.filter { (item) -> Bool in
return item.indexPath.section == indexPath.section - 1
}
// 判断上一组布局属性是否为空,如果为空暂时不需要修改
if !filter.isEmpty {
// 如果上一组的布局属性不为空,获取新坐标
if scrollDirection == .horizontal {
_ = filter.map({ (item) in
if item.frame.maxX > max {
max = item.frame.maxX
}
})
attribute?.ex_x = max
} else if scrollDirection == .vertical {
_ = filter.map({ (item) in
if item.frame.maxY > max {
max = item.frame.maxY
}
})
attribute?.ex_y = max
}
return attribute
}
}
} else if elementKind == UICollectionView.elementKindSectionFooter {
var max: CGFloat = 0
// 获取同一组cell的布局属性
let filter = layoutAttributes.filter { (item) -> Bool in
return item.indexPath.section == indexPath.section && item.representedElementCategory == .cell
}
if !filter.isEmpty {
// 根据同一组cell的边距来修改footer的位置
if scrollDirection == .horizontal {
_ = filter.map({ (item) in
if item.frame.maxX > max {
max = item.frame.maxX
}
})
attribute?.ex_x = max + sectionInset.right
} else if scrollDirection == .vertical {
_ = filter.map({ (item) in
if item.frame.maxY > max {
max = item.frame.maxY
}
})
attribute?.ex_y = max + sectionInset.bottom
}
return attribute
}
}
return attribute
}
继承并修改layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath)
方法
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath)
let filter = layoutAttributes.filter { (item) -> Bool in
return item.representedElementCategory == .cell && item.indexPath.section == indexPath.section
}
if !filter.isEmpty {
var minX: CGFloat = -1, maxX: CGFloat = 0, minY: CGFloat = -1, maxY: CGFloat = 0
_ = filter.map({ (item) in
if item.frame.minX < minX || minX < 0 {
minX = item.frame.minX
}
if item.frame.maxX > maxX {
maxX = item.frame.maxX
}
if item.frame.minY < minY || minY < 0 {
minY = item.frame.minY
}
if item.frame.maxY > maxY {
maxY = item.frame.maxY
}
})
attribute.frame = CGRect(x: minX - 5, y: minY - 5, width: maxX - minX + 10, height: maxY - minY + 10)
attribute.zIndex = -1
}
return attribute
}
添加reloadlayoutAttributes
方法
func reloadlayoutAttributes() {
if layoutLine.count == 0 {return} //防止越界
if direction == .end || direction == .center, let collection = collectionView {
//计算填充比例, rightFlow为1, center为0.5
let scale: CGFloat = direction == .end ? 1 : 0.5
if isFollow, let first = layoutLine.first {
switch scrollDirection {
case .vertical:
let firstArr = layoutLine.filter { (item) -> Bool in
return item.frame.minY == first.frame.minY
}
var width = CGFloat(firstArr.count - 1) * minimumInteritemSpacing
for item in firstArr {
width += item.frame.width
}
let space = (collection.bounds.width - width - sectionInset.left - sectionInset.right) * scale
for item in firstArr {
let sameArr = layoutLine.filter { (sameItem) -> Bool in
return sameItem.frame.minX == item.frame.minX
}
for sameItem in sameArr {
sameItem.ex_x += space
}
}
case .horizontal:
let firstArr = layoutLine.filter { (item) -> Bool in
return item.frame.minX == first.frame.minX
}
var height = CGFloat(firstArr.count - 1) * minimumInteritemSpacing
for item in firstArr {
height += item.frame.height
}
let space = (collection.bounds.height - height - sectionInset.top - sectionInset.bottom) * scale
for item in firstArr {
let sameArr = layoutLine.filter { (sameItem) -> Bool in
return sameItem.frame.minY == item.frame.minY
}
for sameItem in sameArr {
sameItem.ex_y += space
}
}
default:
break
}
} else {
if let last = layoutLine.last {
//重新绘制布局有右对齐和居中对齐两种
switch scrollDirection {
case .vertical:
let space = (collection.bounds.width - last.frame.maxX - sectionInset.right) * scale
for layout in layoutLine {
layout.ex_x += space
}
case .horizontal:
let space = (collection.bounds.height - last.frame.maxY - sectionInset.bottom) * scale
for layout in layoutLine {
layout.ex_y += space
}
default:
break
}
}
}
}
layoutLine.removeAll()
}
继承并修改layoutAttributesForElements(in rect:CGRect)
方法
override func layoutAttributesForElements(in rect:CGRect) -> [UICollectionViewLayoutAttributes]{
return layoutAttributes
}
添加拓展
extension UICollectionViewLayoutAttributes {
var ex_x: CGFloat {
set {
var newFrame = frame
newFrame.origin.x = newValue
frame = newFrame
} get {
return frame.origin.x
}
}
var ex_y: CGFloat {
set {
var newFrame = frame
newFrame.origin.y = newValue
frame = newFrame
} get {
return frame.origin.y
}
}
}
1. 左对齐
上面的代码中就是左对齐的实现, 调用的代码如下:
//初始化一个AlignFlowLayout实例
let flowLayout = AlignFlowLayout()
//设置方向
flowLayout.direction = .start
//设置行间距
flowLayout.minimumLineSpacing = 10
//设置列间距
flowLayout.minimumInteritemSpacing = 10
//设置边界的填充距离
flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
//注册DecorationView
flowLayout.register(CustomDecorationView.self, forDecorationViewOfKind: kCustomDecorationViewKind)
//给collectionView设置布局属性, 也可以通过init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)方法来创建一个UICollectionView对象
collectionView.collectionViewLayout = flowLayout
实质上就是简单地把UICollectionViewFlowLayout
替换成我们自定义的AlignFlowLayout
, 运行效果如下:
2. 右对齐
右对齐有两种显示方式, 一种是数据源从右到左显示, 另一种是数据源从左到右显示但是UI向右靠拢,因为前面设置了对齐方式是左对齐, 所以现在要修改对齐方式:
flowLayout.direction = .end
运行效果如下:
第二种方式调用是一样的, 修改对齐方式即可:
flowLayout.direction = .dataEnd
下面是效果:
3. 居中对齐
计算思路可以简单地分两种:
1.先用.left方式计算出每行所有item的位置, 然后将右边空白部分的一半宽度填充到左边
2.先用.dataEnd方式计算出每行所有item的位置, 然后将左边空白部分的一半填充到右边
按照计算步骤来考虑, 第一种方法比较好
最终效果如下:
4. 2021年新增内容
多个分组
装饰视图
跟随模式
有时候,使用collectionView会用到不规则的矩形,如果不进行适配会如下显示:
应用跟随模式
flowLayout.isFollow = true
效果如下:
水平方向滚动
注意:
- 尽量不要让item的宽度与sectionInset之和超过collectionView的实际宽度, 否则可能会导致显示出错.
- 跟随模式只适用于竖直滚动模式下宽度一致高度不一致和水平模式滚动下高度一致而宽度不一致的情况,其他情况请关闭