From cb2d8145a2d425b002bf179e19b5ef44add8950b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 22 Nov 2022 13:07:27 -0600 Subject: [PATCH] created basebutton Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 + .../Buttons/Button/BaseButton.swift | 182 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 VDS/Components/Buttons/Button/BaseButton.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 0244a9b3..1ba01d97 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ EAB5FED429267EB300998C17 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FED329267EB300998C17 /* UIView.swift */; }; EAB5FEED2927E1B200998C17 /* ButtonGroupPositionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEEC2927E1B200998C17 /* ButtonGroupPositionLayout.swift */; }; EAB5FEF12927F4AA00998C17 /* SelfSizingCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */; }; + EAB5FEF5292D371F00998C17 /* BaseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF4292D371F00998C17 /* BaseButton.swift */; }; EAC9257D29119B5400091998 /* TextLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9257C29119B5400091998 /* TextLink.swift */; }; EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC925822911B35300091998 /* TextLinkCaret.swift */; }; EAC925842911C63100091998 /* Colorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEDF28F49DB3003B3210 /* Colorable.swift */; }; @@ -153,6 +154,7 @@ EAB5FED329267EB300998C17 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; EAB5FEEC2927E1B200998C17 /* ButtonGroupPositionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupPositionLayout.swift; sourceTree = ""; }; EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizingCollectionView.swift; sourceTree = ""; }; + EAB5FEF4292D371F00998C17 /* BaseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseButton.swift; sourceTree = ""; }; EAC9257C29119B5400091998 /* TextLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLink.swift; sourceTree = ""; }; EAC925822911B35300091998 /* TextLinkCaret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLinkCaret.swift; sourceTree = ""; }; EAC925872911C9DE00091998 /* TextEntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; @@ -203,6 +205,7 @@ isa = PBXGroup; children = ( 5FC35BE228D51405004EBEAC /* Button.swift */, + EAB5FEF4292D371F00998C17 /* BaseButton.swift */, ); path = Button; sourceTree = ""; @@ -654,6 +657,7 @@ EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */, EAC925842911C63100091998 /* Colorable.swift in Sources */, EA3361C5289030FC0071C351 /* Accessable.swift in Sources */, + EAB5FEF5292D371F00998C17 /* BaseButton.swift in Sources */, EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */, EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */, EAF7F11728A1475A00B287F5 /* RadioButton.swift in Sources */, diff --git a/VDS/Components/Buttons/Button/BaseButton.swift b/VDS/Components/Buttons/Button/BaseButton.swift new file mode 100644 index 00000000..6a92c0fe --- /dev/null +++ b/VDS/Components/Buttons/Button/BaseButton.swift @@ -0,0 +1,182 @@ +// +// 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(VDSBaseButton) +open class BaseButton: 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) + } +}