refactored everything in the buttons classes to remove buttonable and push configuration inside enums for the classes in question

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2023-09-14 11:47:24 -05:00
parent ad6c00214e
commit 72f2b09255
7 changed files with 87 additions and 123 deletions

View File

@ -11,19 +11,13 @@ import VDSColorTokens
import VDSFormControlsTokens import VDSFormControlsTokens
import Combine import Combine
/// Enum used to describe the size of a buttonable.
public enum ButtonSize: String, CaseIterable {
case large
case small
}
/// A button is an interactive element that triggers an action. Buttons are prominent and attention-getting, with more visual emphasis than any of the Text Link components. For this reason, buttons are best suited for critical and driving actions. This class can be used within a ``ButtonGroup``. /// A button is an interactive element that triggers an action. Buttons are prominent and attention-getting, with more visual emphasis than any of the Text Link components. For this reason, buttons are best suited for critical and driving actions. This class can be used within a ``ButtonGroup``.
/// ///
/// If you are using AutoLayoutConstraints you have a combination of Leading/Left and Trailing/Right NSLayoutConstraints, /// If you are using AutoLayoutConstraints you have a combination of Leading/Left and Trailing/Right NSLayoutConstraints,
/// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges /// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges
/// to its parent this object will stretch to the parent's width. /// to its parent this object will stretch to the parent's width.
@objc(VDSButton) @objc(VDSButton)
open class Button: ButtonBase, Useable, Buttonable { open class Button: ButtonBase, Useable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
@ -40,6 +34,50 @@ open class Button: ButtonBase, Useable, Buttonable {
super.init(coder: coder) super.init(coder: coder)
} }
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
/// Enum used to describe the size.
public enum Size: String, CaseIterable {
case large
case small
/// Height for this size of button.
public var height: CGFloat {
switch self {
case .large:
return 44
case .small:
return 32
}
}
/// Corner radius for this size of button.
public var cornerRadius: CGFloat {
height / 2
}
/// Minimum width for this size of button.
public var minimumWidth: CGFloat {
switch self {
case .large:
return 76
case .small:
return 60
}
}
/// EdgeInsets for this size of button.
public var edgeInsets: UIEdgeInsets {
switch self {
case .large:
return .axis(horizontal: 24, vertical: 12)
case .small:
return .axis(horizontal: 16, vertical: 8)
}
}
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
@ -48,11 +86,8 @@ open class Button: ButtonBase, Useable, Buttonable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
/// The ButtonSize available to this type of Buttonable.
open override var availableSizes: [ButtonSize] { [.large, .small] }
/// The ButtonSize for ths Button. /// The ButtonSize for ths Button.
open var size: ButtonSize = .large { didSet { setNeedsUpdate() } } open var size: Size = .large { didSet { setNeedsUpdate() } }
/// The Use for this Button. /// The Use for this Button.
open var use: Use = .primary { didSet { setNeedsUpdate() } } open var use: Use = .primary { didSet { setNeedsUpdate() } }
@ -183,48 +218,6 @@ open class Button: ButtonBase, Useable, Buttonable {
} }
} }
internal extension ButtonSize {
var height: CGFloat {
switch self {
case .large:
return 44
case .small:
return 32
}
}
var cornerRadius: CGFloat {
height / 2
}
var minimumWidth: CGFloat {
switch self {
case .large:
return 76
case .small:
return 60
}
}
var edgeInsets: UIEdgeInsets {
var verticalPadding = 0.0
var horizontalPadding = 0.0
switch self {
case .large:
verticalPadding = 12
horizontalPadding = 24
break
case .small:
verticalPadding = 8
horizontalPadding = 16
break
}
return UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
}
}
extension Use { extension Use {
public var color: UIColor { public var color: UIColor {
return self == .primary ? VDSColor.backgroundPrimaryDark : .clear return self == .primary ? VDSColor.backgroundPrimaryDark : .clear

View File

@ -11,18 +11,6 @@ import VDSColorTokens
import VDSFormControlsTokens import VDSFormControlsTokens
import Combine import Combine
public protocol Buttonable: UIControl, Surfaceable, Enabling {
/// The ButtonSize available to this type of Buttonable.
var availableSizes: [ButtonSize] { get }
/// The Text that will show up in the TitleLabel for this Buttonable.
var text: String? { get set }
/// The natural size for the receiving view, considering only properties of the view itself.
var intrinsicContentSize: CGSize { get }
}
/// Base class used for UIButton type classes. /// Base class used for UIButton type classes.
@objc(VDSButtonBase) @objc(VDSButtonBase)
open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
@ -45,11 +33,6 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
initialSetup() initialSetup()
} }
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
private let hitAreaHeight = 44.0
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Combine Properties // MARK: - Combine Properties
//-------------------------------------------------- //--------------------------------------------------
@ -75,9 +58,6 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
/// Key of whether or not updateView() is called in setNeedsUpdate() /// Key of whether or not updateView() is called in setNeedsUpdate()
open var shouldUpdateView: Bool = true open var shouldUpdateView: Bool = true
/// The ButtonSize available to this type of Buttonable.
open var availableSizes: [ButtonSize] { [] }
open var surface: Surface = .light { didSet { setNeedsUpdate() } } open var surface: Surface = .light { didSet { setNeedsUpdate() } }
/// Text that will be used in the titleLabel. /// Text that will be used in the titleLabel.

View File

@ -60,7 +60,7 @@ open class ButtonGroup: View {
open var alignment: Alignment = .center { didSet { setNeedsUpdate() } } open var alignment: Alignment = .center { didSet { setNeedsUpdate() } }
/// Array of Buttonable Views that are shown in the group. /// Array of Buttonable Views that are shown in the group.
open var buttons: [Buttonable] = [] { didSet { setNeedsUpdate() } } open var buttons: [ButtonBase] = [] { didSet { setNeedsUpdate() } }
private var _childWidth: ChildWidth? private var _childWidth: ChildWidth?
@ -205,7 +205,7 @@ extension ButtonGroup: UICollectionViewDataSource, UICollectionViewDelegate {
} }
extension ButtonGroup : ButtongGroupPositionLayoutDelegate { extension ButtonGroup : ButtongGroupPositionLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, buttonableAtIndexPath indexPath: IndexPath) -> Buttonable { func collectionView(_ collectionView: UICollectionView, buttonBaseAtIndexPath indexPath: IndexPath) -> ButtonBase {
buttons[indexPath.row] buttons[indexPath.row]
} }
} }

