# Conflicts: # VDS/Components/BadgeIndicator/BadgeIndicator.swift # VDS/Components/TextFields/EntryFieldBase.swift Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
562 lines
25 KiB
Swift
562 lines
25 KiB
Swift
//
|
|
// ButtonIcon.swift
|
|
// VDS
|
|
//
|
|
// Created by Matt Bruce on 5/12/23.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import VDSTokens
|
|
import Combine
|
|
|
|
/// A button icon is an interactive element that visually communicates the action it triggers via an icon.
|
|
/// It usually represents a supplementary or utilitarian action. A button icon can stand alone, but often
|
|
/// exists in a group when there are several actions that can be performed.
|
|
@objc(VDSButtonIcon)
|
|
open class ButtonIcon: Control, Changeable, FormFieldable {
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Initializers
|
|
//--------------------------------------------------
|
|
required public init() {
|
|
super.init(frame: .zero)
|
|
}
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: .zero)
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Enums
|
|
//--------------------------------------------------
|
|
/// Enum used to describe the type of button based on the contrast.
|
|
public enum Kind: String, CaseIterable {
|
|
case ghost, lowContrast, highContrast
|
|
}
|
|
|
|
/// Enum used to describe the background inside icon button determining the surface type.
|
|
public enum SurfaceType: String, CaseIterable {
|
|
case colorFill, media
|
|
}
|
|
|
|
/// Enum used to describe the size of button icon.
|
|
public enum Size: String, EnumSubset {
|
|
case large
|
|
case small
|
|
|
|
public var defaultValue: Icon.Size { .large }
|
|
|
|
public var containerSize: CGFloat {
|
|
switch self {
|
|
case .large:
|
|
return 44.0
|
|
case .small:
|
|
return 32.0
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Private Properties
|
|
//--------------------------------------------------
|
|
private var centerXConstraint: NSLayoutConstraint?
|
|
private var centerYConstraint: NSLayoutConstraint?
|
|
private var layoutGuideWidthConstraint: NSLayoutConstraint?
|
|
private var layoutGuideHeightConstraint: NSLayoutConstraint?
|
|
private var badgeIndicatorLeadingConstraint: NSLayoutConstraint?
|
|
private var badgeIndicatorTrailingConstraint: NSLayoutConstraint?
|
|
private var badgeIndicatorCenterXConstraint: NSLayoutConstraint?
|
|
private var badgeIndicatorCenterYConstraint: NSLayoutConstraint?
|
|
private var currentIconName: Icon.Name? {
|
|
if let selectedIconName, isSelected {
|
|
return selectedIconName
|
|
} else {
|
|
return iconName
|
|
}
|
|
}
|
|
|
|
private var badgeIndicatorOffset: CGPoint {
|
|
switch (size, kind) {
|
|
case (.small, .ghost):
|
|
return .init(x: 1, y: 0)
|
|
case (.large, .ghost):
|
|
return .init(x: 1, y: 1)
|
|
case (.small, .lowContrast), (.small, .highContrast):
|
|
return .init(x: 4, y: 4)
|
|
case (.large, .lowContrast), (.large, .highContrast):
|
|
return .init(x: 6, y: 6)
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Public Properties
|
|
//--------------------------------------------------
|
|
|
|
///Badge Indicator object used to render for the ButtonIcon.
|
|
open var badgeIndicator = BadgeIndicator().with {
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
$0.size = .small
|
|
$0.isHidden = true
|
|
}
|
|
|
|
/// If set, this is used to render the badge indicator.
|
|
open var badgeIndicatorModel: BadgeIndicatorModel? { didSet { setNeedsUpdate() } }
|
|
|
|
/// Icon object used to render out the Icon for this ButtonIcon.
|
|
open var icon = Icon().with {
|
|
$0.isUserInteractionEnabled = false
|
|
$0.accessibilityTraits = .button
|
|
}
|
|
|
|
/// Determines the type of button based on the contrast.
|
|
open var kind: Kind = .ghost { didSet { setNeedsUpdate() } }
|
|
|
|
/// Applies background inside icon button determining the surface type.
|
|
open var surfaceType: SurfaceType = .colorFill { didSet { setNeedsUpdate() } }
|
|
|
|
/// Icon Name used within the Icon.
|
|
open var iconName: Icon.Name? { didSet { setNeedsUpdate() } }
|
|
|
|
/// Icon Name used within the Icon within the Selected State.
|
|
open var selectedIconName: Icon.Name? { didSet { setNeedsUpdate() } }
|
|
|
|
/// Sets the size of button icon and icon.
|
|
open var size: Size = .large { didSet { setNeedsUpdate() } }
|
|
|
|
/// Sets the size of button icon and icon.
|
|
open var customSize: Int? { didSet { setNeedsUpdate() } }
|
|
|
|
/// If provided, the button icon will have a box shadow.
|
|
open var floating: Bool = false { didSet { setNeedsUpdate() } }
|
|
|
|
/// If true, container shrinks to fit the size of the icon for kind equals .ghost.
|
|
open var fitToIcon: Bool = false { didSet { setNeedsUpdate() } }
|
|
|
|
/// If set to true, the button icon will not have a border.
|
|
open var hideBorder: Bool = true { didSet { setNeedsUpdate() } }
|
|
|
|
/// If provided, the badge indicator will present.
|
|
open var showBadgeIndicator: Bool = false { didSet { setNeedsUpdate() } }
|
|
|
|
/// If true, button will be rendered as selected, when it is selectable.
|
|
open var selectable: Bool = false {
|
|
didSet {
|
|
//unselect
|
|
if !selectable && isSelected {
|
|
isSelected = false
|
|
}
|
|
setNeedsUpdate()
|
|
}
|
|
}
|
|
|
|
/// Used to move the icon inside the button in both x and y axis.
|
|
open var iconOffset: CGPoint = .init(x: 0, y: 0) { didSet { setNeedsUpdate() } }
|
|
|
|
open var onChangeSubscriber: AnyCancellable?
|
|
|
|
open var inputId: String? { didSet { setNeedsUpdate() } }
|
|
|
|
open var value: AnyHashable? { didSet { setNeedsUpdate() } }
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Configuration
|
|
//--------------------------------------------------
|
|
private var iconColorConfiguration: AnyColorable {
|
|
if kind == .highContrast {
|
|
return highContrastIconColorConfiguration
|
|
} else if kind == .lowContrast {
|
|
return (surfaceType == .colorFill) ? lowContrastIconColorConfiguration : (floating ? lowContrastIconColorConfiguration : standardIconColorConfiguration)
|
|
} else {
|
|
return standardIconColorConfiguration
|
|
}
|
|
}
|
|
|
|
private var currentConfiguration: any Configuration {
|
|
switch kind {
|
|
case .ghost:
|
|
return GhostConfiguration()
|
|
|
|
case .lowContrast:
|
|
if surfaceType == .colorFill {
|
|
return floating ? LowContrastColorFillFloatingConfiguration() : LowContrastColorFillConfiguration()
|
|
} else {
|
|
return floating ? LowContrastMediaFloatingConfiguration() : LowContrastMediaConfiguration()
|
|
}
|
|
|
|
case .highContrast:
|
|
return floating ? HighContrastFloatingConfiguration() : HighContrastConfiguration()
|
|
}
|
|
}
|
|
|
|
private var standardIconColorConfiguration: AnyColorable = {
|
|
return ControlColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
|
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
|
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
|
|
}.eraseToAnyColorable()
|
|
}()
|
|
|
|
private var lowContrastIconColorConfiguration: AnyColorable = {
|
|
return ControlColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
|
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.paletteBlack.withAlphaComponent(0.70), forState: .disabled)
|
|
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.paletteBlack.withAlphaComponent(0.70), forState: [.selected, .disabled])
|
|
}.eraseToAnyColorable()
|
|
}()
|
|
|
|
private var highContrastIconColorConfiguration: AnyColorable = {
|
|
return SurfaceColorConfiguration(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight).eraseToAnyColorable()
|
|
}()
|
|
|
|
private struct GhostConfiguration: Configuration {
|
|
var kind: Kind = .ghost
|
|
var surfaceType: SurfaceType = .colorFill
|
|
var floating: Bool = false
|
|
var backgroundColorConfiguration: AnyColorable = {
|
|
SurfaceColorConfiguration(.clear, .clear).eraseToAnyColorable()
|
|
}()
|
|
}
|
|
|
|
private struct LowContrastColorFillConfiguration: Configuration {
|
|
var kind: Kind = .lowContrast
|
|
var surfaceType: SurfaceType = .colorFill
|
|
var floating: Bool = false
|
|
var backgroundColorConfiguration: AnyColorable = {
|
|
SurfaceColorConfiguration(VDSColor.paletteGray44.withAlphaComponent(0.06), VDSColor.paletteGray44.withAlphaComponent(0.26)).eraseToAnyColorable()
|
|
}()
|
|
}
|
|
|
|
private struct LowContrastColorFillFloatingConfiguration: Configuration, DropShadowableConfiguration {
|
|
var kind: Kind = .lowContrast
|
|
var surfaceType: SurfaceType = .colorFill
|
|
var floating: Bool = true
|
|
var backgroundColorConfiguration: AnyColorable = {
|
|
SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteGray20).eraseToAnyColorable()
|
|
}()
|
|
private let dropshadow1Configuration = DropShadowConfiguration().with {
|
|
$0.shadowColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
|
$0.shadowOpacityConfiguration = SurfaceConfigurationValue(CGFloat(0.12), CGFloat(0.22))
|
|
$0.shadowOffsetConfiguration = SurfaceConfigurationValue(.init(width: 0, height: 1), .init(width: 0, height: 1))
|
|
$0.shadowRadiusConfiguration = SurfaceConfigurationValue(CGFloat(10), CGFloat(12))
|
|
}
|
|
private let dropshadow2Configuration = DropShadowConfiguration().with {
|
|
$0.shadowColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
|
$0.shadowOpacityConfiguration = SurfaceConfigurationValue(CGFloat(0.05), CGFloat(0.15))
|
|
$0.shadowOffsetConfiguration = SurfaceConfigurationValue(.init(width: 0, height: 2), .init(width: 0, height: 2))
|
|
$0.shadowRadiusConfiguration = SurfaceConfigurationValue(CGFloat(4), CGFloat(6))
|
|
}
|
|
var configurations: [DropShadowable] { [dropshadow1Configuration, dropshadow2Configuration] }
|
|
}
|
|
|
|
private struct LowContrastMediaConfiguration: Configuration, Borderable {
|
|
var kind: Kind = .lowContrast
|
|
var surfaceType: SurfaceType = .media
|
|
var floating: Bool = false
|
|
var backgroundColorConfiguration: AnyColorable = {
|
|
SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark).eraseToAnyColorable()
|
|
}()
|
|
var borderWidth: CGFloat = 1.0
|
|
var borderColorConfiguration: AnyColorable = {
|
|
SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).eraseToAnyColorable()
|
|
}()
|
|
}
|
|
|
|
private struct LowContrastMediaFloatingConfiguration: Configuration, DropShadowableConfiguration {
|
|
var kind: Kind = .lowContrast
|
|
var surfaceType: SurfaceType = .media
|
|
var floating: Bool = true
|
|
var backgroundColorConfiguration: AnyColorable = {
|
|
SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteGray20).eraseToAnyColorable()
|
|
}()
|
|
private let dropshadow1Configuration = DropShadowConfiguration().with {
|
|
$0.shadowColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
|
$0.shadowOpacityConfiguration = SurfaceConfigurationValue(CGFloat(0.12), CGFloat(0.22))
|
|
$0.shadowOffsetConfiguration = SurfaceConfigurationValue(.init(width: 0, height: 1), .init(width: 0, height: 1))
|
|
$0.shadowRadiusConfiguration = SurfaceConfigurationValue(CGFloat(10), CGFloat(12))
|
|
}
|
|
private let dropshadow2Configuration = DropShadowConfiguration().with {
|
|
$0.shadowColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
|
$0.shadowOpacityConfiguration = SurfaceConfigurationValue(CGFloat(0.05), CGFloat(0.15))
|
|
$0.shadowOffsetConfiguration = SurfaceConfigurationValue(.init(width: 0, height: 2), .init(width: 0, height: 2))
|
|
$0.shadowRadiusConfiguration = SurfaceConfigurationValue(CGFloat(4), CGFloat(6))
|
|
}
|
|
var configurations: [DropShadowable] { [dropshadow1Configuration, dropshadow2Configuration] }
|
|
}
|
|
|
|
private struct HighContrastConfiguration: Configuration {
|
|
var kind: Kind = .highContrast
|
|
var surfaceType: SurfaceType = .colorFill
|
|
var floating: Bool = false
|
|
var backgroundColorConfiguration: AnyColorable = {
|
|
return ControlColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight, forState: .normal)
|
|
$0.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight, forState: .selected)
|
|
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
|
|
}.eraseToAnyColorable()
|
|
|
|
}()
|
|
}
|
|
|
|
private struct HighContrastFloatingConfiguration: Configuration, DropShadowableConfiguration {
|
|
var kind: Kind = .highContrast
|
|
var surfaceType: SurfaceType = .colorFill
|
|
var floating: Bool = true
|
|
var backgroundColorConfiguration: AnyColorable = {
|
|
return ControlColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.paletteGray20, VDSColor.paletteWhite, forState: .normal)
|
|
$0.setSurfaceColors(VDSColor.paletteGray20, VDSColor.paletteWhite, forState: .selected)
|
|
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
|
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: [.selected, .highlighted])
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
|
|
}.eraseToAnyColorable()
|
|
}()
|
|
private let dropshadow1Configuration = DropShadowConfiguration().with {
|
|
$0.shadowColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
|
$0.shadowOpacityConfiguration = SurfaceConfigurationValue(CGFloat(0.22), CGFloat(0.12))
|
|
$0.shadowOffsetConfiguration = SurfaceConfigurationValue(.init(width: 0, height: 1), .init(width: 0, height: 1))
|
|
$0.shadowRadiusConfiguration = SurfaceConfigurationValue(CGFloat(12), CGFloat(10))
|
|
}
|
|
private let dropshadow2Configuration = DropShadowConfiguration().with {
|
|
$0.shadowColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteBlack).eraseToAnyColorable()
|
|
$0.shadowOpacityConfiguration = SurfaceConfigurationValue(CGFloat(0.15), CGFloat(0.05))
|
|
$0.shadowOffsetConfiguration = SurfaceConfigurationValue(.init(width: 0, height: 2), .init(width: 0, height: 2))
|
|
$0.shadowRadiusConfiguration = SurfaceConfigurationValue(CGFloat(6), CGFloat(4))
|
|
}
|
|
var configurations: [DropShadowable] { [dropshadow1Configuration, dropshadow2Configuration] }
|
|
}
|
|
|
|
private var badgeIndicatorDefaultSize: CGSize = .zero
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Overrides
|
|
//--------------------------------------------------
|
|
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
|
open override func setup() {
|
|
super.setup()
|
|
isAccessibilityElement = false
|
|
accessibilityElements = [icon, badgeIndicator]
|
|
|
|
//create a layoutGuide for the icon to key off of
|
|
let iconLayoutGuide = UILayoutGuide()
|
|
addLayoutGuide(iconLayoutGuide)
|
|
|
|
//add the icon
|
|
addSubview(icon)
|
|
|
|
//add badgeIndicator
|
|
addSubview(badgeIndicator)
|
|
badgeIndicator.isHidden = !showBadgeIndicator
|
|
badgeIndicatorDefaultSize = badgeIndicator.frame.size
|
|
|
|
//determines the height/width of the icon
|
|
layoutGuideWidthConstraint = iconLayoutGuide.width(constant: size.containerSize)
|
|
layoutGuideHeightConstraint = iconLayoutGuide.height(constant: size.containerSize)
|
|
badgeIndicatorLeadingConstraint = badgeIndicator.leadingAnchor.constraint(equalTo: icon.centerXAnchor)
|
|
badgeIndicatorTrailingConstraint = badgeIndicator.trailingAnchor.constraint(equalTo: icon.centerXAnchor)
|
|
badgeIndicatorCenterXConstraint = badgeIndicator.centerXAnchor.constraint(equalTo: icon.centerXAnchor)
|
|
badgeIndicatorCenterYConstraint = icon.centerYAnchor.constraint(equalTo: badgeIndicator.centerYAnchor)
|
|
badgeIndicatorCenterYConstraint?.isActive = true
|
|
|
|
badgeIndicatorLeadingConstraint?.isActive = true
|
|
//pin layout guide
|
|
iconLayoutGuide
|
|
.pinTop()
|
|
.pinLeading()
|
|
.pinTrailing(0, .defaultHigh)
|
|
.pinBottom(0, .defaultHigh)
|
|
|
|
//determines the center point of the icon
|
|
centerXConstraint = icon.centerXAnchor.constraint(equalTo: iconLayoutGuide.centerXAnchor, constant: 0)
|
|
centerXConstraint?.activate()
|
|
centerYConstraint = icon.centerYAnchor.constraint(equalTo: iconLayoutGuide.centerYAnchor, constant: 0)
|
|
centerYConstraint?.activate()
|
|
}
|
|
|
|
/// Executed on initialization for this View.
|
|
open override func initialSetup() {
|
|
super.initialSetup()
|
|
onClick = { control in
|
|
guard control.isEnabled else { return }
|
|
if control.selectedIconName != nil && control.selectable {
|
|
control.toggle()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This will change the state of the Selector and execute the actionBlock if provided.
|
|
open func toggle() {
|
|
//removed error
|
|
isSelected.toggle()
|
|
sendActions(for: .valueChanged)
|
|
}
|
|
|
|
/// Resets to default settings.
|
|
open override func reset() {
|
|
super.reset()
|
|
shouldUpdateView = false
|
|
kind = .ghost
|
|
surfaceType = .colorFill
|
|
size = .large
|
|
floating = false
|
|
hideBorder = true
|
|
iconOffset = .init(x: 0, y: 0)
|
|
iconName = nil
|
|
selectedIconName = nil
|
|
showBadgeIndicator = false
|
|
selectable = false
|
|
badgeIndicatorModel = nil
|
|
shouldUpdateView = true
|
|
setNeedsUpdate()
|
|
}
|
|
|
|
/// Used to make changes to the View based off a change events or from local properties.
|
|
open override func updateView() {
|
|
super.updateView()
|
|
|
|
//ensure there is an icon to set
|
|
if let currentIconName {
|
|
icon.name = currentIconName
|
|
let color = iconColorConfiguration.getColor(self)
|
|
icon.color = color
|
|
icon.size = size.value
|
|
icon.customSize = customSize
|
|
icon.isEnabled = isEnabled
|
|
} else {
|
|
icon.reset()
|
|
}
|
|
updateBadgeIndicator()
|
|
setNeedsLayout()
|
|
}
|
|
|
|
open override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
|
|
let currentConfig = currentConfiguration
|
|
|
|
backgroundColor = currentConfig.backgroundColorConfiguration.getColor(self)
|
|
|
|
// calculate center point for child view with offset
|
|
let childCenter = CGPoint(x: center.x + iconOffset.x, y: center.y + iconOffset.y)
|
|
centerXConstraint?.constant = childCenter.x - center.x
|
|
centerYConstraint?.constant = childCenter.y - center.y
|
|
|
|
//updating current container size
|
|
var iconLayoutSize = size.containerSize
|
|
if let customSize {
|
|
iconLayoutSize = CGFloat(customSize)
|
|
}
|
|
// check to see if this is fitToIcon
|
|
if fitToIcon && kind == .ghost {
|
|
iconLayoutSize = size.value.dimensions.width
|
|
layer.cornerRadius = 0
|
|
} else {
|
|
layer.cornerRadius = min(frame.width, frame.height) / 2.0
|
|
}
|
|
|
|
layoutGuideWidthConstraint?.constant = iconLayoutSize
|
|
layoutGuideHeightConstraint?.constant = iconLayoutSize
|
|
|
|
//border
|
|
if let borderable = currentConfig as? Borderable, hideBorder {
|
|
layer.borderColor = borderable.borderColorConfiguration.getColor(self).cgColor
|
|
layer.borderWidth = borderable.borderWidth
|
|
} else {
|
|
layer.borderColor = nil
|
|
layer.borderWidth = 0
|
|
}
|
|
|
|
badgeIndicatorCenterXConstraint?.constant = badgeIndicatorOffset.x + badgeIndicatorDefaultSize.width/2
|
|
badgeIndicatorCenterYConstraint?.constant = badgeIndicatorOffset.y + badgeIndicatorDefaultSize.height/2
|
|
badgeIndicatorLeadingConstraint?.constant = badgeIndicatorOffset.x
|
|
badgeIndicatorTrailingConstraint?.constant = badgeIndicatorOffset.x + badgeIndicatorDefaultSize.width
|
|
|
|
if showBadgeIndicator {
|
|
updateExpandDirectionalConstraints()
|
|
}
|
|
|
|
if let configurations = (currentConfig as? DropShadowableConfiguration)?.configurations {
|
|
addDropShadows(configurations)
|
|
} else {
|
|
removeDropShadows()
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Private Methods
|
|
//--------------------------------------------------
|
|
private func updateBadgeIndicator() {
|
|
badgeIndicator.isHidden = !showBadgeIndicator
|
|
|
|
guard let badgeIndicatorModel else {
|
|
badgeIndicator.isHidden = true
|
|
return
|
|
}
|
|
badgeIndicator.surface = surface
|
|
badgeIndicator.kind = badgeIndicatorModel.kind
|
|
badgeIndicator.fillColor = badgeIndicatorModel.fillColor
|
|
badgeIndicator.size = badgeIndicatorModel.size
|
|
badgeIndicator.maximumDigits = badgeIndicatorModel.maximumDigits
|
|
badgeIndicator.width = badgeIndicatorModel.width
|
|
badgeIndicator.height = badgeIndicatorModel.height
|
|
badgeIndicator.number = badgeIndicatorModel.number
|
|
badgeIndicator.leadingCharacter = badgeIndicatorModel.leadingCharacter
|
|
badgeIndicator.trailingText = badgeIndicatorModel.trailingText
|
|
badgeIndicator.dotSize = badgeIndicatorModel.dotSize
|
|
badgeIndicator.verticalPadding = badgeIndicatorModel.verticalPadding
|
|
badgeIndicator.horizontalPadding = badgeIndicatorModel.horizontalPadding
|
|
badgeIndicator.hideDot = badgeIndicatorModel.hideDot
|
|
badgeIndicator.hideBorder = badgeIndicatorModel.hideBorder
|
|
}
|
|
|
|
private func updateExpandDirectionalConstraints() {
|
|
guard let badgeIndicatorModel else { return }
|
|
switch badgeIndicatorModel.expandDirection {
|
|
case .right:
|
|
badgeIndicatorLeadingConstraint?.isActive = true
|
|
badgeIndicatorTrailingConstraint?.isActive = false
|
|
badgeIndicatorCenterXConstraint?.isActive = false
|
|
case .center:
|
|
badgeIndicatorLeadingConstraint?.isActive = false
|
|
badgeIndicatorTrailingConstraint?.isActive = false
|
|
badgeIndicatorCenterXConstraint?.isActive = true
|
|
case .left:
|
|
badgeIndicatorLeadingConstraint?.isActive = false
|
|
badgeIndicatorCenterXConstraint?.isActive = false
|
|
badgeIndicatorTrailingConstraint?.isActive = true
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: AppleGuidelinesTouchable
|
|
extension ButtonIcon: AppleGuidelinesTouchable {
|
|
/// Overrides to ensure that the touch point meets a minimum of the minimumTappableArea.
|
|
override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
Self.acceptablyOutsideBounds(point: point, bounds: bounds)
|
|
}
|
|
}
|
|
|
|
private protocol Borderable {
|
|
var borderWidth: CGFloat { get set }
|
|
var borderColorConfiguration: AnyColorable { get set }
|
|
}
|
|
|
|
private protocol Configuration {
|
|
var kind: ButtonIcon.Kind { get set }
|
|
var surfaceType: ButtonIcon.SurfaceType { get set }
|
|
var floating: Bool { get set }
|
|
var backgroundColorConfiguration: AnyColorable { get set }
|
|
}
|