iOS开发进阶 - 自定义UICollectionViewLayout实现瀑布流布局
Posted W_C__L
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发进阶 - 自定义UICollectionViewLayout实现瀑布流布局相关的知识,希望对你有一定的参考价值。
移动端访问不佳,请访问我的个人博客
最近项目中需要用到瀑布流的效果,但是用UICollectionViewFlowLayout又达不到效果,自己动手写了一个瀑布流的layout,下面是我的心路路程
先先上效果图与demo地址:
因为是用UICollectionView来实现瀑布流的,决定继承UICollectionViewLayout来自定义一个layout来实现一个简单瀑布流的布局,下面是需要重写的方法:
- 重写这个属性得出UICollectionView的ContentSize:
collectionViewContentSize
- 重写这个方法来得到每个item的布局:
layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
- 重写这个方法给UICollectionView所有item的布局:
layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
- 重写这个方法来实现UICollectionView前的操作:
prepare()
实现思路
通过代理模式获得到需要的列数和每一item的高度,用过列数与列之间的间隔和UICollectionView的宽度来得出每一列的宽度,item从左边到右布局,下一列的item放到高度最小的列下面,防止每列的高度不均匀,下面贴上代码和注释:
import UIKit
@objc protocol WCLWaterFallLayoutDelegate
//waterFall的列数
func columnOfWaterFall(_ collectionView: UICollectionView) -> Int
//每个item的高度
func waterFall(_ collectionView: UICollectionView, layout waterFallLayout: WCLWaterFallLayout, heightForItemAt indexPath: IndexPath) -> CGFloat
class WCLWaterFallLayout: UICollectionViewLayout
//代理
weak var delegate: WCLWaterFallLayoutDelegate?
//行间距
@IBInspectable var lineSpacing: CGFloat = 0
//列间距
@IBInspectable var columnSpacing: CGFloat = 0
//section的top
@IBInspectable var sectionTop: CGFloat = 0
willSet
sectionInsets.top = newValue
//section的Bottom
@IBInspectable var sectionBottom: CGFloat = 0
willSet
sectionInsets.bottom = newValue
//section的left
@IBInspectable var sectionLeft: CGFloat = 0
willSet
sectionInsets.left = newValue
//section的right
@IBInspectable var sectionRight: CGFloat = 0
willSet
sectionInsets.right = newValue
//section的Insets
@IBInspectable var sectionInsets: UIEdgeInsets = UIEdgeInsets.zero
//每行对应的高度
private var columnHeights: [Int: CGFloat] = [Int: CGFloat]()
private var attributes: [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
//MARK: Initial Methods
init(lineSpacing: CGFloat, columnSpacing: CGFloat, sectionInsets: UIEdgeInsets)
super.init()
self.lineSpacing = lineSpacing
self.columnSpacing = columnSpacing
self.sectionInsets = sectionInsets
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
//MARK: Public Methods
//MARK: Override
override var collectionViewContentSize: CGSize
var maxHeight: CGFloat = 0
for height in columnHeights.values
if height > maxHeight
maxHeight = height
return CGSize.init(width: collectionView?.frame.width ?? 0, height: maxHeight + sectionInsets.bottom)
override func prepare()
super.prepare()
guard collectionView != nil else
return
if let columnCount = delegate?.columnOfWaterFall(collectionView!)
for i in 0..<columnCount
columnHeights[i] = sectionInsets.top
let itemCount = collectionView!.numberOfItems(inSection: 0)
attributes.removeAll()
for i in 0..<itemCount
if let att = layoutAttributesForItem(at: IndexPath.init(row: i, section: 0))
attributes.append(att)
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
if let collectionView = collectionView
//根据indexPath获取item的attributes
let att = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
//获取collectionView的宽度
let width = collectionView.frame.width
if let columnCount = delegate?.columnOfWaterFall(collectionView)
guard columnCount > 0 else
return nil
//item的宽度 = (collectionView的宽度 - 内边距与列间距) / 列数
let totalWidth = (width - sectionInsets.left - sectionInsets.right - (CGFloat(columnCount) - 1) * columnSpacing)
let itemWidth = totalWidth / CGFloat(columnCount)
//获取item的高度,由外界计算得到
let itemHeight = delegate?.waterFall(collectionView, layout: self, heightForItemAt: indexPath) ?? 0
//找出最短的那一列
var minIndex = 0
for column in columnHeights
if column.value < columnHeights[minIndex] ?? 0
minIndex = column.key
//根据最短列的列数计算item的x值
let itemX = sectionInsets.left + (columnSpacing + itemWidth) * CGFloat(minIndex)
//item的y值 = 最短列的最大y值 + 行间距
let itemY = (columnHeights[minIndex] ?? 0) + lineSpacing
//设置attributes的frame
att.frame = CGRect.init(x: itemX, y: itemY, width: itemWidth, height: itemHeight)
//更新字典中的最大y值
columnHeights[minIndex] = att.frame.maxY
return att
return nil
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
return attributes
最后附带demo地址,大家喜欢的话可以star一下
上面是简单的瀑布流的实现过程,希望大家能学到东西,有很多地方考虑的不足,欢迎大家交流学习,谢谢大家的阅读~~
以上是关于iOS开发进阶 - 自定义UICollectionViewLayout实现瀑布流布局的主要内容,如果未能解决你的问题,请参考以下文章
iOS开发进阶 - 自定义UICollectionViewLayout实现瀑布流布局
Android进阶之自定义View实战仿iOS UISwitch控件实现
Android进阶之自定义View实战仿iOS UISwitch控件实现