vds_ios/VDS/Components/Tilelet/TileletGroup/TileletGroupFlowLayout.swift
Matt Bruce 2e519eee28 updated flow
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-09 16:30:57 -05:00

143 lines
5.8 KiB
Swift

//
// TileletGroupPositionLayout.swift
// VDS
//
// Created by Matt Bruce on 10/8/24.
//
import Foundation
import UIKit
import VDSCoreTokens
protocol TiletGroupCollectionViewLayoutDataDelegate: AnyObject {
func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> Tilelet
}
class TileletGroupFlowLayout : UICollectionViewFlowLayout {
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
/// Parameter to store the layout attributes of cell, while calculating the size & position of the cell
private var itemCache: [UICollectionViewLayoutAttributes] = []
/// Parameter to store the total height of the collectionView
private var layoutHeight: CGFloat = 0.0
/// Parameter to store the total width of the collectionView
private var layoutWidth: CGFloat = 0.0
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Spacing between items in the row
public var horizontalPadding: CGFloat = VDSLayout.space1X
/// Spacing between rows (sections)
public var verticalPadding: CGFloat = VDSLayout.space1X
//--------------------------------------------------
// MARK: - Internal Properties
//--------------------------------------------------
weak var delegate: TiletGroupCollectionViewLayoutDataDelegate?
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Calculates the layout attribute properties & total height of the collectionView
override func prepare() {
super.prepare()
itemCache.removeAll()
layoutHeight = 0.0
guard let collectionView, let delegate else { return }
let sections = collectionView.numberOfSections
var yPos: CGFloat = 0.0
/// Calculate the available width for items (subtract the left and right padding)
let collectionViewWidth = collectionView.bounds.width
/// Determine the number of columns and calculate item width based on available space
///
let columns = collectionView.numberOfItems(inSection: 0)
let totalPadding = horizontalPadding * CGFloat(columns - 1)
let availableWidth = collectionViewWidth - totalPadding
let itemWidth = availableWidth / CGFloat(columns)
for currentSection in 0..<sections {
let items = collectionView.numberOfItems(inSection: currentSection)
layoutWidth = 0.0
var xPos: CGFloat = 0.0
/// Looping through all the items in the section, visually these are each column in the row
for currentItem in 0..<items {
let indexPath = IndexPath(row: currentItem, section: currentSection)
let selectedItem = delegate.collectionView(collectionView, dataForItemAt: indexPath)
let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth, index: indexPath)
layoutWidth += itemWidth + (currentItem > 0 ? horizontalPadding : 0)
let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attribute.frame = CGRect(x: xPos, y: yPos, width: itemWidth, height: itemHeight)
itemCache.append(attribute)
/// Update x position for the next item
xPos += itemWidth + horizontalPadding
}
/// Determines the highest height from all the cells (columns) in the row
let highestHeightForSection = itemCache
.filter({ $0.indexPath.section == currentSection })
.sorted(by: { $0.frame.size.height > $1.frame.size.height })
.first?.frame.size.height ?? 0.0
/// Set the highest height as height to all the cells in the row for uniform height
itemCache
.filter({ $0.indexPath.section == currentSection })
.forEach { attributes in
attributes.frame.size.height = highestHeightForSection
}
/// Adds the height to y position for the next section, including vertical padding
yPos += highestHeightForSection + verticalPadding
}
layoutHeight = yPos - verticalPadding /// Adjust bottom padding to total height
}
/// Fetches estimated height by calling the cell's component estimated height and adding padding
private func estimateHeightFor(item: Tilelet, with width: CGFloat, index: IndexPath) -> CGFloat {
let itemWidth = width
let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude)
let estItemSize = item.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return estItemSize.height
}
/// This will return the layout attributes for the elements in the defined rect
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
for attributes in itemCache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
/// This will return the layout attributes at particular indexPath
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return itemCache.first(where: { $0.indexPath == indexPath })
}
/// Returns the collectionView content size
override var collectionViewContentSize: CGSize {
return CGSize(width: collectionView?.bounds.width ?? layoutWidth, height: layoutHeight)
}
}