// // ButtonGroupPositionLayout.swift // VDS // // Created by Matt Bruce on 11/18/22. // import Foundation import UIKit class ButtonCollectionViewRow { var attributes = [UICollectionViewLayoutAttributes]() var spacing: CGFloat = 0 init(spacing: CGFloat) { self.spacing = spacing } func add(attribute: UICollectionViewLayoutAttributes) { attributes.append(attribute) } var rowWidth: CGFloat { return attributes.reduce(0, { result, attribute -> CGFloat in return result + attribute.frame.width }) + CGFloat(attributes.count - 1) * spacing } func layout(for position: ButtonPosition, with collectionViewWidth: CGFloat){ var offset = 0.0 switch position { case .left: break case .center: offset = (collectionViewWidth - rowWidth) / 2 case .right: offset = (collectionViewWidth - rowWidth) } for attribute in attributes { attribute.frame.origin.x = offset offset += attribute.frame.width + spacing } } } public enum ButtonPosition: String, CaseIterable { case left, center, right } protocol ButtongGroupPositionLayoutDelegate: AnyObject { func collectionView(_ collectionView: UICollectionView, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize func collectionView(_ collectionView: UICollectionView, insetsForItemsInSection section: Int) -> UIEdgeInsets func collectionView(_ collectionView: UICollectionView, itemSpacingInSection section: Int) -> CGFloat } class ButtonGroupPositionLayout: UICollectionViewLayout { weak var delegate: ButtongGroupPositionLayoutDelegate? // Total height of the content. Will be used to configure the scrollview content var layoutHeight: CGFloat = 0.0 var position: ButtonPosition = .left private var itemCache: [UICollectionViewLayoutAttributes] = [] override func prepare() { super.prepare() itemCache.removeAll() layoutHeight = 0.0 guard let collectionView = collectionView else { return } var itemSpacing = 0.0 // Variable to track the width of the layout at the current state when the item is being drawn var layoutWidthIterator: CGFloat = 0.0 for section in 0.. collectionView.frame.width { // If the current row width (after this item being laid out) is exceeding the width of the collection view content, put it in the next line layoutWidthIterator = 0.0 layoutHeight += itemSize.height + interItemSpacing } let frame = CGRect(x: layoutWidthIterator + insets.left, y: layoutHeight, width: itemSize.width, height: itemSize.height) let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) attributes.frame = frame itemCache.append(attributes) layoutWidthIterator = layoutWidthIterator + frame.width + interItemSpacing } layoutHeight += itemSize.height + insets.bottom layoutWidthIterator = 0.0 } //Turn into rows and re-calculate var rows = [ButtonCollectionViewRow]() var currentRowY: CGFloat = -1 for attribute in itemCache { if currentRowY != attribute.frame.midY { currentRowY = attribute.frame.midY rows.append(ButtonCollectionViewRow(spacing: itemSpacing)) } rows.last?.add(attribute: attribute) } //recalculate rows based off of positions rows.forEach { $0.layout(for: position, with: collectionView.frame.width) } let rowAttributes = rows.flatMap { $0.attributes } itemCache = rowAttributes } override func layoutAttributesForElements(in rect: CGRect)-> [UICollectionViewLayoutAttributes]? { var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = [] for attributes in itemCache { if attributes.frame.intersects(rect) { visibleLayoutAttributes.append(attributes) } } return visibleLayoutAttributes } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { return itemCache[indexPath.row] } override var collectionViewContentSize: CGSize { return CGSize(width: contentWidth, height: layoutHeight) } private var contentWidth: CGFloat { guard let collectionView = collectionView else { return 0 } let insets = collectionView.contentInset return collectionView.bounds.width - (insets.left + insets.right) } }