[iOS笔记]UICollectionView实现瀑布流
2018-09-18 本文已影响0人
Seacen_Liu
写在前面
在iOS开发中UITableView
随处可见,但他的整体布局并没有像UICollectionView
一样可以自定义布局。通过自定义UICollectionView
的布局,我们灵活的做出个很多不一样的布局。在这一篇笔记中,可以学到的是如何自己实现一个普通的瀑布流布局。
![](https://img.haomeiwen.com/i4720638/a6bb9b8e2266c264.gif)
普通使用
这里介绍的是使用Xib
在正常使用UICollectionView
下的操作
let layout = WaterfallLayout()
layout.itemSize = { [unowned self] indexPath in
let model = self.commodities[indexPath.item]
return CGSize(width: model.w, height: model.h)
}
layout.headerHeight = { _ in return 20 }
layout.footerHeight = { _ in return 30 }
let collectionView = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: layout)
其他UICollectionViewDatasource
的步骤就不重复了
主要思路
UICollectionView
与UITableView
最大的不同是,前者在初始化的时候需要传入一个UICollectionViewLayout
对象。在默认的UICollectionView
中有一个默认的Layout
是UICollectionViewFlowLayout
即是流式布局,就是平时最常见的类似“九宫格”的布局。所以,要做到瀑布流布局,自定义一个UICollectionViewLayout
是必须的了,在里面我们计算item
的frame
。
重要步骤
重写下面的方法
/// 布局准备
- (void)prepareLayout;
/// 获取属性数组
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
/// 根据 indexPath 返回属性
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
/// 计算ContentSize
- (CGSize)collectionViewContentSize;
实现步骤
- 声明
WaterfallLayout(瀑布流布局)
继承自UICollectionViewLayout
class WaterfallLayout: UICollectionViewLayout {
...
}
- 使用闭包来为瀑布流提供
itemSize
var itemSize: ((IndexPath) -> CGSize) = { _ in
return .zero
}
- 设置一个属性保存计算好的属性
/// 缓存布局属性数组
private var attributesArray = [UICollectionViewLayoutAttributes]()
- 重写
prepare()
方法,注意需要先super.prepare()
override func prepare() {
super.prepare()
guard let collectionView = collectionView else {
return
}
// 清除缓存
flowHeights = Array(repeating: edgeInsets.top, count: flowCount)
attributesArray.removeAll()
// 根据风格处缓存用于计算的定值
prepareValueForCompute()
// 创建新的布局属性
for section in 0..<collectionView.numberOfSections {
// 创建头视图属性
if let attributes = layoutAttributesForSupplementaryView(
ofKind: UICollectionView.elementKindSectionHeader,
at: IndexPath(item: 0, section: section)) {
attributesArray.append(attributes)
}
// 创建新的 item 视图属性
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPath = IndexPath(item: item, section: section)
if let attributes = layoutAttributesForItem(at: indexPath) {
attributesArray.append(attributes)
}
}
// 创建脚视图属性
if let attributes = layoutAttributesForSupplementaryView(
ofKind: UICollectionView.elementKindSectionFooter,
at: IndexPath(item: 0, section: section)) {
attributesArray.append(attributes)
}
}
}
- 重写
func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return attributesArray
}
- 重写在
prepare()
中调用的下列方法,主要是计算UICollectionViewLayoutAttributes
中的frame
/// 返回头尾视图布局属性
func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
/// 返回每个 indexPath 对应的 item 的布局属性
func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
- 我的
返回每个 indexPath 对应的 item 的布局属性
实现,纯属参考,主要的瀑布流计算量都在这里面。
/// 计算垂直瀑布的Frame
func verticalItemFrame(with indexPath: IndexPath) -> CGRect {
// 布局的宽度和高度
let width = flowWidth
let size = itemSize(indexPath)
let aspectRatio = size.height / size.width
let height = width * aspectRatio
// 查找最短的一列索引和值
var destColumn = 0
var minColumnHeight = flowHeights[0]
for (i, v) in flowHeights.enumerated() {
if v < minColumnHeight {
minColumnHeight = v
destColumn = i
}
}
// 布局的坐标点
let x = edgeInsets.left + CGFloat(destColumn) * (width + columnMargin)
var y = minColumnHeight
if y != edgeInsets.top {
y += rowMargin
}
let rect = CGRect(x: x, y: y, width: width, height: height)
// 更新最短那列的高度
flowHeights[destColumn] = rect.maxY
return rect
}
- 如果需要显示Header和Footer需要重写下面的方法,具体见下面的Demo
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- 计算
collectionViewContentSize
override var collectionViewContentSize: CGSize {
switch style {
case .vertical:
...
case .horizontal:
...
}
}
未完待续
上面的展示的是我计算普通垂直瀑布流的大致方法,大家有什么好的方式和我做的不足的地方都可以说说