暂无描述

WaterfallFlowLayout.swift 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. //
  2. // WaterfallFlowLayout.swift
  3. // PaiAi
  4. //
  5. // Created by FFIB on 2017/11/13.
  6. // Copyright © 2017年 FFIB. All rights reserved.
  7. //
  8. import UIKit
  9. public final class WaterfallFlowLayout: UICollectionViewLayout {
  10. private var minColumn: Int = 0
  11. private var itemWidth: CGFloat = -1
  12. private var columnHeights: [CGFloat] = []
  13. private var minColumnHeight: CGFloat = 0
  14. private(set) var configuration = WaterfallFlowConfiguration()
  15. private var attributesArr: [UICollectionViewLayoutAttributes] = []
  16. private var isNeedLayout: Bool = true
  17. override public init() {
  18. super.init()
  19. }
  20. convenience init(configuration: WaterfallFlowConfiguration) {
  21. self.init()
  22. self.configuration = configuration
  23. }
  24. required init?(coder aDecoder: NSCoder) {
  25. super.init(coder: aDecoder)
  26. }
  27. override public func prepare() {
  28. super.prepare()
  29. initialize()
  30. }
  31. override public var collectionViewContentSize: CGSize {
  32. return calculateViewSize()
  33. }
  34. override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
  35. guard attributesArr.count <= indexPath.row, isNeedLayout else {
  36. return attributesArr[indexPath.row]
  37. }
  38. let itemX = calculateItemX()
  39. let itemY = calculateItemY()
  40. let itemHeight = calculateItemHeight(indexPath: indexPath)
  41. let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
  42. attributes.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: itemHeight)
  43. setMinColumn(height: itemHeight)
  44. return attributes
  45. }
  46. override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
  47. return attributesArr
  48. }
  49. fileprivate func initialize() {
  50. guard collectionView?.numberOfSections == 1,
  51. let itemCount = collectionView?.numberOfItems(inSection: 0) else { return }
  52. let originIndex: Int
  53. if attributesArr.count != itemCount || itemCount == 0 || itemWidth == -1 || isNeedLayout {
  54. minColumn = 0
  55. originIndex = 0
  56. minColumnHeight = 0
  57. attributesArr.removeAll()
  58. calculateItemWidth()
  59. columnHeights = Array(repeating: 0, count: configuration.columnCount)
  60. } else {
  61. originIndex = attributesArr.count
  62. }
  63. if itemCount == 0 {
  64. columnHeights = []
  65. return
  66. }
  67. for i in originIndex..<itemCount {
  68. guard let attributes = layoutAttributesForItem(at: IndexPath(row: i, section: 0))
  69. else { continue }
  70. attributesArr.append(attributes)
  71. }
  72. }
  73. fileprivate func calculateViewSize() -> CGSize {
  74. guard let v = collectionView,
  75. let maxH = columnHeights.max() else { return CGSize.zero }
  76. return CGSize(width: v.bounds.width, height: maxH + configuration.rowSpace)
  77. }
  78. fileprivate func calculateItemX() -> CGFloat {
  79. return CGFloat(minColumn) * itemWidth + CGFloat(minColumn + 1) * configuration.columnSpace
  80. }
  81. fileprivate func calculateItemY() -> CGFloat {
  82. minColumnHeight += configuration.rowSpace
  83. return minColumnHeight
  84. }
  85. fileprivate func calculateItemWidth() {
  86. let width = collectionView?.bounds.width ?? 0
  87. let space = configuration.columnSpace * (CGFloat(configuration.columnCount + 1))
  88. itemWidth = (width - space) / CGFloat(configuration.columnCount)
  89. }
  90. fileprivate func calculateItemHeight(indexPath: IndexPath) -> CGFloat {
  91. guard let collectionView = collectionView,
  92. let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout
  93. else { return 0 }
  94. let itemOriginSize = delegate.collectionView!(collectionView,
  95. layout: self,
  96. sizeForItemAt: indexPath)
  97. return itemWidth / itemOriginSize.width * itemOriginSize.height
  98. }
  99. fileprivate func setMinColumn(height: CGFloat) {
  100. minColumnHeight += height
  101. columnHeights[minColumn] = minColumnHeight
  102. (minColumn, minColumnHeight) = columnHeights.enumerated().min(by: { $0.1 < $1.1 }) ?? (0, 0)
  103. }
  104. /// called at collectionView reload
  105. func setNeedsLayout() {
  106. isNeedLayout = true
  107. }
  108. }