使用Swift實(shí)現(xiàn)簡(jiǎn)單的瀑布流,OC的實(shí)現(xiàn)和Swift大同小異互亮,主要還是了解一下實(shí)現(xiàn)思路犁享,我這里做了三種效果:
- 豎向滑動(dòng),不帶Header和Footer豹休;
- 豎向滑動(dòng)炊昆,帶自定義Header和Footer;
- 橫向滑動(dòng)威根,不帶Header和Footer凤巨;
效果如下:
蘋果為我們提供的UICollectionView可以很方便的實(shí)現(xiàn)很多不同的布局效果,其中它的布局精髓在于UICollectionViewLayout類医窿,所有的item布局在這里邊進(jìn)行磅甩。因此我們可以自定義一個(gè)類來繼承UICollectionViewLayout,重寫UICollectionViewLayout類中的一些函數(shù)/屬性姥卢,來實(shí)現(xiàn)我們想要的瀑布流布局(自定義的這個(gè)類可參照UICollectionViewFlowLayout類)卷要。
- JYWaterfallFlowLayout 實(shí)現(xiàn)思路:
- 新建一個(gè)繼承UICollectionViewLayout類的文件JYWaterfallFlowLayout;
- 新建一個(gè)協(xié)議類JYWaterfallFlowLayoutProtocol独榴,聲明一些函數(shù)用于變量設(shè)置的回調(diào)定義我們布局時(shí)需要的常量/變量等僧叉;
- 定義私有/公有的變量/常量等,并賦默認(rèn)值棺榔;
- 重寫UICollectionViewLayout中的一些方法/變量瓶堕,對(duì)item、header症歇、footer進(jìn)行布局計(jì)算等郎笆;
/// 這里我們先了解一下需要重寫的函數(shù)/屬性的作用
/// 準(zhǔn)備布局,初始化一些信息和所有布局(在UICollectionView布局(或重新布局)時(shí)會(huì)調(diào))
override func prepare() {}
/// 獲取 rect 范圍內(nèi)的所有 item 的布局忘晤,并返回計(jì)算好的布局結(jié)果
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {}
/// 自定義 item 的布局
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {}
/// 自定義 header/footer 的布局
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {}
/// 設(shè)置 collectionVIew 的 contentSize(滾動(dòng)范圍)
override var collectionViewContentSize: CGSize
下面就讓我們進(jìn)入自定義布局重點(diǎn)代碼:
- 定義我們需要的協(xié)議方法
JYWaterfallFlowLayoutProtocol.swift 文件中
這里的協(xié)議函數(shù)是參照UICollectionViewFlowLayout定義的
注意:swift定義協(xié)議的可選函數(shù)時(shí)必須添加“@ objc”關(guān)鍵字
@objc protocol JYWaterfallFlowLayoutDelegate: NSObjectProtocol {
// item 的 size (寬高轉(zhuǎn)換:WaterfallFlowVertical根據(jù)寬算高宛蚓,WaterfallFlowHorizontal根據(jù)高算寬)
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, heightForItemAt indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, heightForItemAt indexPath: IndexPath, itemHeight: CGFloat) -> CGFloat
// header/footer 的 size(僅限豎向滑動(dòng)時(shí)使用)
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, referenceSizeForFooterInSection section:Int) -> CGSize
// 每個(gè) section 的內(nèi)邊距
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, insetForSectionAt section: Int) -> UIEdgeInsets
// 每個(gè) section 下顯示的 item 有多少列,返回每個(gè) section 下的 item 的列數(shù)
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, columnNumberAt section:Int) -> Int
// 每個(gè) section 下顯示的 item 的最小行間距
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
// 每個(gè) section 下顯示的 item 的最小列間距
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
// 本 section 的頭部和上個(gè) section 的尾部的間距
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: JYWaterfallFlowLayout, spacingWithPreviousSectionForSectionAt section: Int) -> CGFloat
}
以下代碼在JYWaterfallFlowLayout.swift 文件中
- 定義一個(gè)瀑布流滑動(dòng)方向的枚舉
/**
* 枚舉:collectionView 滑動(dòng)方向
**/
enum UICollectionViewScrollDirection {
case vertical // 豎向滑動(dòng)
case horizontal // 橫向滑動(dòng)
}
- 定義需要的常量和變量
class JYWaterfallFlowLayout: UICollectionViewLayout {
/**
* MARK: - 定義變量
**/
/// 屏幕的寬高
fileprivate let screenWidth = UIScreen.main.bounds.size.width
fileprivate let screenHeight = UIScreen.main.bounds.size.height
public var NavigationHeight: CGFloat = 0
public var iPhoneXBottomHeight: CGFloat = 0
/// collectionView 相關(guān)
// 記錄 collectionView 的 content 可滑動(dòng)的范圍
fileprivate var contentScope: CGFloat = 0
// collectionView 的滑動(dòng)方向设塔,默認(rèn)為豎向滑動(dòng)
public var scrollDirection: UICollectionViewScrollDirection = .vertical
/// item 相關(guān)
// item 的行/列數(shù)凄吏,默認(rèn)為2行/列
public var lineCount: Int = 2
public var interitemCount: Int = 2
// item 之間的間距(行/列),默認(rèn)間距為10
public var lineSpacing: CGFloat = 10
public var interitemSpacing: CGFloat = 10
/// section 相關(guān)
// section 的內(nèi)邊距,默認(rèn)上下左右都為10
public var sectionInset: UIEdgeInsets = UIEdgeInsets.zero
// 是否要顯示 header/footer痕钢,默認(rèn)不顯示
public var isShowHeader: Bool = false
public var isShowFooter: Bool = false
// section 的 header/footer 的 size
public var headerReferenceSize: CGSize = CGSize.zero
public var footerReferenceSize: CGSize = CGSize.zero
/// 數(shù)據(jù)處理相關(guān)
// 存儲(chǔ) item 的所有 layoutAttributes 數(shù)組
fileprivate lazy var layoutAttributesArray: [UICollectionViewLayoutAttributes] = []
// 存儲(chǔ)橫向滑動(dòng)時(shí) section 每一行的寬度/豎向滑動(dòng)時(shí) section 每一列的高度
fileprivate lazy var lineWidthArray: [CGFloat] = []
fileprivate lazy var interitemHeightArray: [CGFloat] = []
/// 協(xié)議代理
weak var delegate: JYWaterfallFlowLayoutDelegate?
}
- 重寫UICollectionViewLayout類中的一些函數(shù)/屬性
重寫prepare() 函數(shù)
/**
* MARK: - 重寫UICollectionViewLayout中的一些方法
**/
extension JYWaterfallFlowLayout {
/// 為自定義布局做些準(zhǔn)備操作(在collectionView重新布局時(shí)图柏,總會(huì)調(diào)用此方法)
override func prepare() {
super.prepare()
/// 初始化數(shù)據(jù)
// 清空數(shù)組中之前保存的所有布局?jǐn)?shù)據(jù)
layoutAttributesArray.removeAll()
self.contentScope = 0.0
// 設(shè)置代理
if self.delegate == nil {
self.delegate = self.collectionView?.delegate as? JYWaterfallFlowLayoutDelegate
}
/// 計(jì)算 section 下 item/header/footer 的布局
setAllLayoutsForSection()
}
}
自定義函數(shù),用于分解prepare()函數(shù)里的代碼
/*
* 設(shè)置 section 下的 item/header/footer 的布局任连,并緩存
*/
fileprivate func setAllLayoutsForSection() {
// 獲取 collectionView 中 section 的個(gè)數(shù)
let getSectionCount = self.collectionView?.numberOfSections
guard let sectionCount = getSectionCount else { return }
// 遍歷 section 下的所有 item/header/footer蚤吹,并計(jì)算出所有 item/header/footer 的布局
for i in 0..<sectionCount {
// 獲取 NSIndexPath
let indexPath = NSIndexPath(index: i)
// 這里獲取到的 IndexPath 是個(gè)數(shù)組,取其內(nèi)容要用 indexPath.first/indexPath[0]随抠,不能用 indexPath.section距辆,否則會(huì) crash
// let indexPath = IndexPath(index: i)
// 通過代理調(diào)用協(xié)議方法,更新一些變量的值
invokeProxy(inSection: indexPath.section)
// 設(shè)置 header 的布局暮刃,并緩存
if isShowHeader {
let headerAttributesArray = supplementaryViewAttributes(ofKind: UICollectionElementKindSectionHeader, indexPath: indexPath as IndexPath)
self.layoutAttributesArray.append(contentsOf: headerAttributesArray)
}
// 清空數(shù)組中之前緩存的高度跨算,留待使用(給下面的 item 計(jì)算 y 坐標(biāo)時(shí)使用 -- 必須寫在header后面,否則會(huì)計(jì)算錯(cuò)誤)
interitemHeightArray.removeAll()
lineWidthArray.removeAll()
for _ in 0..<interitemCount {
// 判斷是橫向滑動(dòng)還是豎向滑動(dòng)
if scrollDirection == .horizontal {
// 緩存 collectionView 的 content 的寬度椭懊,為 item 的 x 坐標(biāo)開始的位置
self.lineWidthArray.append(contentScope)
} else {
// 緩存 collectionView 的 content 的高度诸蚕,為 item 的 y 坐標(biāo)開始的位置
self.interitemHeightArray.append(contentScope)
}
}
// 設(shè)置 item 的布局,并緩存
let itemAttributesArray = itemAttributes(inSection: indexPath.section)
self.layoutAttributesArray.append(contentsOf: itemAttributesArray)
// 設(shè)置 footer 的布局氧猬,并緩存
if isShowFooter {
let footerAttributesArray = supplementaryViewAttributes(ofKind: UICollectionElementKindSectionFooter, indexPath: indexPath as IndexPath)
self.layoutAttributesArray.append(contentsOf: footerAttributesArray)
}
}
}
調(diào)用代理方法重新給變量設(shè)置的新值(若已經(jīng)在外部實(shí)現(xiàn)了協(xié)議函數(shù)的前提下)
/*
* 調(diào)用代理方法
*/
fileprivate func invokeProxy(inSection section: Int) {
/// 返回 section 下 item 的列數(shù)
if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:columnNumberAt:)))) ?? false) {
self.interitemCount = (delegate?.collectionView!(self.collectionView!, layout: self, columnNumberAt: section)) ?? interitemCount
}
/// 返回 section 的內(nèi)邊距
if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:insetForSectionAt:)))) ?? false) {
self.sectionInset = (delegate?.collectionView!(self.collectionView!, layout: self, insetForSectionAt: section)) ?? sectionInset
}
/// 返回當(dāng)前 section 的 header 與上個(gè) section 的 footer 之間的間距
if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:spacingWithPreviousSectionForSectionAt:)))) ?? false) {
self.spacingWithPreviousSection = (delegate?.collectionView!(self.collectionView!, layout: self, spacingWithPreviousSectionForSectionAt: section)) ?? spacingWithPreviousSection
}
/// 返回 section 下的 item 之間的最小行間距
if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:minimumLineSpacingForSectionAt:)))) ?? false) {
self.lineSpacing = (delegate?.collectionView!(self.collectionView!, layout: self, minimumLineSpacingForSectionAt: section)) ?? lineSpacing
}
/// 返回 section 下的 item 之間的最小列間距
if (delegate != nil && (delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:minimumInteritemSpacingForSectionAt:)))) ?? false) {
self.interitemSpacing = (delegate?.collectionView!(self.collectionView!, layout: self, minimumInteritemSpacingForSectionAt: section)) ?? interitemSpacing
}
}
重寫布局item相關(guān)函數(shù)
/// 獲取 rect 范圍內(nèi)的所有 item 的布局背犯,并返回計(jì)算好的布局結(jié)果
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return layoutAttributesArray
}
/// 自定義 item 的布局
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// 通過 indexPath,創(chuàng)建一個(gè) UICollectionViewLayoutAttributes
let layoutAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
// 判斷是豎向布局還是橫向布局
if scrollDirection == .vertical {
// 計(jì)算 item 的布局屬性
self.verticalLayoutForItem(layoutAttributes, at: indexPath)
/// 設(shè)置 collectionView 的 content 的高度
// 獲取最大列的高度
let (_, maximumInteritemHeight) = maximumInteritemForSection(heightArray: interitemHeightArray)
// 判斷 collectionView 的 content 的高度 是否比當(dāng)前計(jì)算的最大列的高度小盅抚,若小于則更新 collectionView 的 content 的值
if contentScope < maximumInteritemHeight {
self.contentScope = maximumInteritemHeight
// collectionView的content的高度 + section底部?jī)?nèi)邊距 + iphoneX的底部高度
self.contentScope = contentScope + sectionInset.bottom + iPhoneXBottomHeight
}
} else if scrollDirection == .horizontal {
// 計(jì)算 item 的布局屬性
self.horizontalLayoutForItem(layoutAttributes, at: indexPath)
/// 設(shè)置 collectionView 的 content 的寬度
// 獲取最大列的高度
let (_, maximumLineWidth) = maximumInteritemForSection(heightArray: lineWidthArray) // maximumLineForSection(widthArray: lineWidthArray)
// 判斷 collectionView 的 content 的寬度 是否比當(dāng)前計(jì)算的最大行的寬度小漠魏,若小于則更新 collectionView 的 content 的值
if contentScope < maximumLineWidth {
self.contentScope = maximumLineWidth
// collectionView的content的寬度 + section右側(cè)內(nèi)邊距
self.contentScope = contentScope + sectionInset.right
}
}
return layoutAttributes
}
以下是計(jì)算item自定義函數(shù),分解layoutAttributesForItem函數(shù)的功能
/*
* 豎向布局:計(jì)算 item 的布局妄均,并存儲(chǔ)每一列的高度
*
* @param layoutAttributes: 布局屬性
* @param indexPath: 索引
*/
fileprivate func verticalLayoutForItem(_ layoutAttributes: UICollectionViewLayoutAttributes, at indexPath: IndexPath) {
/// 獲取 collectionView 的寬度
let collectionViewWidth = self.collectionView?.frame.size.width ?? screenWidth
/// 計(jì)算 item 的 frame
// item 的寬度【item的寬度 = (collectionView的寬度 - 左右內(nèi)邊距 - 列之間的間距 * (列數(shù) - 1)) / 列數(shù)】
var itemWidth = (collectionViewWidth - sectionInset.left - sectionInset.right - interitemSpacing * CGFloat(interitemCount - 1)) / CGFloat(interitemCount)
// item 的高度【通過代理方法柱锹,并根據(jù) item 的寬度計(jì)算出 item 的高度】
var itemHeight: CGFloat = 0
if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:heightForItemAt:itemWidth:)))) ?? false) {
itemHeight = delegate?.collectionView!(self.collectionView!, layout: self, heightForItemAt: indexPath, itemWidth: itemWidth) ?? 0
}
if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:sizeForItemAt:)))) ?? false) {
let size = delegate?.collectionView!(self.collectionView!, layout: self, sizeForItemAt: indexPath) ?? CGSize.zero
itemWidth = size.width
itemHeight = size.height
}
// 獲取高度最小的那一列的列號(hào)和高度值
let (minimumInteritemNumber, minimumInteritemHeight) = minimumInteritemForSection(heightArray: interitemHeightArray)
// item 的 x 坐標(biāo)
// 【x的坐標(biāo) = 左內(nèi)邊距 + 列號(hào) * (item的寬度 + 列之間的間距)】
let itemX = sectionInset.left + CGFloat(minimumInteritemNumber) * (itemWidth + interitemSpacing)
// item 的 y 坐標(biāo),初始位置為最小列的高度
var itemY: CGFloat = minimumInteritemHeight
// 如果item的y值不等于上個(gè)區(qū)域的最高的高度 既不是此區(qū)的第一列 要加上此區(qū)的每個(gè)item的上下間距
if indexPath.item < interitemCount {
itemY = itemY + sectionInset.top // y坐標(biāo)值 + section內(nèi)邊距top值(也就是第一行上方是否留白)
} else {
itemY = itemY + lineSpacing // y坐標(biāo)值 + 行間距(item與item之間的行間距)
}
// 設(shè)置 item 的 attributes 的 frame
layoutAttributes.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: itemHeight)
/// 存儲(chǔ)所計(jì)算列的高度丰包。若已存在禁熏,則更新其值;若不存在邑彪,則直接賦值(y值 + height)
self.interitemHeightArray[minimumInteritemNumber] = layoutAttributes.frame.maxY
}
/*
* 橫向布局:計(jì)算 item 的布局瞧毙,并存儲(chǔ)每一行的寬度
*
* @param layoutAttributes: 布局屬性
* @param indexPath: 索引
*/
fileprivate func horizontalLayoutForItem(_ layoutAttributes: UICollectionViewLayoutAttributes, at indexPath: IndexPath) {
/// 獲取 collectionView 的高度
let collectionViewHeight = self.collectionView?.frame.size.height ?? (screenHeight - NavigationHeight)
/// 計(jì)算 item 的 frame
// item 的高度【item的高度 = (collectionView的高度 - iphoneX底部的高度 - header的高度 - footer的高度 - 上下內(nèi)邊距 - 行之間的間距 * (行數(shù) - 1)) / 行數(shù)】
var itemHeight = (collectionViewHeight - iPhoneXBottomHeight - headerReferenceSize.height - footerReferenceSize.height - sectionInset.top - sectionInset.bottom - lineSpacing * CGFloat(lineCount - 1)) / CGFloat(lineCount)
// item 的寬度【通過代理方法,并根據(jù) item 的高度計(jì)算出 item 的寬度】
var itemWidth: CGFloat = 0
if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:heightForItemAt:itemHeight:)))) ?? false) {
itemWidth = delegate?.collectionView!(self.collectionView!, layout: self, heightForItemAt: indexPath, itemHeight: itemHeight) ?? 0
}
if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:sizeForItemAt:)))) ?? false) {
let size = delegate?.collectionView!(self.collectionView!, layout: self, sizeForItemAt: indexPath) ?? CGSize.zero
itemWidth = size.width
itemHeight = size.height
}
// 獲取寬度最小的那一行的行號(hào)和寬度值
let (minimumLineNumber, minimumLineWidth) = minimumInteritemForSection(heightArray: lineWidthArray) // minimumLineForSection(widthArray: lineWidthArray)
// item 的 y 坐標(biāo)
// 【y的坐標(biāo) = 上內(nèi)邊距 + 行號(hào) * (item的高度 + 行之間的間距)】
let itemY = headerReferenceSize.height + sectionInset.top + CGFloat(minimumLineNumber) * (itemHeight + lineSpacing)
// item 的 x 坐標(biāo)寄症,初始位置為最小行的寬度
var itemX: CGFloat = minimumLineWidth
// 如果item的x值不等于上個(gè)區(qū)域的最高的高度 既不是此區(qū)的第一列 要加上此區(qū)的每個(gè)item的左右間距
if indexPath.item < lineCount {
itemX = itemX + sectionInset.left // x坐標(biāo)值 + section內(nèi)邊距l(xiāng)eft值(也就是第一列左方是否留白)
} else {
itemX = itemX + interitemSpacing // x坐標(biāo)值 + 列間距(item與item之間的列間距)
}
// 設(shè)置 item 的 attributes 的 frame
layoutAttributes.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: itemHeight)
/// 存儲(chǔ)所計(jì)算列的高度(若已存在宙彪,則更新其值;若不存在有巧,則直接賦值)
self.lineWidthArray[minimumLineNumber] = layoutAttributes.frame.maxX
}
/*
* 豎向布局: 計(jì)算高度最小的是哪一列 / 橫向布局:計(jì)算寬度最小的是哪一行
*
* @param heightArray: 緩存 section 高度的數(shù)組 / 緩存 section 寬度的數(shù)組
* return 返回最小列的列號(hào)和高度值 / 返回最小行的行號(hào)和高度值
*/
fileprivate func minimumInteritemForSection(heightArray: [CGFloat]) -> (Int, CGFloat) {
if heightArray.count <= 0 {
return (0, 0.0)
}
// 默認(rèn)第0列的高度最小
var minimumInteritemNumber = 0
// 從緩存高度數(shù)組中取出第一個(gè)元素释漆,作為最小的那一列的高度
var minimumInteritemHeight = heightArray[0]
// 遍歷數(shù)組,查找出最小的列號(hào)和最小列的高度值
for i in 1..<heightArray.count {
let tempMinimumInteritemHeight = heightArray[i]
if minimumInteritemHeight > tempMinimumInteritemHeight {
minimumInteritemHeight = tempMinimumInteritemHeight
minimumInteritemNumber = i
}
}
return (minimumInteritemNumber, minimumInteritemHeight)
}
/*
* 豎向布局: 計(jì)算高度最大的是哪一列 / 橫向布局:計(jì)算寬度最大的是哪一行
*
* @param heightArray: 緩存 section 高度的數(shù)組 / 緩存 section 寬度的數(shù)組
* return 返回最大列的列號(hào)和高度值 / 返回最大行的行號(hào)和寬度值
*/
fileprivate func maximumInteritemForSection(heightArray: [CGFloat]) -> (Int, CGFloat) {
if heightArray.count <= 0 {
return (0, 0.0)
}
// 默認(rèn)第0列的高度最小
var maximumInteritemNumber = 0
// 從緩存高度數(shù)組中取出第一個(gè)元素剪决,作為最小的那一列的高度
var maximumInteritemHeight = heightArray[0]
// 遍歷數(shù)組灵汪,查找出最小的列號(hào)和最小列的高度值
for i in 1..<heightArray.count {
let tempMaximumInteritemHeight = heightArray[i]
if maximumInteritemHeight < tempMaximumInteritemHeight {
maximumInteritemHeight = tempMaximumInteritemHeight
maximumInteritemNumber = i
}
}
return (maximumInteritemNumber, maximumInteritemHeight)
}
重寫布局Header和Footer的函數(shù)
/// 自定義 header/footer 的布局
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// 通過 elementKind 和 indexPath,創(chuàng)建一個(gè) UICollectionViewLayoutAttributes
let layoutAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
if elementKind == UICollectionElementKindSectionHeader {
/// 通過代理方法柑潦,更新變量 headerReferenceSize 的值
if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:referenceSizeForHeaderInSection:)))) ?? false) {
self.headerReferenceSize = (delegate?.collectionView!(self.collectionView!, layout: self, referenceSizeForHeaderInSection: (indexPath.first ?? 0))) ?? headerReferenceSize
}
/// 計(jì)算 header 的布局
self.layoutSupplementaryView(layoutAttributes, frame: CGRect(x: 0, y: contentScope, width: headerReferenceSize.width, height: headerReferenceSize.height), atTopWhiteSpace: false, atBottomWhiteSpace: false)
} else if elementKind == UICollectionElementKindSectionFooter {
/// 通過代理方法享言,更新變量 footerReferenceSize 的值
if delegate != nil && ((delegate?.responds(to: #selector(JYWaterfallFlowLayoutDelegate.collectionView(_:layout:referenceSizeForFooterInSection:)))) ?? false) {
self.footerReferenceSize = (delegate?.collectionView!(self.collectionView!, layout: self, referenceSizeForFooterInSection: (indexPath.first ?? 0))) ?? footerReferenceSize
}
/// 計(jì)算 footer 的布局
self.layoutSupplementaryView(layoutAttributes, frame: CGRect(x: 0, y: contentScope, width: footerReferenceSize.width, height: footerReferenceSize.height), atTopWhiteSpace: false, atBottomWhiteSpace: false)
} else {
layoutAttributes.frame = CGRect.zero
}
return layoutAttributes
}
以下是自定義函數(shù),分解layoutAttributesForSupplementaryView函數(shù)的功能
/*
* 豎向布局:計(jì)算 header/footer 布局渗鬼,并更新 collectionView 的 content 的滾動(dòng)范圍
*
* @param layoutAttributes: 布局屬性
* @param frame : header/footer 的frame
* @param top : 是否 footer 的上方留白
* @param bottom : 是否 header 的下方留白
*/
fileprivate func layoutSupplementaryView(_ layoutAttributes: UICollectionViewLayoutAttributes, frame: CGRect, atTopWhiteSpace top: Bool, atBottomWhiteSpace bottom: Bool) {
/// 計(jì)算 header/footer 的布局
// 設(shè)置 header/footer 的 frame
layoutAttributes.frame = frame
/// 更新 collectionView 的 content 的值
if isShowHeader {
self.contentScope = self.contentScope + headerReferenceSize.height
} else if isShowFooter {
self.contentScope = self.contentScope + footerReferenceSize.height
}
}
重寫collectionViewContentSize屬性
/// 設(shè)置 collectionVIew 的 content 的寬高(滾動(dòng)范圍)
override var collectionViewContentSize: CGSize {
get {
let _ = super.collectionViewContentSize
if scrollDirection == .horizontal && lineWidthArray.count <= 0 {
return CGSize.zero
} else if scrollDirection == .vertical && interitemHeightArray.count <= 0 {
return CGSize.zero
}
// 計(jì)算 collectionView 的 content 的 size
let getCollectionViewWidth = self.collectionView?.frame.size.width
let getCollectionViewHeight = self.collectionView?.frame.size.height
// 記錄豎向滾動(dòng)情況下 collectionView 固定的寬 / 橫向滾動(dòng)情況下 collectionView 固定的高
var collectionViewWidth: CGFloat = 0.0
var collectionViewHeight: CGFloat = 0.0
if let width = getCollectionViewWidth, let height = getCollectionViewHeight {
collectionViewWidth = width
collectionViewHeight = height
} else {
collectionViewWidth = screenWidth
collectionViewHeight = screenHeight - NavigationHeight
}
// 記錄豎向滑動(dòng)下的固定寬和動(dòng)態(tài)高/橫向滑動(dòng)下的動(dòng)態(tài)寬和固定高
let tempContentWidth = (scrollDirection == .vertical ? collectionViewWidth : contentScope)
let tempContentHeight = (scrollDirection == .vertical ? contentScope : collectionViewHeight)
return CGSize(width: tempContentWidth, height: tempContentHeight)
}
}