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:UICollectionView纯自定义的布局:瀑布流布局
创建瀑布流有三种方式: 第一种方式:在一个ScrollView里面放入三个单元格高度一样的tableView,禁止tableView滚动,只需让tableView随着ScrollView滚动即可. ...
- iOS开发进阶
<iOS开发进阶>基本信息作者: 唐巧 出版社:电子工业出版社ISBN:9787121247453上架时间:2014-12-26出版日期:2015 年1月开本:16开页码:268版次:1- ...
- iOS开发进阶(唐巧)读书笔记(一)
如何提高iOS开发技能 1.阅读博客:https://github.com/tangqiaoboy/iOSBlogCN 40多位iOS开发博主的博客地址 2.读书:每年阅读一本高质量的iOS开发书籍 ...
- iOS开发之自定义表情键盘(组件封装与自动布局)
下面的东西是编写自定义的表情键盘,话不多说,开门见山吧!下面主要用到的知识有MVC, iOS开发中的自动布局,自定义组件的封装与使用,Block回调,CoreData的使用.有的小伙伴可能会问写一个自 ...
- 详解iOS开发之自定义View
iOS开发之自定义View是本文要将介绍的内容,iOS SDK中的View是UIView,我们可以很方便的自定义一个View.创建一个 Window-based Application程序,在其中添加 ...
- Xamarin自定义布局系列——瀑布流布局
Xamarin.Forms以Xamarin.Android和Xamarin.iOS等为基础,自己实现了一整套比较完整的UI框架,包含了绝大多数常用的控件,如下图 虽然XF(Xamarin.Forms简 ...
- 【Swift】IOS开发中自定义转场动画
在IOS开发中,我们model另外一个控制器的时候,一般都使用默认的转场动画. 其实我们可以自定义一些转场动画.达到不同的转场效果. 步骤如下:(photoBrowser是目标控制器) 1.在源控制器 ...
- iOS开发进阶--1.多线程简介
学习是由已知的知识模型推理未知的知识模型的的过程. 本文适合学习完objective-c基础,想进一步提高做iOS开发的同学阅读. 在说线程的时候,我们先看看进程. 1.进程 每一个运行在系统中的应用 ...
- 自定义UICollectionViewLayout 实现瀑布流
今天研究了一下自定义UICollectionViewLayout. 看了看官方文档,要自定义UICollectionViewLayout,需要创建一个UICollectionViewLayout的子类 ...
随机推荐
- Android ArrayAdapter,BaseAdapter,SimpleAdapter适配器绑定数据方法
/** * 2017-07-31.Book 类 */ public class Book { public String getName() { return name; } public void ...
- 微信开发 获取用户openId 与路由控制
w实践,满足当前需求. www.w.com www.w.com/w1.php $wxurl='https://open.weixin.qq.com/connect/oauth2/authorize?a ...
- the ONLY_FULL_GROUP_BY SQL mode is enabled (which it is by default) 用户变量
MySQL :: MySQL 8.0 Reference Manual :: 5.1.11 Server SQL Modes https://dev.mysql.com/doc/refman/8.0/ ...
- Yii框架2.0的安装过程
Yii框架是个不错的php开发框架,大型项目上都可以使用.和大多框架一样他也是开源,而且采用了mvc结构的. Yii1.*,直接下载然后用脚步可以创建自己的项目了,最近看了下Yii2.0版本的,他推荐 ...
- leadecode 2 Add Two Numbers
package leadcode; /** * 2. Add Two Numbers * Medium * 4067 * 953 * * * You are given two non-empty l ...
- Fibonacci----poj3070(矩阵快速幂, 模板)
题目链接:http://poj.org/problem?id=3070 . 就是斐波那契的另一种表示方法是矩阵的幂: 所以是矩阵快速幂:矩阵快速幂学习 #include <cstdio> ...
- Python并行编程(七):线程同步之事件
1.基本概念 事件是线程之间用于通讯的对象.有的线程等待信号,有的线程发出信号.基本上事件对象都会维护一个内部变量,可以通过set方法设置为true,也可以通过clear方法设置为false.wait ...
- nginx 哈希表结构图
- python 面向对象(进阶篇)转载武沛齐
上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...
- Linux ssh服务
关于ssh服务不多说就提几句,1,机房的服务器一般都是通过远程连接登录的,远程登录就必然少不了ssh客户端.2,虚拟机每次都要点击进去,每次退出来也需要按Ctrl+Alt+Enter,也比较麻烦,有时 ...