起因
最近需要实现瀑布流的一个 UICollectionViewLayout, 网上东西一大堆,但是碍于自己对 Swift
了解不是太深, 还是想用 Swift
来实现,加深对 Swift
的理解
注意
实现自定义的UICollectionViewLayout
需要注意下面的几个方法
// 生成每个视图的布局属性(头尾视图和cell的布局属性)
override func prepare()
// 返回滚动区域的大小,当你的UICollectionView 不滚动的情况下可以检查这个方法
override var collectionViewContentSize: CGSize{}
// 返回该区域内的布局属性
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
// 返回 indexpath 位置上的 cell 对应的布局属性
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
思路:
我们参照 UIKit 提供的 UICollectionViewFlowLayout
, 使用 protocol 将 item 的 Size返回,这个事必须要实现的方法., 还有一些其他不是必须要实现的方法, 比如返回列数, 返回头尾视图的 Size, 行间距, 列间距等,
因为在 Swift 中默认 协议里的方法,都是必须要实现的, 我们可以使用 extension
来对可选实现的协议,进行默认的实现.
在每次布局的时候, 一定要注意记录最大的 底部距离,是 UICollectionView 的ContentSize,
还有在头尾视图上,对边距的处理
实现
具体的代码如下
import UIKit
// 瀑布流
protocol LYFWaterFlowLayoutDelegate:NSObjectProtocol{
// require
func waterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, sizeForItemAt indexPath:IndexPath) -> CGSize
// optional
// 头视图的Size
func waterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, sizeForHeaderViewIn Section:Int) -> CGSize
// 尾视图的Size
func waterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, sizeForFooterViewIn Section:Int) -> CGSize
// 列数
func columnCountInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> Int
// 列边距
func columnMarginInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> CGFloat
// 行边距
func rowMarginInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> CGFloat
// 边缘之间的距离
func edgeInsetInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> UIEdgeInsets
}
// optional delagate 进行默认实现
extension LYFWaterFlowLayoutDelegate{
func waterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout,sizeForHeaderViewIn Section:Int) -> CGSize{
return CGSize.zero
}
func waterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, sizeForFooterViewIn Section:Int) -> CGSize{
return CGSize.zero
}
func columnCountInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> Int{
return 1
}
func columnMarginInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> CGFloat{
return 0
}
func rowMarginInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> CGFloat{
return 0
}
func edgeInsetInWaterFlowLayout(_ waterFlowLayout:LYFWaterFlowLayout, forItemAt IndexPath:IndexPath) -> UIEdgeInsets{
return .zero
}
}
class LYFWaterFlowLayout: UICollectionViewLayout {
weak open var waterDelegate:LYFWaterFlowLayoutDelegate!
/// 存放所有cell 的布局属性
lazy var attrsArray:[UICollectionViewLayoutAttributes] = []
/// 存放每一列的最大Y值
lazy var columnHeights:[CGFloat] = []
/// 存放每一行的最大X值
lazy var rowWidths:[CGFloat] = []
var maxColumnHeight:CGFloat = 0.0
/// 初始化生成每个视图的布局属性
override func prepare() {
super.prepare()
self.maxColumnHeight = 0.0
self.columnHeights.removeAll()
self.attrsArray.removeAll()
if let collectionView = self.collectionView {
let sectionCount = collectionView.numberOfSections
for section in 0..<sectionCount {
// head
let headAttri = self.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath.init(item: 0, section: section))
if let headAttri = headAttri {
self.attrsArray.append(headAttri)
}
// item
let rowCount = collectionView.numberOfItems(inSection: section)
for item in 0..<rowCount{
let indexPath = IndexPath.init(item: item, section: section)
let itemAttri = self.layoutAttributesForItem(at: indexPath)
if let itemAttri = itemAttri{
self.attrsArray.append(itemAttri)
}
}
// foot
let footAttri = self.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: IndexPath.init(item: 0, section: section))
if let footAttri = footAttri {
self.attrsArray.append(footAttri)
}
}
}
}
override var collectionViewContentSize: CGSize{
return CGSize(width: 0, height: self.maxColumnHeight)
}
// 返回一定区域内的所有的布局属性
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return self.attrsArray
}
// 返回 indexpath 位置上的 cell 对应的布局属性
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attrs = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
let width = self.collectionView?.frame.size.width
let edgInsets = waterDelegate.edgeInsetInWaterFlowLayout(self, forItemAt: indexPath)
let columCount = waterDelegate.columnCountInWaterFlowLayout(self, forItemAt: indexPath)
let columMargin = waterDelegate.columnMarginInWaterFlowLayout(self, forItemAt: indexPath)
let rowMargin = waterDelegate.rowMarginInWaterFlowLayout(self, forItemAt: indexPath)
let temp = width! - edgInsets.left - edgInsets.right
let marginWith = temp - CGFloat((columCount - 1)) * columMargin
let itemWidth = marginWith / CGFloat(columCount)
let itemHeight = waterDelegate.waterFlowLayout(self, sizeForItemAt: indexPath).height
// 找到高度最短的那一列
let min = minInArray(nums: self.columnHeights)
let minY = min.1
let minIndex = min.0
let x = edgInsets.left + CGFloat(minIndex) * (itemWidth + columMargin)
var y = minY
if y != edgInsets.top {
y = y + rowMargin
}
// 更新最短的那列的高度
self.columnHeights[minIndex] = y + itemHeight
// 记录内容的高度
if self.maxColumnHeight < self.columnHeights[minIndex] {
self.maxColumnHeight = self.columnHeights[minIndex]
}
attrs.frame = CGRect(x: x, y: y, width: itemWidth, height: itemHeight)
return attrs
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
var attri:UICollectionViewLayoutAttributes!
if elementKind == UICollectionView.elementKindSectionHeader {
attri = UICollectionViewLayoutAttributes.init(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: indexPath)
attri.frame = self.headerViewFrameOfVerticalWaterFlow(indexPath: indexPath)
}else{
attri = UICollectionViewLayoutAttributes.init(forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, with: indexPath)
attri.frame = self.footerViewFrameOfVerticalWaterFlow(indexPath: indexPath)
}
return attri
}
/// 返回header 的 CGRect
/// - Parameter indexPath: indexPath
/// - Returns: header 的Frame
func headerViewFrameOfVerticalWaterFlow(indexPath:IndexPath) -> CGRect {
let edgInsets = waterDelegate.edgeInsetInWaterFlowLayout(self, forItemAt: indexPath)
let columCount = waterDelegate.columnCountInWaterFlowLayout(self, forItemAt: indexPath)
let rowMargin = waterDelegate.rowMarginInWaterFlowLayout(self, forItemAt: indexPath)
let size = waterDelegate.waterFlowLayout(self, sizeForHeaderViewIn: indexPath.section)
let x:CGFloat = 0.0
var y = self.maxColumnHeight == 0.0 ? edgInsets.top : self.maxColumnHeight
if self.waterDelegate.waterFlowLayout(self, sizeForFooterViewIn: indexPath.section).height == 0.0 {
y = self.maxColumnHeight == 0.0 ? edgInsets.top : self.maxColumnHeight + rowMargin
}
self.maxColumnHeight = y + size.height
self.columnHeights.removeAll()
for index in 0..<columCount{
self.columnHeights.append(self.maxColumnHeight)
}
return CGRect(x: x, y: y, width: (self.collectionView?.frame.size.width)!, height: size.height)
}
/// footer 的 CGRect
/// - Parameter indexPath: indexPath
/// - Returns: footer 的 Frame
func footerViewFrameOfVerticalWaterFlow(indexPath:IndexPath) -> CGRect {
let edgInsets = waterDelegate.edgeInsetInWaterFlowLayout(self, forItemAt: indexPath)
let columCount = waterDelegate.columnCountInWaterFlowLayout(self, forItemAt: indexPath)
let rowMargin = waterDelegate.rowMarginInWaterFlowLayout(self, forItemAt: indexPath)
let size = waterDelegate.waterFlowLayout(self, sizeForFooterViewIn: indexPath.section)
let x:CGFloat = 0.0
let y = self.maxColumnHeight == 0.0 ? edgInsets.top : self.maxColumnHeight + rowMargin
self.maxColumnHeight = y + size.height
self.columnHeights.removeAll()
for index in 0..<columCount{
self.columnHeights.append(self.maxColumnHeight)
}
return CGRect(x: x, y: y, width: (self.collectionView?.frame.size.width)!, height: size.height)
}
/// 返回数组的最小值
/// - Parameter nums: 数组
/// - Returns: 最小值得下标 和 最小值
func minInArray(nums:[CGFloat]) -> (Int,CGFloat) {
var minIndex = 0
var minValue = nums[0]
for (index,value) in nums.enumerated() {
if value < minValue {
minValue = value
minIndex = index
}
}
return (minIndex,minValue)
}
}
因工程繁杂, 先不放 demo 了