前言:之前用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的實際寬度, 否則可能會導致顯示出錯.
- 跟隨模式只適用于豎直滾動模式下寬度一致高度不一致和水平模式滾動下高度一致而寬度不一致的情況,其他情況請關閉