View File

@ -14,13 +14,13 @@ struct ButtonGroupConstants {
case horizontal, vertical case horizontal, vertical
} }
/// This will determine the spacing that will go between 2 buttonables either horizontally or vertically /// This will determine the spacing that will go between 2 ButtonBases either horizontally or vertically
/// - Parameters: /// - Parameters:
/// - axis: horizontal/vertical /// - axis: horizontal/vertical
/// - primary: first buttonable /// - primary: first ButtonBase
/// - neighboring: next buttonable based off of axis /// - neighboring: next ButtonBase based off of axis
/// - Returns: float value /// - Returns: float value
static func getSpacing(for axis: ButtonSpacingAxis, with primary: Buttonable, neighboring: Buttonable) -> CGFloat { static func getSpacing(for axis: ButtonSpacingAxis, with primary: ButtonBase, neighboring: ButtonBase) -> CGFloat {
//large button //large button
if let button = primary as? Button, button.size == .large { if let button = primary as? Button, button.size == .large {
@ -87,7 +87,7 @@ struct ButtonGroupConstants {
// //
/// Gets the tallest buttonables within the row /// Gets the tallest ButtonBases within the row
/// - Parameter row: Row that includes the attributes /// - Parameter row: Row that includes the attributes
/// - Returns: Array of [ButtonLayoutAttributes] of the tallest items /// - Returns: Array of [ButtonLayoutAttributes] of the tallest items
private static func getTallestAttributes(for row: ButtonCollectionViewRow) -> [ButtonLayoutAttributes] { private static func getTallestAttributes(for row: ButtonCollectionViewRow) -> [ButtonLayoutAttributes] {
@ -122,7 +122,7 @@ struct ButtonGroupConstants {
primaryTallestAttributes.forEach { primaryAttribute in primaryTallestAttributes.forEach { primaryAttribute in
neighboringTallestAttributes.forEach { neighboringTallestAttribute in neighboringTallestAttributes.forEach { neighboringTallestAttribute in
let space = getSpacing(for: .vertical, with: primaryAttribute.buttonable!, neighboring: neighboringTallestAttribute.buttonable!) let space = getSpacing(for: .vertical, with: primaryAttribute.button!, neighboring: neighboringTallestAttribute.button!)
if space > largestVerticalSpace { if space > largestVerticalSpace {
largestVerticalSpace = space largestVerticalSpace = space
} }

View File

@ -82,7 +82,7 @@ class ButtonCollectionViewRow {
} }
} }
if buttonWidth >= ButtonSize.large.minimumWidth { if buttonWidth >= Button.Size.large.minimumWidth {
if testSize <= buttonAvailableSpace { if testSize <= buttonAvailableSpace {
for attribute in attributes { for attribute in attributes {
if attribute.isButton { if attribute.isButton {
@ -122,25 +122,25 @@ class ButtonCollectionViewRow {
protocol ButtongGroupPositionLayoutDelegate: AnyObject { protocol ButtongGroupPositionLayoutDelegate: AnyObject {
func collectionView(_ collectionView: UICollectionView, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize func collectionView(_ collectionView: UICollectionView, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize
func collectionView(_ collectionView: UICollectionView, buttonableAtIndexPath indexPath: IndexPath) -> any Buttonable func collectionView(_ collectionView: UICollectionView, buttonBaseAtIndexPath indexPath: IndexPath) -> ButtonBase
} }
class ButtonLayoutAttributes: UICollectionViewLayoutAttributes{ class ButtonLayoutAttributes: UICollectionViewLayoutAttributes{
var spacing: CGFloat = 0 var spacing: CGFloat = 0
var buttonable: Buttonable? var button: ButtonBase?
var isButton: Bool { var isButton: Bool {
guard buttonable is Button else { return false } guard button is Button else { return false }
return true return true
} }
convenience init(spacing: CGFloat, convenience init(spacing: CGFloat,
buttonable: Buttonable, button: ButtonBase,
forCellWith indexPath: IndexPath) { forCellWith indexPath: IndexPath) {
self.init(forCellWith: indexPath) self.init(forCellWith: indexPath)
self.spacing = spacing self.spacing = spacing
self.buttonable = buttonable self.button = button
} }
} }
@ -173,7 +173,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
// Variables to track individual item width and cumultative height of all items as they are being laid out. // Variables to track individual item width and cumultative height of all items as they are being laid out.
var itemSize: CGSize = .zero var itemSize: CGSize = .zero
// get number of buttonables // get number of buttons
let totalItems = collectionView.numberOfItems(inSection: section) let totalItems = collectionView.numberOfItems(inSection: section)
//create rows //create rows
@ -190,10 +190,10 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
// create the indexPath // create the indexPath
let indexPath = IndexPath(item: item, section: section) let indexPath = IndexPath(item: item, section: section)
// get the rect size of the buttonable // get the rect size of the button
itemSize = delegate.collectionView(collectionView, sizeForItemAtIndexPath: indexPath) itemSize = delegate.collectionView(collectionView, sizeForItemAtIndexPath: indexPath)
// determine if the current buttonable will fit in the row // determine if the current button will fit in the row
let rowItemCount = rows.last?.attributes.count ?? 0 let rowItemCount = rows.last?.attributes.count ?? 0
if (layoutWidthIterator + itemSize.width) > collectionViewWidth || (rowQuantity > 0 && rowItemCount == rowQuantity) { if (layoutWidthIterator + itemSize.width) > collectionViewWidth || (rowQuantity > 0 && rowItemCount == rowQuantity) {
@ -209,26 +209,26 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
rows.append(ButtonCollectionViewRow()) rows.append(ButtonCollectionViewRow())
} }
// get the buttonable // get the button
let itemButtonable = delegate.collectionView(collectionView, buttonableAtIndexPath: indexPath) let itemButtonBase = delegate.collectionView(collectionView, buttonBaseAtIndexPath: indexPath)
// see if there is another item in the array // see if there is another item in the array
let nextItem = item + 1 let nextItem = item + 1
// if so, get the buttonable // if so, get the button
// and get the spacing based of the // and get the spacing based of the
// current buttonable and the next buttonable // current button and the next button
if nextItem < totalItems { if nextItem < totalItems {
//get the next buttonable //get the next button
let neighbor = delegate.collectionView(collectionView, buttonableAtIndexPath: IndexPath(item: nextItem, section: section)) let neighbor = delegate.collectionView(collectionView, buttonBaseAtIndexPath: IndexPath(item: nextItem, section: section))
// get the spacing to go between the current and next buttonable // get the spacing to go between the current and next button
itemSpacing = ButtonGroupConstants.getSpacing(for: .horizontal, with: itemButtonable, neighboring: neighbor) itemSpacing = ButtonGroupConstants.getSpacing(for: .horizontal, with: itemButtonBase, neighboring: neighbor)
} }
// create the custom layout attribute // create the custom layout attribute
let attributes = ButtonLayoutAttributes(spacing: itemSpacing, buttonable: itemButtonable, forCellWith: indexPath) let attributes = ButtonLayoutAttributes(spacing: itemSpacing, button: itemButtonBase, forCellWith: indexPath)
attributes.frame = CGRect(x: 0, y: 0, width: itemSize.width, height: itemSize.height) attributes.frame = CGRect(x: 0, y: 0, width: itemSize.width, height: itemSize.height)
// add it to the array // add it to the array

View File

@ -18,7 +18,7 @@ import Combine
/// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges /// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges
/// to its parent this object will stretch to the parent's width. /// to its parent this object will stretch to the parent's width.
@objc(VDSTextLink) @objc(VDSTextLink)
open class TextLink: ButtonBase, Buttonable { open class TextLink: ButtonBase {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
//-------------------------------------------------- //--------------------------------------------------
@ -34,6 +34,15 @@ open class TextLink: ButtonBase, Buttonable {
super.init(coder: coder) super.init(coder: coder)
} }
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
/// Enum used to describe the size.
public enum Size: String, CaseIterable {
case large
case small
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
@ -46,11 +55,9 @@ open class TextLink: ButtonBase, Buttonable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
open var size: ButtonSize = .large { didSet { setNeedsUpdate() } } /// The ButtonSize available.
open var size: Size = .large { didSet { setNeedsUpdate() } }
/// The ButtonSize available to this type of Buttonable.
open override var availableSizes: [ButtonSize] { [.large, .small] }
open override var textStyle: TextStyle { open override var textStyle: TextStyle {
size == .large ? TextStyle.bodyLarge : TextStyle.bodySmall size == .large ? TextStyle.bodyLarge : TextStyle.bodySmall
} }
@ -66,15 +73,6 @@ open class TextLink: ButtonBase, Buttonable {
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
} }
private var height: CGFloat {
switch size {
case .large:
return 44
case .small:
return 32
}
}
/// The natural size for the receiving view, considering only properties of the view itself. /// The natural size for the receiving view, considering only properties of the view itself.
open override var intrinsicContentSize: CGSize { open override var intrinsicContentSize: CGSize {
return titleLabel?.intrinsicContentSize ?? super.intrinsicContentSize return titleLabel?.intrinsicContentSize ?? super.intrinsicContentSize

View File

@ -18,7 +18,7 @@ import Combine
/// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges /// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges
/// to its parent this object will stretch to the parent's width. /// to its parent this object will stretch to the parent's width.
@objc(VDSTextLinkCaret) @objc(VDSTextLinkCaret)
open class TextLinkCaret: ButtonBase, Buttonable { open class TextLinkCaret: ButtonBase {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializers // MARK: - Initializers
//-------------------------------------------------- //--------------------------------------------------
@ -41,14 +41,10 @@ open class TextLinkCaret: ButtonBase, Buttonable {
public enum IconPosition: String, CaseIterable { public enum IconPosition: String, CaseIterable {
case left, right case left, right
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
private var height: CGFloat {
44
}
private var textColorConfiguration = ControlColorConfiguration().with { private var textColorConfiguration = ControlColorConfiguration().with {
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
@ -59,10 +55,7 @@ open class TextLinkCaret: ButtonBase, Buttonable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
/// The ButtonSize available to this type of Buttonable.
public override var availableSizes: [ButtonSize] { [.large] }
/// Determines icon position of Caret. /// Determines icon position of Caret.
open var iconPosition: IconPosition = .right { didSet { setNeedsUpdate() } } open var iconPosition: IconPosition = .right { didSet { setNeedsUpdate() } }