Skip to content

Instantly share code, notes, and snippets.

@AmatsuZero
Last active April 30, 2020 03:32
Show Gist options
  • Save AmatsuZero/4a0f12a1d82f34dc6dd7c5c842e0559a to your computer and use it in GitHub Desktop.
Save AmatsuZero/4a0f12a1d82f34dc6dd7c5c842e0559a to your computer and use it in GitHub Desktop.
iOS不规则瀑布流
import UIKit
public protocol RandomWaterfallLayoutDelegate: class {
/// 列数
func columnCount(_ layout: RandomWaterfallLayout) -> CGFloat
/// 列间距
func columnMargin(_ layout: RandomWaterfallLayout) -> CGFloat
/// 行间距
func rowMargin(_ layout: RandomWaterfallLayout) -> CGFloat
/// CollectionView 边距
func edgeInsets(_ layout: RandomWaterfallLayout) -> UIEdgeInsets
/// 返回Cell 尺寸
func cellSize(at indexPath: IndexPath) -> CGSize
}
public class RandomWaterfallLayout: UICollectionViewLayout {
public var pageCount: Int {
return 48
}
public weak var delegate: RandomWaterfallLayoutDelegate?
/// 所有的cell的布局
private var attrsArray = [UICollectionViewLayoutAttributes]()
/// 每一列的高度
private var columnHeights = [CGFloat]()
/// 没有生成大尺寸次数
private var noneDouleTime = 0
/// 最后一次大尺寸的列数
private var lastDoubleIndex = 0
/// 默认列数
private let defaultColumnCount: CGFloat = 3
/// 默认列边距
private let defaultColumMargin: CGFloat = 10
/// 默认行边距
private let defaultRowMargin: CGFloat = 10
/// 默认collectionView边距
private let defaultUIEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
/// 列数
private var columnCount: CGFloat {
return delegate?.columnCount(self) ?? defaultColumnCount
}
/// 列边距
private var columnMargin: CGFloat {
return delegate?.rowMargin(self) ?? defaultRowMargin
}
/// 行边距
private var rowMargin: CGFloat {
return delegate?.rowMargin(self) ?? defaultRowMargin
}
// collectionView边距
private var edgeInsets: UIEdgeInsets {
return delegate?.edgeInsets(self) ?? defaultUIEdgeInsets
}
// collectionView 首次布局和之后重新布局的时候会调用
// 并不是每次滑动都调用,只有在数据源变化的时候才调用
public override func prepare() {
// 重写必须调用super方法
super.prepare()
guard let collectionView = collectionView else {
return
}
if collectionView.numberOfItems(inSection: 0) == pageCount, attrsArray.count > pageCount {
attrsArray.removeAll()
columnHeights.removeAll()
}
// 当列高度数组为空时,即为第一行计算,每一列的基础高度加上collection的边框的top值
if columnHeights.isEmpty {
columnHeights.append(contentsOf: [CGFloat](repeating: edgeInsets.top, count: Int(columnCount)))
}
// 遍历所有的cell,计算所有cell的布局
for i in attrsArray.count..<collectionView.numberOfItems(inSection: 0) {
if let attr = layoutAttributesForItem(at: .init(row: i, section: 0)) {
attrsArray.append(attr)
}
}
}
// 返回布局属性,一个UICollectionViewLayoutAttributes对象数组
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return attrsArray
}
public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = self.collectionView,
let size = delegate?.cellSize(at: indexPath),
!columnHeights.isEmpty else {
return nil
}
let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
// cell的宽度
let w = collectionView.frame.width - edgeInsets.left - edgeInsets.right - columnMargin * (columnCount - 1) / columnCount
// cell的高度
let h = size.width / size.height * w
// 高度最小的列数高度
let minColumnHeight = columnHeights.sorted().first!
// 获取高度最小的列数
// cell应该拼接的列数
let destColumn = columnHeights.firstIndex(of: minColumnHeight)!
// 计算cell的x
let x = edgeInsets.left + CGFloat(destColumn) * (w + columnMargin)
// 计算cell的y
let y = minColumnHeight != edgeInsets.top ? minColumnHeight + edgeInsets.top : minColumnHeight
// 判断是否放大
if CGFloat(destColumn) < columnCount - 1, // 放大的列数不能是最后一列(最后一列方法超出屏幕)
noneDouleTime > 1, // 如果前个cell有放大就不放大,防止连续出现两个放大
Int.random(in: 0..<100) > 33, // 33%几率不放大
columnHeights[destColumn] == columnHeights[destColumn + 1], // 当前列的顶部和下一列的顶部要对齐
lastDoubleIndex != destColumn {// 最后一次放大的列不等当前列,防止出现连续两列出现放大不美观
noneDouleTime = 0
lastDoubleIndex = destColumn
// 重定义当前cell的布局:宽度*2,高度*2
attrs.frame.origin = CGPoint(x: x, y: y)
attrs.frame.size.width = w * 2 + columnMargin
attrs.frame.size.height = h * 2 + rowMargin
columnHeights[destColumn] = attrs.frame.maxY
columnHeights[destColumn+1] = attrs.frame.maxY
} else {
// 正常cell的布局
if columnHeights.count > destColumn + 1, abs(y + h - columnHeights[destColumn + 1]) < h * 0.2 {
// 当前cell填充后和上一列的高度偏差不超过cell最大高度的10%,就和下一列对齐
attrs.frame = CGRect(x: x, y: y, width: w, height: columnHeights[destColumn+1] - y)
} else if destColumn >= 1, abs(y + h - columnHeights[destColumn - 1]) < h * 0.2 {
// 当前cell填充后和上上列的高度偏差不超过cell最大高度的10%,就和下一列对齐
attrs.frame = CGRect(x: x, y: y, width: w, height: columnHeights[destColumn - 1] - y)
} else {
attrs.frame = CGRect(x: x, y: y, width: w, height: h)
}
// 当前cell列的高度就是当前cell的最大Y值
columnHeights[destColumn] = attrs.frame.maxY
noneDouleTime += 1
}
return attrs
}
public override var collectionViewContentSize: CGSize {
guard !columnHeights.isEmpty else {
return .zero
}
// collectionView的contentSize的高度等于所有列高度中最大的值
let maxColumnHeight = columnHeights.sorted().last!
return CGSize(width: 0, height: maxColumnHeight + edgeInsets.bottom)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment