Merge branch 'mbruce/bugfixes' into 'develop'
ButtonBase and related classes refactor See merge request BPHV_MIPS/vds_ios!109
This commit is contained in:
commit
86a5dd6ca6
@ -11,19 +11,13 @@ import VDSColorTokens
|
||||
import VDSFormControlsTokens
|
||||
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``.
|
||||
///
|
||||
/// 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
|
||||
/// to its parent this object will stretch to the parent's width.
|
||||
@objc(VDSButton)
|
||||
open class Button: ButtonBase, Useable, Buttonable {
|
||||
open class Button: ButtonBase, Useable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
@ -40,6 +34,50 @@ open class Button: ButtonBase, Useable, Buttonable {
|
||||
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
|
||||
//--------------------------------------------------
|
||||
@ -48,11 +86,8 @@ open class Button: ButtonBase, Useable, Buttonable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// The ButtonSize available to this type of Buttonable.
|
||||
open override var availableSizes: [ButtonSize] { [.large, .small] }
|
||||
|
||||
/// The ButtonSize for ths Button.
|
||||
open var size: ButtonSize = .large { didSet { setNeedsUpdate() } }
|
||||
open var size: Size = .large { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// The Use for this Button.
|
||||
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 {
|
||||
public var color: UIColor {
|
||||
return self == .primary ? VDSColor.backgroundPrimaryDark : .clear
|
||||
|
||||
@ -11,18 +11,6 @@ import VDSColorTokens
|
||||
import VDSFormControlsTokens
|
||||
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.
|
||||
@objc(VDSButtonBase)
|
||||
open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
|
||||
@ -45,11 +33,6 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
|
||||
initialSetup()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Configuration Properties
|
||||
//--------------------------------------------------
|
||||
private let hitAreaHeight = 44.0
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Combine Properties
|
||||
//--------------------------------------------------
|
||||
@ -75,9 +58,6 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
|
||||
/// Key of whether or not updateView() is called in setNeedsUpdate()
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
/// The ButtonSize available to this type of Buttonable.
|
||||
open var availableSizes: [ButtonSize] { [] }
|
||||
|
||||
open var surface: Surface = .light { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// Text that will be used in the titleLabel.
|
||||
|
||||
@ -60,7 +60,7 @@ open class ButtonGroup: View {
|
||||
open var alignment: Alignment = .center { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// 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?
|
||||
|
||||
@ -205,7 +205,7 @@ extension ButtonGroup: UICollectionViewDataSource, UICollectionViewDelegate {
|
||||
}
|
||||
|
||||
extension ButtonGroup : ButtongGroupPositionLayoutDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, buttonableAtIndexPath indexPath: IndexPath) -> Buttonable {
|
||||
func collectionView(_ collectionView: UICollectionView, buttonBaseAtIndexPath indexPath: IndexPath) -> ButtonBase {
|
||||
buttons[indexPath.row]
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,13 +14,13 @@ struct ButtonGroupConstants {
|
||||
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:
|
||||
/// - axis: horizontal/vertical
|
||||
/// - primary: first buttonable
|
||||
/// - neighboring: next buttonable based off of axis
|
||||
/// - primary: first ButtonBase
|
||||
/// - neighboring: next ButtonBase based off of axis
|
||||
/// - 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
|
||||
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
|
||||
/// - Returns: Array of [ButtonLayoutAttributes] of the tallest items
|
||||
private static func getTallestAttributes(for row: ButtonCollectionViewRow) -> [ButtonLayoutAttributes] {
|
||||
@ -122,7 +122,7 @@ struct ButtonGroupConstants {
|
||||
|
||||
primaryTallestAttributes.forEach { primaryAttribute 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 {
|
||||
largestVerticalSpace = space
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ class ButtonCollectionViewRow {
|
||||
}
|
||||
}
|
||||
|
||||
if buttonWidth >= ButtonSize.large.minimumWidth {
|
||||
if buttonWidth >= Button.Size.large.minimumWidth {
|
||||
if testSize <= buttonAvailableSpace {
|
||||
for attribute in attributes {
|
||||
if attribute.isButton {
|
||||
@ -122,25 +122,25 @@ class ButtonCollectionViewRow {
|
||||
|
||||
protocol ButtongGroupPositionLayoutDelegate: AnyObject {
|
||||
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{
|
||||
var spacing: CGFloat = 0
|
||||
|
||||
var buttonable: Buttonable?
|
||||
var button: ButtonBase?
|
||||
|
||||
var isButton: Bool {
|
||||
guard buttonable is Button else { return false }
|
||||
guard button is Button else { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
convenience init(spacing: CGFloat,
|
||||
buttonable: Buttonable,
|
||||
button: ButtonBase,
|
||||
forCellWith indexPath: IndexPath) {
|
||||
self.init(forCellWith: indexPath)
|
||||
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.
|
||||
var itemSize: CGSize = .zero
|
||||
|
||||
// get number of buttonables
|
||||
// get number of buttons
|
||||
let totalItems = collectionView.numberOfItems(inSection: section)
|
||||
|
||||
//create rows
|
||||
@ -190,10 +190,10 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
// create the indexPath
|
||||
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)
|
||||
|
||||
// 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
|
||||
|
||||
if (layoutWidthIterator + itemSize.width) > collectionViewWidth || (rowQuantity > 0 && rowItemCount == rowQuantity) {
|
||||
@ -209,26 +209,26 @@ class ButtonGroupPositionLayout: UICollectionViewLayout {
|
||||
rows.append(ButtonCollectionViewRow())
|
||||
}
|
||||
|
||||
// get the buttonable
|
||||
let itemButtonable = delegate.collectionView(collectionView, buttonableAtIndexPath: indexPath)
|
||||
// get the button
|
||||
let itemButtonBase = delegate.collectionView(collectionView, buttonBaseAtIndexPath: indexPath)
|
||||
|
||||
// see if there is another item in the array
|
||||
let nextItem = item + 1
|
||||
|
||||
// if so, get the buttonable
|
||||
// if so, get the button
|
||||
// and get the spacing based of the
|
||||
// current buttonable and the next buttonable
|
||||
// current button and the next button
|
||||
if nextItem < totalItems {
|
||||
|
||||
//get the next buttonable
|
||||
let neighbor = delegate.collectionView(collectionView, buttonableAtIndexPath: IndexPath(item: nextItem, section: section))
|
||||
//get the next button
|
||||
let neighbor = delegate.collectionView(collectionView, buttonBaseAtIndexPath: IndexPath(item: nextItem, section: section))
|
||||
|
||||
// get the spacing to go between the current and next buttonable
|
||||
itemSpacing = ButtonGroupConstants.getSpacing(for: .horizontal, with: itemButtonable, neighboring: neighbor)
|
||||
// get the spacing to go between the current and next button
|
||||
itemSpacing = ButtonGroupConstants.getSpacing(for: .horizontal, with: itemButtonBase, neighboring: neighbor)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// add it to the array
|
||||
|
||||
@ -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
|
||||
/// to its parent this object will stretch to the parent's width.
|
||||
@objc(VDSTextLink)
|
||||
open class TextLink: ButtonBase, Buttonable {
|
||||
open class TextLink: ButtonBase {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -34,6 +34,15 @@ open class TextLink: ButtonBase, Buttonable {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Enums
|
||||
//--------------------------------------------------
|
||||
/// Enum used to describe the size.
|
||||
public enum Size: String, CaseIterable {
|
||||
case large
|
||||
case small
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
@ -46,11 +55,9 @@ open class TextLink: ButtonBase, Buttonable {
|
||||
//--------------------------------------------------
|
||||
// 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 {
|
||||
size == .large ? TextStyle.bodyLarge : TextStyle.bodySmall
|
||||
}
|
||||
@ -66,15 +73,6 @@ open class TextLink: ButtonBase, Buttonable {
|
||||
$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.
|
||||
open override var intrinsicContentSize: CGSize {
|
||||
return titleLabel?.intrinsicContentSize ?? super.intrinsicContentSize
|
||||
|
||||
@ -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
|
||||
/// to its parent this object will stretch to the parent's width.
|
||||
@objc(VDSTextLinkCaret)
|
||||
open class TextLinkCaret: ButtonBase, Buttonable {
|
||||
open class TextLinkCaret: ButtonBase {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -41,14 +41,10 @@ open class TextLinkCaret: ButtonBase, Buttonable {
|
||||
public enum IconPosition: String, CaseIterable {
|
||||
case left, right
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
private var height: CGFloat {
|
||||
44
|
||||
}
|
||||
|
||||
private var textColorConfiguration = ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
||||
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
||||
@ -59,10 +55,7 @@ open class TextLinkCaret: ButtonBase, Buttonable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// The ButtonSize available to this type of Buttonable.
|
||||
public override var availableSizes: [ButtonSize] { [.large] }
|
||||
|
||||
//--------------------------------------------------
|
||||
/// Determines icon position of Caret.
|
||||
open var iconPosition: IconPosition = .right { didSet { setNeedsUpdate() } }
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user