vds_ios/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift
Matt Bruce 718acd90c5 updated for better spacing
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2022-12-01 11:34:51 -06:00

349 lines
12 KiB
Swift

//
// ButtonGroupPositionLayout.swift
// VDS
//
// Created by Matt Bruce on 11/18/22.
//
import Foundation
import UIKit
class ButtonLayoutAttributes: UICollectionViewLayoutAttributes{
var spacing: CGFloat = 0
var isButton: Bool = false
convenience init(spacing: CGFloat,
forCellWith indexPath: IndexPath) {
self.init(forCellWith: indexPath)
self.spacing = spacing
}
}
class ButtonCollectionViewRow {
var attributes = [ButtonLayoutAttributes]()
var spacing: CGFloat = 0
init(spacing: CGFloat) {
self.spacing = spacing
}
func add(attribute: ButtonLayoutAttributes) {
attributes.append(attribute)
}
var hasButtons: Bool {
attributes.contains(where: { $0.isButton })
}
var rowWidth: CGFloat {
return attributes.reduce(0, { result, attribute -> CGFloat in
return result + attribute.frame.width + attribute.spacing
})
}
var rowHeight: CGFloat {
attributes.compactMap{$0.frame.height}.max() ?? 0
}
var rowY: CGFloat = 0 {
didSet {
for attribute in attributes {
attribute.frame.origin.y = rowY
}
}
}
func layout(for position: ButtonPosition, with collectionViewWidth: CGFloat){
var offset = 0.0
attributes.last?.spacing = 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 + attribute.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, buttonableAtIndexPath indexPath: IndexPath) -> any Buttonable
func collectionView(_ collectionView: UICollectionView, insetsForItemsInSection section: Int) -> UIEdgeInsets
}
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: [ButtonLayoutAttributes] = []
override func prepare() {
super.prepare()
let rowSpacingButton = 12.0
let rowSpacingTextLink = 12.0
itemCache.removeAll()
layoutHeight = 0.0
guard let collectionView, let delegate 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
let section = 0
// Get the necessary data (if implemented) from the delegates else provide default values
let insets: UIEdgeInsets = delegate.collectionView(collectionView, insetsForItemsInSection: section)
let rowSpacing: CGFloat = rowSpacingButton
// Variables to track individual item width and cumultative height of all items as they are being laid out.
var itemSize: CGSize = .zero
layoutHeight += insets.top
let totalItems = collectionView.numberOfItems(inSection: section)
for item in 0..<totalItems {
itemSpacing = 0.0
let indexPath = IndexPath(item: item, section: section)
itemSize = delegate.collectionView(collectionView, sizeForItemAtIndexPath: indexPath)
if (layoutWidthIterator + itemSize.width + insets.left + insets.right) > 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 + rowSpacing
}
let itemButtonable = delegate.collectionView(collectionView, buttonableAtIndexPath: indexPath)
let nextItem = item + 1
if nextItem < totalItems {
let neighbor = delegate.collectionView(collectionView, buttonableAtIndexPath: IndexPath(item: nextItem, section: section))
itemSpacing = getHorizontalSpacing(for: itemButtonable, neighboring: neighbor)
}
let frame = CGRect(x: layoutWidthIterator + insets.left, y: layoutHeight, width: itemSize.width, height: itemSize.height)
print(frame)
let attributes = ButtonLayoutAttributes(spacing: itemSpacing, forCellWith: indexPath)
attributes.frame = frame
attributes.isButton = isButton(buttonable: itemButtonable)
itemCache.append(attributes)
layoutWidthIterator = layoutWidthIterator + frame.width + itemSpacing
}
print("*******")
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 }
layoutHeight = insets.top
for item in 0..<rows.count {
let row = rows[item]
var rowSpacing = 0.0
if item > 0 && item < rows.count {
rowSpacing = row.hasButtons ? rowSpacingButton : rowSpacingTextLink
}
if item > 0 {
row.rowY = layoutHeight + rowSpacing
layoutHeight += rowSpacing
}
layoutHeight += row.rowHeight
}
layoutHeight += insets.bottom
itemCache = rowAttributes
}
func isButton(buttonable: Buttonable) -> Bool{
if let _ = buttonable as? Button {
return true
} else {
return false
}
}
func getHorizontalSpacing(for primary: Buttonable, neighboring: Buttonable) -> CGFloat {
let defaultSpace = 12.0
//large button
if let button = primary as? Button, button.size == .large {
if let neighboringButton = neighboring as? Button, neighboringButton.size == .large {
return 12.0
} else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large {
return 16.0
} else if let _ = neighboring as? TextLinkCaret {
return 24.0
} else {
return defaultSpace
}
}
//large text link
else if let textLink = primary as? TextLink, textLink.size == .large {
if let neighboringButton = neighboring as? Button, neighboringButton.size == .large {
return 16.0
} else if let _ = neighboring as? TextLinkCaret {
return 24.0
} else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large {
return 16.0
} else {
return defaultSpace
}
}
//text link caret
else if let _ = primary as? TextLinkCaret {
if let _ = neighboring as? TextLinkCaret {
return 24.0
} else {
return defaultSpace
}
}
//small button
else if let button = primary as? Button, button.size == .small {
if let neighboringButton = neighboring as? Button, neighboringButton.size == .small {
return 12.0
} else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small {
return 16.0
} else {
return defaultSpace
}
}
//small text link
else if let textLink = primary as? TextLink, textLink.size == .small {
if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small {
return 16.0
} else {
return defaultSpace
}
}
//return defaultSpace
else {
return defaultSpace
}
}
func getVerticalSpacing(for primary: Buttonable, neighboring: Buttonable) -> CGFloat {
let defaultSpace = 12.0
//large button
if let button = primary as? Button, button.size == .large {
if let neighboringButton = neighboring as? Button, neighboringButton.size == .large {
return 12.0
} else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large {
return 16.0
} else if let _ = neighboring as? TextLinkCaret {
return 24.0
} else {
return defaultSpace
}
}
//large text link
else if let textLink = primary as? TextLink, textLink.size == .large {
if let neighboringButton = neighboring as? Button, neighboringButton.size == .large {
return 16.0
} else if let _ = neighboring as? TextLinkCaret {
return 24.0
} else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large {
return 24.0
} else {
return defaultSpace
}
}
//text link caret
else if let _ = primary as? TextLinkCaret {
if let _ = neighboring as? TextLinkCaret {
return 24.0
} else {
return defaultSpace
}
}
//small button
else if let button = primary as? Button, button.size == .small {
if let neighboringButton = neighboring as? Button, neighboringButton.size == .small {
return 12.0
} else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small {
return 24.0
} else {
return defaultSpace
}
}
//small text link
else if let textLink = primary as? TextLink, textLink.size == .small {
if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small {
return 32.0
} else {
return defaultSpace
}
}
//return defaultSpace
else {
return defaultSpace
}
}
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)
}
}