瀑布流 UICollectionView Swift
最近整理以前iOS开发中用到的功能代码,发觉对瀑布流的布局有点新的认识,现记述如下,也方便对此不熟悉的小伙伴们作以参考。
关于瀑布流布局,我有两种思路,一种是使用UIScrollView
,一种是使用UICollectionView
,但UIScrollView
的话,要考虑每个cell的循环复用问题,带来的开发问题比较多。如果使用UICollectionView
,就会很方便地实现瀑布流布局
思路
- 通过UICollectionView的特性,我们可以容易实现cell的布局,也不需要考虑循环复用的问题,但每个cell的布局都是一样的,frame是固定死的。不过深入研究后,决定UICollectionView布局的是UICollectionViewLayout这个类。
- 那么我们是否可以重写UICollectionViewLayout的子类UICollectionViewFlowLayout来自定义布局呢
- 答案显然是可以的,我们可以仿照UICollectionViewFlowLayout重新定义一个瀑布流布局。如果这样,显然要耗费不少时间,而且也没有必要,这里,我重新定义一个布局,继承自UICollectionViewFlowLayout,重写一些函数和属性,来实现瀑布流布局
重要代码
在UICollectionView
布局的时候,总是会调用prepare()
函数,那么我们重写这个函数:
override func prepare() {
super.prepare()
guard let collectionView = collectionView else {
return
}
let count = collectionView.numberOfItems(inSection: 0)
let itemW = (collectionView.bounds.width - sectionInset.left - sectionInset.right - (CGFloat(cols) - 1) * minimumInteritemSpacing) / CGFloat(cols)
var itemH: CGFloat = 0
var itemX: CGFloat = 0
var itemY: CGFloat = 0
for i in cellAttributes.count..<count {
let indexPath = IndexPath(item: i, section: 0)
let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
itemH = dataSource?.waterfallLayout(self, indexPath: indexPath) ?? 0
let minH = colHeight.min()!
let minIndex = colHeight.index(of: minH)!
itemX = sectionInset.left + CGFloat(minIndex) * (itemW + minimumInteritemSpacing)
itemY = minH
attr.frame = CGRect(x: itemX, y: itemY, width: itemW, height: itemH)
colHeight[minIndex] = attr.frame.maxY + minimumLineSpacing
cellAttributes.append(attr)
}
}
cell中布局的实质是UICollectionViewLayoutAttributes
决定着每个cell的frame,那么就需要重写layoutAttributesForElements()
这个函数,用来返回我们自定义的布局:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return cellAttributes
}
collectionView 之所以能滚动是因为有滚动范围,那么我们依然重写collectionViewContentSize
这个计算型属性:
override var collectionViewContentSize: CGSize {
return CGSize(width: 0, height: colHeight.max()! + sectionInset.bottom - minimumLineSpacing)
}
除此之外,还需要定义一些私有和公开属性:
/// 数据源
weak var dataSource: TCWaterfallLayoutDataSource?
/// 列数
fileprivate lazy var cols: Int = self.dataSource?.waterfallLayoutWithCols?(self) ?? 2
/// cell属性数组
fileprivate lazy var cellAttributes: [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
/// 缓存高度
fileprivate lazy var colHeight: [CGFloat] = Array(repeatElement(self.sectionInset.top, count: self.cols))
既然有数据源属性,那么就得有数据源协议,这里定义了2个函数,一个返回cell高度,一个返回的是UICollectionView
显示的列数:
@objc protocol TCWaterfallLayoutDataSource: class {
func waterfallLayoutForHeight(_ layout: TCWaterfallLayout, indexPath: IndexPath) -> CGFloat
@objc optional func waterfallLayoutForCols(_ layout: TCWaterfallLayout) -> Int
}
如此,创建UICollectionView 的时候,就可以把布局换成自定义的布局了,实现数据源,就会有以下效果:
效果图.png
网友评论