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一下
上面是简单的瀑布流的实现过程,希望大家能学到东西,有很多地方考虑的不足,欢迎大家交流学习,谢谢大家的阅读。