143 lines
5.8 KiB
Swift
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)
|
|
}
|
|
}
|