前言:之前用Swift 3.0進(jìn)行實(shí)現(xiàn)兵罢,現(xiàn)在更新到了Swift 5族壳,添加了多個分組这橙、頭部視圖补履、尾部視圖日丹、裝飾視圖的計算鲤桥,添加了跟隨模式坏平,代碼沒有仔細(xì)打磨拢操,大家看看就好啦。
如果覺得文章的描寫不夠清晰, 可以去看看項(xiàng)目的 Demo
需要對齊cell的原因:
UICollectionView是常用的一個應(yīng)用于流布局的類, 但是很可惜, 有時候蘋果官方提供的方法并不能優(yōu)美地展示你的UI, 畢竟每個人需要展示的效果可能都不一致. 下面是具體的解析:
首先看官方的對齊方式
創(chuàng)建一個新的項(xiàng)目, 在storyboard的ViewController中拽入UICollectionView定好約束, 然后拽入一個UICollectionViewCell并對Cell進(jìn)行注冊
Cell的代碼如下:
class TextCell: UICollectionViewCell {
@IBOutlet weak var textLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
//設(shè)置文本標(biāo)簽背景顏色
textLabel.backgroundColor = .hexString("eeeeee")
//設(shè)置超出的部分隱藏
textLabel.layer.masksToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
//設(shè)置四角的弧度, 設(shè)置為高度的一般即是左右為半圓, 效果看下面的效果圖
textLabel.layer.cornerRadius = frame.height/2
}
}
修改ViewController
中的代碼
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
//文字?jǐn)?shù)據(jù)
var dataSource: [String] = ["made","in","China","UIView","UITableView","UICollectionView"]
override func viewDidLoad() {
super.viewDidLoad()
//初始化一個AlignFlowLayout實(shí)例
let flowLayout = UICollectionViewFlowLayout()
//設(shè)置行間距
flowLayout.minimumLineSpacing = 10
//設(shè)置列間距
flowLayout.minimumInteritemSpacing = 10
//設(shè)置邊界的填充距離
flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
//給collectionView設(shè)置布局屬性, 也可以通過init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)方法來創(chuàng)建一個UICollectionView對象
collectionView.collectionViewLayout = flowLayout
//設(shè)置代理
collectionView.delegate = self
collectionView.dataSource = self
}
}
由于UICollectionViewDelegateFlowLayout
這個協(xié)議提供了collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
這個方法, 我們可以實(shí)現(xiàn)該方法返回item的size達(dá)成cell大小自適應(yīng). 下面是協(xié)議實(shí)現(xiàn):
//MARK: - UICollectionViewDataSource
//返回對應(yīng)組中item的個數(shù), Demo中只有一個分組, 所以直接返回個數(shù)
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
//設(shè)置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)
}
下面是現(xiàn)在代碼的運(yùn)行效果:
標(biāo)記圖如下, 標(biāo)記的單位是px, 20px相當(dāng)于代碼中的10:
通過觀察可以發(fā)現(xiàn), 在collectionView第一行顯示不下
UITableView
這個cell, 自動把它移動到第二行,第一行的cell平鋪開, 我們一開始設(shè)置的列間距minimumInteritemSpacing = 10
在第一行變得無效了, 因?yàn)樵诠俜酱a的計算中sectionInset
的優(yōu)先級比minimumInteritemSpacing
高, 實(shí)際顯示的列間距變成了(collectionView.frame.size.width-(同一行各item的寬度之和)-sectionInset.left-sectionInset.right)/(item的個數(shù)-1)
, 然而這樣的顯示效果有時候不是我們想要的, 有時候我們就想要minimumInteritemSpacing
這個列間距生效所有cell向左看齊, 在我目前的摸索中還沒有發(fā)現(xiàn)方便快捷的屬性可以直接改變系統(tǒng)默認(rèn)的對齊方式, 希望有這方面知識的朋友可以指點(diǎn)我.
對齊的實(shí)現(xiàn)(寬度一致)
我參考很多優(yōu)秀的開源代碼, 給了我很好的思路, 現(xiàn)在我用Swift 3.0 語言來把自己的想法通過比較簡單的代碼展現(xiàn)出來.
創(chuàng)建一個枚舉, 用于區(qū)分對齊方向
/// 對齊方向的枚舉, 可拓展, 命名可根據(jù)自己喜好
enum AlignDirection: Int {
case start = 0, //左對齊
end, //右對齊 左起顯示
dataEnd, //右對齊 右起顯示
center, //中間對齊
auto //自動對齊舶替,系統(tǒng)默認(rèn)效果
}
創(chuàng)建一個類AlignFlowLayout
繼承于UICollectionViewFlowLayout
, 該類內(nèi)部的代碼如下:
AlignDelegateFlowLayout協(xié)議
protocol AlignDelegateFlowLayout: UICollectionViewDelegateFlowLayout {
//代理方法, 返回collectionView內(nèi)容的尺寸
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, collectionViewContentSize contentSize: CGSize)
}
給AlignFlowLayout
類創(chuàng)建以下屬性
class AlignFlowLayout: UICollectionViewFlowLayout {
/// 默認(rèn)自動對齊
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 {
// 獲取分組個數(shù)
let sections = collection.numberOfSections
// 遍歷分組
for i in 0..<sections {
// 獲取組內(nèi)元素個數(shù)
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)
}
// 遍歷獲取組內(nèi)每個元素的布局屬性
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)
}
// 添加裝飾視圖支持,內(nèi)容需要自定義
// 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
}
}
// 設(shè)置內(nèi)容尺寸
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
}
// 判斷是否處于貼緊狀態(tài)
if isFollow {
// 獲取同組的布局屬性
let filter = layoutAttributes.filter { (item) -> Bool in
return item.indexPath.section == attribute.indexPath.section && item.representedElementCategory == .cell
}
if filter.isEmpty {
// 如果沒有同組布局屬性钉跷,則現(xiàn)有布局屬性為該組的第一個布局屬性
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 {
// 越界,采用貼緊坐標(biāo)
attribute.ex_x = minX
attribute.ex_y = minY + minimumLineSpacing
} else {
// 不越界膝晾,右方直接添加
attribute.ex_x = last.frame.maxX + minimumInteritemSpacing
attribute.ex_y = last.frame.minY
}
}
} else {
// 不處于貼緊狀態(tài)下栓始,切換行時會調(diào)整上一列的布局屬性
if attribute.frame.minX < last.frame.minX {
reloadlayoutAttributes()
}
}
} else if last.representedElementCategory == .supplementaryView {
attribute.ex_x = self.sectionInset.left
// 如果上一個布局屬性是屬于頭部或者尾部的,當(dāng)前布局屬性需要增加組內(nèi)填充距離
if isFollow {
attribute.ex_y = last.frame.maxY + self.sectionInset.top
}
}
} else if isFollow {
// 沒有上一個布局屬性血当,當(dāng)前是第一個cell幻赚,直接設(shè)置x坐標(biāo)
attribute.ex_y = self.sectionInset.top
}
// 添加進(jìn)當(dāng)前行
layoutLine.append(attribute)
// 判斷當(dāng)前元素是當(dāng)前組的最后一個元素,重置當(dāng)前行的布局屬性
if let items = collectionView?.numberOfItems(inSection: indexPath.section), indexPath.row == items - 1 {
reloadlayoutAttributes()
}
case .dataEnd:
// 右起顯示需要獲取collectionView顯示區(qū)域的寬度
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
}
// 判斷是否處于貼緊狀態(tài)
if isFollow {
// 獲取同組的布局屬性
let filter = layoutAttributes.filter { (item) -> Bool in
return item.indexPath.section == attribute.indexPath.section && item.representedElementCategory == .cell
}
if filter.isEmpty {
// 如果沒有同組布局屬性巍扛,則現(xiàn)有布局屬性為該組的第一個布局屬性
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 {
// 越界,采用貼緊坐標(biāo)
attribute.ex_x = minX + minimumLineSpacing
attribute.ex_y = minY
} else {
// 不越界胧瓜,下方直接添加
attribute.ex_x = last.frame.minX
attribute.ex_y = last.frame.maxY + minimumInteritemSpacing
}
}
} else {
// 不處于貼緊狀態(tài)下矢棚,切換列時會調(diào)整上一列的布局屬性
if attribute.frame.minX <= last.frame.minX {
reloadlayoutAttributes()
}
}
} else if last.representedElementCategory == .supplementaryView {
attribute.ex_y = self.sectionInset.top
// 如果上一個布局屬性是屬于頭部或者尾部的,當(dāng)前布局屬性需要增加組內(nèi)填充距離
if isFollow {
attribute.ex_x = last.frame.maxX + self.sectionInset.left
}
}
} else if isFollow {
// 沒有上一個布局屬性府喳,當(dāng)前是第一個cell蒲肋,直接設(shè)置x坐標(biāo)
attribute.ex_x = self.sectionInset.left
}
// 添加進(jìn)當(dāng)前列
layoutLine.append(attribute)
// 判斷當(dāng)前元素是當(dāng)前組的最后一個元素,重置當(dāng)前列的布局屬性
if let items = collectionView?.numberOfItems(inSection: indexPath.section), indexPath.row == items - 1 {
reloadlayoutAttributes()
}
case .dataEnd:
// 下起顯示需要獲取collectionView顯示區(qū)域的高度
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
}
// 默認(rèn)布局,返回原始布局屬性
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 {
// 如果上一組的布局屬性不為空,獲取新坐標(biāo)
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 {
// 根據(jù)同一組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. 左對齊
上面的代碼中就是左對齊的實(shí)現(xiàn), 調(diào)用的代碼如下:
//初始化一個AlignFlowLayout實(shí)例
let flowLayout = AlignFlowLayout()
//設(shè)置方向
flowLayout.direction = .start
//設(shè)置行間距
flowLayout.minimumLineSpacing = 10
//設(shè)置列間距
flowLayout.minimumInteritemSpacing = 10
//設(shè)置邊界的填充距離
flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10)
//注冊DecorationView
flowLayout.register(CustomDecorationView.self, forDecorationViewOfKind: kCustomDecorationViewKind)
//給collectionView設(shè)置布局屬性, 也可以通過init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout)方法來創(chuàng)建一個UICollectionView對象
collectionView.collectionViewLayout = flowLayout
實(shí)質(zhì)上就是簡單地把UICollectionViewFlowLayout
替換成我們自定義的AlignFlowLayout
, 運(yùn)行效果如下:
2. 右對齊
右對齊有兩種顯示方式, 一種是數(shù)據(jù)源從右到左顯示, 另一種是數(shù)據(jù)源從左到右顯示但是UI向右靠攏路鹰,因?yàn)榍懊嬖O(shè)置了對齊方式是左對齊, 所以現(xiàn)在要修改對齊方式:
flowLayout.direction = .end
運(yùn)行效果如下:
第二種方式調(diào)用是一樣的, 修改對齊方式即可:
flowLayout.direction = .dataEnd
下面是效果:
3. 居中對齊
計算思路可以簡單地分兩種:
1.先用.left方式計算出每行所有item的位置, 然后將右邊空白部分的一半寬度填充到左邊
2.先用.dataEnd方式計算出每行所有item的位置, 然后將左邊空白部分的一半填充到右邊
按照計算步驟來考慮, 第一種方法比較好
最終效果如下:
4. 2021年新增內(nèi)容
多個分組
裝飾視圖
跟隨模式
有時候贷洲,使用collectionView會用到不規(guī)則的矩形,如果不進(jìn)行適配會如下顯示:
應(yīng)用跟隨模式
flowLayout.isFollow = true
效果如下:
水平方向滾動
注意:
- 盡量不要讓item的寬度與sectionInset之和超過collectionView的實(shí)際寬度, 否則可能會導(dǎo)致顯示出錯.
- 跟隨模式只適用于豎直滾動模式下寬度一致高度不一致和水平模式滾動下高度一致而寬度不一致的情況晋柱,其他情況請關(guān)閉