diff --git a/VDS/Components/Buttons/ButtonGroup/ButtonGroupConstants.swift b/VDS/Components/Buttons/ButtonGroup/ButtonGroupConstants.swift index 015be8b4..ff8605d0 100644 --- a/VDS/Components/Buttons/ButtonGroup/ButtonGroupConstants.swift +++ b/VDS/Components/Buttons/ButtonGroup/ButtonGroupConstants.swift @@ -8,77 +8,28 @@ import Foundation struct ButtonGroupConstants { - static let rowSpacingButton = 12.0 - static let rowSpacingTextLink = 12.0 - - static 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 - } + static let defaultSpace = 12.0 + + enum ButtonSpacingAxis { + case horizontal, vertical } - static func getVerticalSpacing(for primary: Buttonable, neighboring: Buttonable) -> CGFloat { - let defaultSpace = 12.0 + /// This will determine the spacing that will go between 2 buttonables either horizontally or vertically + /// - Parameters: + /// - axis: horizontal/vertical + /// - primary: first buttonable + /// - neighboring: next buttonable based off of axis + /// - Returns: float value + static func getSpacing(for axis: ButtonSpacingAxis, with primary: Buttonable, neighboring: Buttonable) -> CGFloat { + //large button if let button = primary as? Button, button.size == .large { if let neighboringButton = neighboring as? Button, neighboringButton.size == .large { - return 12.0 + return axis == .horizontal ? 12.0 : 12.0 + } else if neighboring is TextLinkCaret { + return axis == .horizontal ? 24.0 : 24.0 } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large { - return 16.0 - } else if let _ = neighboring as? TextLinkCaret { - return 24.0 + return axis == .horizontal ? 16.0 : 16.0 } else { return defaultSpace } @@ -86,11 +37,11 @@ struct ButtonGroupConstants { //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 + return axis == .horizontal ? 16.0 : 16.0 + } else if neighboring is TextLinkCaret { + return axis == .horizontal ? 24.0 : 24.0 } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large { - return 24.0 + return axis == .horizontal ? 16.0 : 24.0 } else { return defaultSpace } @@ -98,11 +49,11 @@ struct ButtonGroupConstants { //text link caret else if let _ = primary as? TextLinkCaret { if let neighboringButton = neighboring as? Button, neighboringButton.size == .large { - return 16.0 + return axis == .horizontal ? 24.0 : 24.0 } else if let _ = neighboring as? TextLinkCaret { - return 24.0 + return axis == .horizontal ? 24.0 : 24.0 } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large { - return 24.0 + return axis == .horizontal ? 24.0 : 24.0 } else { return defaultSpace } @@ -110,9 +61,9 @@ struct ButtonGroupConstants { //small button else if let button = primary as? Button, button.size == .small { if let neighboringButton = neighboring as? Button, neighboringButton.size == .small { - return 12.0 + return axis == .horizontal ? 12.0 : 12.0 } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small { - return 24.0 + return axis == .horizontal ? 16.0 : 24.0 } else { return defaultSpace } @@ -120,9 +71,9 @@ struct ButtonGroupConstants { //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 if let neighboringButton = neighboring as? Button, neighboringButton.size == .small { - return 24.0 + return axis == .horizontal ? 16.0 : 24.0 + } else if let _ = neighboring as? Button { + return axis == .horizontal ? 16.0 : 32.0 } else { return defaultSpace } @@ -133,4 +84,54 @@ struct ButtonGroupConstants { } } + + // + + /// Gets the tallest buttonables within the row + /// - Parameter row: Row that includes the attributes + /// - Returns: Array of [ButtonLayoutAttributes] of the tallest items + private static func getTallestAttributes(for row: ButtonCollectionViewRow) -> [ButtonLayoutAttributes] { + var height = 0.0 + var foundIndexes:[Int] = [] + for (index, attribute) in row.attributes.enumerated() { + if attribute.frame.height >= height { + height = attribute.frame.height + foundIndexes.append(index) + } + } + return foundIndexes.compactMap { row.attributes[$0] } + } + + + /// Gets the vertical spacing that will go between rows. + /// - Parameters: + /// - row: Primary row that the space will go between + /// - neighboringRow: Secondary row that will be below the Primary + /// - Returns: Amount of space that should live between these rows based off of the items. The largest space will win when the comparison occurs. + static func getVerticalSpacing(for row: ButtonCollectionViewRow, neighboringRow: ButtonCollectionViewRow?) -> CGFloat { + // if the neighboringRow is nil, this is the last row in the collection + // so return no space + guard let neighboringRow else { return 0.0 } + + let primaryTallestAttributes = getTallestAttributes(for: row) + let neighboringTallestAttributes = getTallestAttributes(for: neighboringRow) + + if primaryTallestAttributes.count > 0 && neighboringTallestAttributes.count > 0 { + // If there is a tie for “tallest child,” the tiebreaker criteria is to refer to the child with the larger space requirement (for vertical spacing). + var largestVerticalSpace = defaultSpace + + primaryTallestAttributes.forEach { primaryAttribute in + neighboringTallestAttributes.forEach { neighboringTallestAttribute in + let space = getSpacing(for: .vertical, with: primaryAttribute.buttonable!, neighboring: neighboringTallestAttribute.buttonable!) + if space > largestVerticalSpace { + largestVerticalSpace = space + } + } + } + return largestVerticalSpace + } + else { + return defaultSpace + } + } } diff --git a/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift b/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift index a9307adf..eead60ba 100644 --- a/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift +++ b/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift @@ -17,7 +17,7 @@ class ButtonCollectionViewRow { func add(attribute: ButtonLayoutAttributes) { attributes.append(attribute) } - + var hasButtons: Bool { attributes.contains(where: { $0.isButton }) } @@ -33,7 +33,7 @@ class ButtonCollectionViewRow { var rowHeight: CGFloat { attributes.compactMap{$0.frame.height}.max() ?? 0 } - + var maxHeightIndexPath: IndexPath { let maxHeight = rowHeight return attributes.first(where: {$0.frame.height == maxHeight})!.indexPath @@ -112,7 +112,8 @@ class ButtonCollectionViewRow { for attribute in attributes { attribute.frame.origin.x = offset if attribute.frame.height < height { - attribute.frame.size.height = height + //recalibrate the y to vertically center align rect + attribute.frame.origin.y += (height - attribute.frame.size.height) / 2 } offset += attribute.frame.width + attribute.spacing } @@ -130,11 +131,20 @@ protocol ButtongGroupPositionLayoutDelegate: AnyObject { class ButtonLayoutAttributes: UICollectionViewLayoutAttributes{ var spacing: CGFloat = 0 - var isButton: Bool = false + + var buttonable: Buttonable? + + var isButton: Bool { + guard buttonable is Button else { return false } + return true + } + convenience init(spacing: CGFloat, + buttonable: Buttonable, forCellWith indexPath: IndexPath) { self.init(forCellWith: indexPath) self.spacing = spacing + self.buttonable = buttonable } } @@ -218,13 +228,12 @@ class ButtonGroupPositionLayout: UICollectionViewLayout { let neighbor = delegate.collectionView(collectionView, buttonableAtIndexPath: IndexPath(item: nextItem, section: section)) // get the spacing to go between the current and next buttonable - itemSpacing = ButtonGroupConstants.getHorizontalSpacing(for: itemButtonable, neighboring: neighbor) + itemSpacing = ButtonGroupConstants.getSpacing(for: .horizontal, with: itemButtonable, neighboring: neighbor) } // create the custom layout attribute - let attributes = ButtonLayoutAttributes(spacing: itemSpacing, forCellWith: indexPath) + let attributes = ButtonLayoutAttributes(spacing: itemSpacing, buttonable: itemButtonable, forCellWith: indexPath) attributes.frame = CGRect(x: 0, y: 0, width: itemSize.width, height: itemSize.height) - attributes.isButton = isButton(buttonable: itemButtonable) // add it to the array rows.last?.add(attribute: attributes) @@ -247,7 +256,8 @@ class ButtonGroupPositionLayout: UICollectionViewLayout { var rowSpacing = 0.0 if item > 0 { - rowSpacing = 12.0 + let prevRow = rows[item - 1] + rowSpacing = ButtonGroupConstants.getVerticalSpacing(for: prevRow, neighboringRow: row) row.rowY = layoutHeight + rowSpacing layoutHeight += rowSpacing } @@ -265,14 +275,6 @@ class ButtonGroupPositionLayout: UICollectionViewLayout { itemCache = rowAttributes } - - func isButton(buttonable: Buttonable) -> Bool{ - if let _ = buttonable as? Button { - return true - } else { - return false - } - } override func layoutAttributesForElements(in rect: CGRect)-> [UICollectionViewLayoutAttributes]? { var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []