// // 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.. 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) } }