// // BaseButton.swift // VDS // // Created by Matt Bruce on 11/22/22. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine public protocol Buttonable: UIControl, Surfaceable, Disabling { var availableSizes: [ButtonSize] { get } var text: String? { get set } var intrinsicContentSize: CGSize { get } } @objc(VDSButtonBase) open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable { //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- public var subject = PassthroughSubject() public var subscribers = Set() //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var initialSetupPerformed = false //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- open var availableSizes: [ButtonSize] { [] } open var text: String? { didSet { didChange() } } open var attributes: [any LabelAttributeModel]? { nil } open var surface: Surface = .light { didSet { didChange() }} open var disabled: Bool = false { didSet { isEnabled = !disabled } } open override var isHighlighted: Bool { didSet { if isHighlighted != oldValue { updateView() } } } open var typograpicalStyle: TypographicalStyle { .defaultStyle } open var textColor: UIColor { .black } open override var isEnabled: Bool { get { !disabled } set { if disabled != !newValue { disabled = !newValue } isUserInteractionEnabled = isEnabled didChange() } } //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) initialSetup() } public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) initialSetup() } //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- open func initialSetup() { if !initialSetupPerformed { backgroundColor = .clear translatesAutoresizingMaskIntoConstraints = false accessibilityCustomActions = [] accessibilityTraits = .staticText setup() setupDidChangeEvent() updateView() } } open func setup() { translatesAutoresizingMaskIntoConstraints = false titleLabel?.adjustsFontSizeToFitWidth = false titleLabel?.lineBreakMode = .byTruncatingTail } open func reset() { surface = .light disabled = false text = nil accessibilityCustomActions = [] accessibilityTraits = .button } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- override open var intrinsicContentSize: CGSize { let intrinsicContentSize = super.intrinsicContentSize let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom return CGSize(width: adjustedWidth, height: adjustedHeight) } open func updateView() { updateLabel() } //-------------------------------------------------- // MARK: - PRIVATE //-------------------------------------------------- private func updateLabel() { let font = typograpicalStyle.font //clear the arrays holding actions accessibilityCustomActions = [] //create the primary string let startingAttributes = [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: textColor] let mutableText = NSMutableAttributedString(string: text ?? "No Text", attributes: startingAttributes) //set the local lineHeight/lineSpacing attributes //get the range let entireRange = NSRange(location: 0, length: mutableText.length) //set letterSpacing if typograpicalStyle.letterSpacing > 0.0 { mutableText.addAttribute(.kern, value: typograpicalStyle.letterSpacing, range: entireRange) } let paragraph = NSMutableParagraphStyle().with { $0.alignment = titleLabel?.textAlignment ?? .center $0.lineBreakMode = titleLabel?.lineBreakMode ?? .byTruncatingTail } //set lineHeight if typograpicalStyle.lineHeight > 0.0 { let lineHeight = typograpicalStyle.lineHeight let adjustment = lineHeight > font.lineHeight ? 2.0 : 1.0 let baselineOffset = (lineHeight - font.lineHeight) / 2.0 / adjustment paragraph.maximumLineHeight = lineHeight paragraph.minimumLineHeight = lineHeight mutableText.addAttribute(.baselineOffset, value: baselineOffset, range: entireRange) mutableText.addAttribute( .paragraphStyle, value: paragraph, range: entireRange) } else { mutableText.addAttribute( .paragraphStyle, value: paragraph, range: entireRange) } if let attributes = attributes { //loop through the models attributes for attribute in attributes { //add attribute on the string attribute.setAttribute(on: mutableText) } } //set the attributed text setAttributedTitle(mutableText, for: .normal) setAttributedTitle(mutableText, for: .highlighted) } }