From cb2d8145a2d425b002bf179e19b5ef44add8950b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 22 Nov 2022 13:07:27 -0600 Subject: [PATCH 1/9] 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) + } +} From 17eab885495f1b403deeadda8f4c6561c6634ca3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 22 Nov 2022 13:07:37 -0600 Subject: [PATCH 2/9] allow subclass Signed-off-by: Matt Bruce --- VDS/Classes/ColorConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Classes/ColorConfiguration.swift b/VDS/Classes/ColorConfiguration.swift index 89cfe2c4..cafac5f9 100644 --- a/VDS/Classes/ColorConfiguration.swift +++ b/VDS/Classes/ColorConfiguration.swift @@ -68,7 +68,7 @@ extension DisabledSurfaceColorable { /// let textColor = config.getColor(model) //returns .white /// /// -final public class DisabledSurfaceColorConfiguration: DisabledSurfaceColorable { +open class DisabledSurfaceColorConfiguration: DisabledSurfaceColorable { public typealias ObjectType = Surfaceable & Disabling public var disabled = SurfaceColorConfiguration() public var enabled = SurfaceColorConfiguration() From 372dd0cfd0c9f5b72c976b957bafad3411f1b0cd Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 22 Nov 2022 13:07:47 -0600 Subject: [PATCH 3/9] added isHighlighted Signed-off-by: Matt Bruce --- VDS/Classes/Control.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VDS/Classes/Control.swift b/VDS/Classes/Control.swift index bd37b435..90dae4e5 100644 --- a/VDS/Classes/Control.swift +++ b/VDS/Classes/Control.swift @@ -29,6 +29,8 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable { open override var isSelected: Bool { didSet { didChange() } } + open override var isHighlighted: Bool { didSet { updateView() } } + open override var isEnabled: Bool { get { !disabled } set { From 1a45cfa6c10c40d680bc387526c764ba16e4fdee Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 22 Nov 2022 13:08:07 -0600 Subject: [PATCH 4/9] refactored the 3 buttons to use basebutton Signed-off-by: Matt Bruce --- VDS/Components/Buttons/Button/Button.swift | 135 +++++++----------- .../Buttons/TextLink/TextLink.swift | 97 ++++++++----- .../Buttons/TextLinkCaret/TextLinkCaret.swift | 91 ++++++------ 3 files changed, 158 insertions(+), 165 deletions(-) diff --git a/VDS/Components/Buttons/Button/Button.swift b/VDS/Components/Buttons/Button/Button.swift index 89a9440b..fb43f30f 100644 --- a/VDS/Components/Buttons/Button/Button.swift +++ b/VDS/Components/Buttons/Button/Button.swift @@ -11,26 +11,14 @@ 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 } -} - public enum ButtonSize: String, Codable, CaseIterable { case large case small } @objc(VDSButton) -open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, Useable { +open class Button: BaseButton, Useable { - //-------------------------------------------------- - // MARK: - Combine Properties - //-------------------------------------------------- - public var subject = PassthroughSubject() - public var subscribers = Set() - //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -42,31 +30,21 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var availableSizes: [ButtonSize] { [.large, .small] } + open override var availableSizes: [ButtonSize] { [.large, .small] } - open var text: String? { didSet { didChange() } } - open var use: Use = .primary { didSet { didChange() }} open var size: ButtonSize = .large { didSet { didChange() }} open var width: CGFloat? { didSet { didChange() }} - - open var surface: Surface = .light { didSet { didChange() }} - - open var disabled: Bool = false { didSet { isEnabled = !disabled } } - open override var isEnabled: Bool { - get { !disabled } - set { - if disabled != !newValue { - disabled = !newValue - } - isUserInteractionEnabled = isEnabled - didChange() - } + open override var textColor: UIColor { + buttonTitleColorConfiguration.getColor(self) + } + + open override var typograpicalStyle: TypographicalStyle { + size == .large ? TypographicalStyle.BoldBodyLarge : TypographicalStyle.BoldBodySmall } - //-------------------------------------------------- // MARK: - Configuration Properties @@ -76,23 +54,35 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, $0.primary.enabled.darkColor = VDSColor.backgroundPrimaryLight $0.primary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.primary.disabled.darkColor = VDSColor.interactiveDisabledOndark + + $0.primaryHighlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.primaryHighlighted.darkColor = VDSColor.interactiveActiveOndark $0.secondary.enabled.lightColor = UIColor.clear $0.secondary.enabled.darkColor = UIColor.clear $0.secondary.disabled.lightColor = UIColor.clear $0.secondary.disabled.darkColor = UIColor.clear + + $0.secondaryHighlighted.lightColor = UIColor.clear + $0.secondaryHighlighted.darkColor = UIColor.clear } - + private var buttonBorderColorConfiguration = UseableColorConfiguration().with { $0.primary.enabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.enabled.darkColor = VDSColor.elementsPrimaryOnlight $0.primary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.primary.disabled.darkColor = VDSColor.interactiveDisabledOndark - + + $0.primaryHighlighted.lightColor = VDSColor.elementsPrimaryOndark + $0.primaryHighlighted.darkColor = VDSColor.elementsPrimaryOnlight + $0.secondary.enabled.lightColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.darkColor = VDSColor.elementsPrimaryOndark $0.secondary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.secondary.disabled.darkColor = VDSColor.interactiveDisabledOndark + + $0.secondaryHighlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.secondaryHighlighted.darkColor = VDSColor.interactiveActiveOndark } private var buttonTitleColorConfiguration = UseableColorConfiguration().with { @@ -100,11 +90,17 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, $0.primary.enabled.darkColor = VDSColor.elementsPrimaryOnlight $0.primary.disabled.lightColor = VDSColor.elementsPrimaryOndark $0.primary.disabled.darkColor = VDSColor.elementsPrimaryOnlight - + + $0.primaryHighlighted.lightColor = VDSColor.elementsPrimaryOndark + $0.primaryHighlighted.darkColor = VDSColor.elementsPrimaryOnlight + $0.secondary.enabled.lightColor = VDSColor.elementsPrimaryOnlight $0.secondary.enabled.darkColor = VDSColor.elementsPrimaryOndark $0.secondary.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.secondary.disabled.darkColor = VDSColor.interactiveDisabledOndark + + $0.secondaryHighlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.secondaryHighlighted.darkColor = VDSColor.interactiveActiveOndark } //-------------------------------------------------- @@ -112,91 +108,57 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, //-------------------------------------------------- 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 override func setup() { + super.setup() //only 1 of the 2 widths can be on at the same time widthConstraint = widthAnchor.constraint(equalToConstant: 0) minWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: size.minimumWidth) //height - heightConstraint = heightAnchor.constraint(equalToConstant: size.height) + heightConstraint = heightAnchor.constraint(equalToConstant: 0) heightConstraint?.isActive = true } - open func reset() { - surface = .light - disabled = false + open override func reset() { + super.reset() use = .primary width = nil size = .large - accessibilityCustomActions = [] - accessibilityTraits = .staticText } //-------------------------------------------------- // 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() { - + open override func updateView() { + super.updateView() let bgColor = buttonBackgroundColorConfiguration.getColor(self) let borderColor = buttonBorderColorConfiguration.getColor(self) - let titleColor = buttonTitleColorConfiguration.getColor(self) let borderWidth = use == .secondary ? 1.0 : 0.0 let buttonHeight = size.height - let cornerRadius = buttonHeight / 2 + let cornerRadius = size.cornerRadius let minWidth = size.minimumWidth - let font = size == .large ? TypographicalStyle.BoldBodyLarge.font : TypographicalStyle.BoldBodySmall.font let edgeInsets = size.edgeInsets - - setTitle(text ?? "No Text", for: .normal) - titleLabel?.font = font + backgroundColor = bgColor - setTitleColor(titleColor, for: .normal) layer.borderColor = borderColor.cgColor layer.cornerRadius = cornerRadius layer.borderWidth = borderWidth contentEdgeInsets = edgeInsets - + minWidthConstraint?.constant = minWidth heightConstraint?.constant = buttonHeight @@ -215,17 +177,23 @@ open class Button: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, //-------------------------------------------------- private class UseableColorConfiguration: ObjectColorable { - typealias ObjectType = Disabling & Surfaceable & Useable + typealias ObjectType = Buttonable & Useable public var primary = DisabledSurfaceColorConfiguration() public var secondary = DisabledSurfaceColorConfiguration() + + public var primaryHighlighted = SurfaceColorConfiguration() + public var secondaryHighlighted = SurfaceColorConfiguration() required public init(){} public func getColor(_ object: ObjectType) -> UIColor { - return object.use == .primary ? primary.getColor(object) : secondary.getColor(object) + if object.isHighlighted { + return object.use == .primary ? primaryHighlighted.getColor(object) : secondaryHighlighted.getColor(object) + } else { + return object.use == .primary ? primary.getColor(object) : secondary.getColor(object) + } } - } - + } } extension ButtonSize { @@ -239,6 +207,10 @@ extension ButtonSize { } } + public var cornerRadius: CGFloat { + height / 2 + } + public var minimumWidth: CGFloat { switch self { case .large: @@ -267,7 +239,6 @@ extension ButtonSize { } extension Use { - public var color: UIColor { return self == .primary ? VDSColor.backgroundPrimaryDark : .clear } diff --git a/VDS/Components/Buttons/TextLink/TextLink.swift b/VDS/Components/Buttons/TextLink/TextLink.swift index f3e8a6f3..fbe3ab0f 100644 --- a/VDS/Components/Buttons/TextLink/TextLink.swift +++ b/VDS/Components/Buttons/TextLink/TextLink.swift @@ -12,23 +12,40 @@ import VDSFormControlsTokens import Combine @objc(VDSTextLink) -open class TextLink: Control, Buttonable { +open class TextLink: BaseButton { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var heightConstraint: NSLayoutConstraint? - private var label = Label() - + private var lineHeightConstraint: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var text: String? { didSet { didChange() } } - open var size: ButtonSize = .large { didSet { didChange() }} - public var availableSizes: [ButtonSize] { [.large, .small] } + open override var availableSizes: [ButtonSize] { [.large, .small] } + open override var typograpicalStyle: TypographicalStyle { + size == .large ? TypographicalStyle.BodyLarge : TypographicalStyle.BodySmall + } + + open override var textColor: UIColor { + textColorConfiguration.getColor(self) + } + + private var textColorConfiguration = HighlightDisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsSecondaryOnlight + $0.disabled.darkColor = VDSColor.elementsSecondaryOndark + $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.enabled.darkColor = VDSColor.elementsPrimaryOndark + + $0.highlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.highlighted.darkColor = VDSColor.interactiveActiveOndark + + } + private var height: CGFloat { switch size { case .large: @@ -43,49 +60,42 @@ open class TextLink: Control, Buttonable { //-------------------------------------------------- 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() + } + + private var line = UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false } //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- - open override func initialSetup() { - super.initialSetup() - } - open override func setup() { super.setup() - addSubview(label) - - //add tapGesture to self - publisher(for: UITapGestureRecognizer()).sink { [weak self] _ in - self?.sendActions(for: .touchUpInside) - }.store(in: &subscribers) - - //pin stackview to edges - label.pinToSuperView() - label.numberOfLines = 1 + if let titleLabel { + addSubview(line) + line.pinLeading(titleLabel.leadingAnchor) + line.pinTrailing(titleLabel.trailingAnchor) + line.pinTop(titleLabel.bottomAnchor, 1) + lineHeightConstraint = line.heightAnchor.constraint(equalToConstant: 1.0) + lineHeightConstraint?.isActive = true + } + heightConstraint = heightAnchor.constraint(equalToConstant: height) heightConstraint?.isActive = true } open override func reset() { super.reset() - label.reset() - size = .large - text = nil - + size = .large accessibilityCustomActions = [] accessibilityTraits = .staticText } @@ -93,17 +103,32 @@ open class TextLink: Control, Buttonable { //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- - override open var intrinsicContentSize: CGSize { - return CGSize(width: label.intrinsicContentSize.width, height: height) - } - open override func updateView() { - label.surface = surface - label.disabled = disabled - label.typograpicalStyle = size == .large ? TypographicalStyle.BodyLarge : TypographicalStyle.BodySmall - label.text = text ?? "" - label.attributes = [UnderlineLabelAttribute(location: 0, length: label.text!.count)] + //need to set the properties so the super class + //can render out the label correctly heightConstraint?.constant = height + lineHeightConstraint?.constant = isHighlighted ? 2.0 : 1.0 + line.backgroundColor = textColor + + //always call last so the label is rendered + super.updateView() } } + +class HighlightDisabledSurfaceColorConfiguration: ObjectColorable { + typealias ObjectType = Buttonable + public var highlighted = SurfaceColorConfiguration() + public var disabled = SurfaceColorConfiguration() + public var enabled = SurfaceColorConfiguration() + + required public init(){} + + public func getColor(_ object: any ObjectType) -> UIColor { + if object.isHighlighted { + return highlighted.getColor(object) + } else { + return object.disabled ? disabled.getColor(object) : enabled.getColor(object) + } + } +} diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index 577ff332..e4733a1b 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -16,103 +16,103 @@ public enum TextLinkCaretPosition: String, CaseIterable { } @objc(VDSTextLinkCaret) -open class TextLinkCaret: Control, Buttonable { +open class TextLinkCaret: BaseButton { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var heightConstraint: NSLayoutConstraint? - private var label = Label().with { - $0.typograpicalStyle = TypographicalStyle.BoldBodyLarge + open override var typograpicalStyle: TypographicalStyle { + TypographicalStyle.BoldBodyLarge } - + private var caretView = CaretView().with { $0.size = CaretView.CaretSize.small(.vertical) $0.lineWidth = 2 } + private var imageAttribute: ImageLabelAttribute? + + open override var attributes: [any LabelAttributeModel]? { + guard let imageAttribute else { return nil } + return [imageAttribute] + } //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var availableSizes: [ButtonSize] { [.large] } - - open var text: String? { didSet { didChange() } } + public override var availableSizes: [ButtonSize] { [.large] } open var iconPosition: TextLinkCaretPosition = .right { didSet { didChange() } } private var height: CGFloat { 44 } + + private var _text: String? + + open override var text: String? { + get{ _text } + set { + var updatedText = newValue ?? "" + updatedText = iconPosition == .right ? "\(updatedText) " : " \(updatedText)" + _text = updatedText + didChange() + } + } + + open override var textColor: UIColor { + textColorConfiguration.getColor(self) + } + private var textColorConfiguration = HighlightDisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsSecondaryOnlight + $0.disabled.darkColor = VDSColor.elementsSecondaryOndark + $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.enabled.darkColor = VDSColor.elementsPrimaryOndark + + $0.highlighted.lightColor = VDSColor.interactiveActiveOnlight + $0.highlighted.darkColor = VDSColor.interactiveActiveOndark + } + //-------------------------------------------------- // 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 override func initialSetup() { - super.initialSetup() - } - open override func setup() { super.setup() - //add tapGesture to self - publisher(for: UITapGestureRecognizer()).sink { [weak self] _ in - self?.sendActions(for: .touchUpInside) - }.store(in: &subscribers) //constraints heightAnchor.constraint(greaterThanOrEqualToConstant: height).isActive = true let size = caretView.size!.dimensions() caretView.frame = .init(x: 0, y: 0, width: size.width, height: size.height) - addSubview(label) - label.pinToSuperView() - - label.numberOfLines = 1 - } - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - private var caretLeadingConstraint: NSLayoutConstraint? - private var caretTrailingConstraint: NSLayoutConstraint? - private var labelConstraint: NSLayoutConstraint? - open override func reset() { super.reset() - label.reset() - - label.typograpicalStyle = TypographicalStyle.BoldBodyLarge - text = nil iconPosition = .right - - accessibilityCustomActions = [] - accessibilityTraits = .staticText } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- override open var intrinsicContentSize: CGSize { - var itemWidth = label.intrinsicContentSize.width + var itemWidth = super.intrinsicContentSize.width if let caretWidth = caretView.size?.dimensions().width { itemWidth += caretWidth } @@ -121,22 +121,19 @@ open class TextLinkCaret: Control, Buttonable { open override func updateView() { - let updatedText = text ?? "" - + var updatedText = text ?? "" caretView.surface = surface caretView.disabled = disabled caretView.direction = iconPosition == .right ? CaretView.Direction.right : CaretView.Direction.left let image = caretView.getImage() - let location = iconPosition == .right ? updatedText.count + 1 : 0 - let textColor = label.textColorConfiguration.getColor(self) - let imageAttribute = ImageLabelAttribute(location: location, + let location = iconPosition == .right ? updatedText.count : 0 + + imageAttribute = ImageLabelAttribute(location: location, image: image, tintColor: textColor) - label.surface = surface - label.disabled = disabled - label.text = iconPosition == .right ? "\(updatedText) " : " \(updatedText)" - label.attributes = [imageAttribute] + + super.updateView() } } From ca5d67cea2fe8a6721d394b170ba07d973a402ce Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 28 Nov 2022 14:28:53 -0600 Subject: [PATCH 5/9] refactored BaseButton to ButtonBase Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 8 ++++---- VDS/Components/Buttons/Button/Button.swift | 2 +- .../Buttons/Button/{BaseButton.swift => ButtonBase.swift} | 4 ++-- VDS/Components/Buttons/TextLink/TextLink.swift | 2 +- VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename VDS/Components/Buttons/Button/{BaseButton.swift => ButtonBase.swift} (98%) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 1ba01d97..18741ce7 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -61,7 +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 */; }; + EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF4292D371F00998C17 /* ButtonBase.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 */; }; @@ -154,7 +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 = ""; }; + EAB5FEF4292D371F00998C17 /* ButtonBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonBase.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 = ""; }; @@ -205,7 +205,7 @@ isa = PBXGroup; children = ( 5FC35BE228D51405004EBEAC /* Button.swift */, - EAB5FEF4292D371F00998C17 /* BaseButton.swift */, + EAB5FEF4292D371F00998C17 /* ButtonBase.swift */, ); path = Button; sourceTree = ""; @@ -657,7 +657,7 @@ EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */, EAC925842911C63100091998 /* Colorable.swift in Sources */, EA3361C5289030FC0071C351 /* Accessable.swift in Sources */, - EAB5FEF5292D371F00998C17 /* BaseButton.swift in Sources */, + EAB5FEF5292D371F00998C17 /* ButtonBase.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/Button.swift b/VDS/Components/Buttons/Button/Button.swift index fb43f30f..72953bdb 100644 --- a/VDS/Components/Buttons/Button/Button.swift +++ b/VDS/Components/Buttons/Button/Button.swift @@ -17,7 +17,7 @@ public enum ButtonSize: String, Codable, CaseIterable { } @objc(VDSButton) -open class Button: BaseButton, Useable { +open class Button: ButtonBase, Useable { //-------------------------------------------------- // MARK: - Private Properties diff --git a/VDS/Components/Buttons/Button/BaseButton.swift b/VDS/Components/Buttons/Button/ButtonBase.swift similarity index 98% rename from VDS/Components/Buttons/Button/BaseButton.swift rename to VDS/Components/Buttons/Button/ButtonBase.swift index 6a92c0fe..887fff2c 100644 --- a/VDS/Components/Buttons/Button/BaseButton.swift +++ b/VDS/Components/Buttons/Button/ButtonBase.swift @@ -17,8 +17,8 @@ public protocol Buttonable: UIControl, Surfaceable, Disabling { var intrinsicContentSize: CGSize { get } } -@objc(VDSBaseButton) -open class BaseButton: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable { +@objc(VDSButtonBase) +open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable { //-------------------------------------------------- // MARK: - Combine Properties diff --git a/VDS/Components/Buttons/TextLink/TextLink.swift b/VDS/Components/Buttons/TextLink/TextLink.swift index fbe3ab0f..292e28ad 100644 --- a/VDS/Components/Buttons/TextLink/TextLink.swift +++ b/VDS/Components/Buttons/TextLink/TextLink.swift @@ -12,7 +12,7 @@ import VDSFormControlsTokens import Combine @objc(VDSTextLink) -open class TextLink: BaseButton { +open class TextLink: ButtonBase { //-------------------------------------------------- // MARK: - Private Properties diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index e4733a1b..7555095e 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -16,7 +16,7 @@ public enum TextLinkCaretPosition: String, CaseIterable { } @objc(VDSTextLinkCaret) -open class TextLinkCaret: BaseButton { +open class TextLinkCaret: ButtonBase { //-------------------------------------------------- // MARK: - Private Properties From 5445445136650935c4f4abf3341cf3ef3c32716d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 28 Nov 2022 14:29:09 -0600 Subject: [PATCH 6/9] added documentation Signed-off-by: Matt Bruce --- VDS/Components/Badge/Badge.swift | 2 ++ VDS/Components/Checkbox/Checkbox.swift | 1 + VDS/Components/Label/Label.swift | 5 +---- VDS/VDS.docc/VDS.md | 21 +++++++++++++++++---- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/VDS/Components/Badge/Badge.swift b/VDS/Components/Badge/Badge.swift index e61f11a9..9541a483 100644 --- a/VDS/Components/Badge/Badge.swift +++ b/VDS/Components/Badge/Badge.swift @@ -15,6 +15,8 @@ public enum BadgeFillColor: String, Codable, CaseIterable { case red, yellow, green, orange, blue, black, white } + +/// Badges are visual labels used to convey status or highlight supplemental information. @objc(VDSBadge) public class Badge: View, Accessable { diff --git a/VDS/Components/Checkbox/Checkbox.swift b/VDS/Components/Checkbox/Checkbox.swift index 22ff909b..21e31c9b 100644 --- a/VDS/Components/Checkbox/Checkbox.swift +++ b/VDS/Components/Checkbox/Checkbox.swift @@ -11,6 +11,7 @@ import VDSColorTokens import VDSFormControlsTokens import Combine +/// Checkboxes are a multi-select component through which a customer indicates a choice. If a binary choice, the component is a checkbox. If the choice has multiple options, the component is a ``CheckboxGroup``. @objc(VDSCheckbox) public class Checkbox: CheckboxBase{} diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 7fcc931e..b1e9256f 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -11,10 +11,7 @@ import VDSColorTokens import Combine @objc(VDSLabel) -public class Label: LabelBase {} - -@objc(VDSLabelBase) -open class LabelBase: UILabel, Handlerable, ViewProtocol, Resettable { +public class Label: UILabel, Handlerable, ViewProtocol, Resettable { //-------------------------------------------------- // MARK: - Combine Properties diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index 27aecc21..0cf1e32e 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -1,13 +1,26 @@ # ``VDS`` -Summary +The Verizon Design System is the single source of truth for Verizon’s digital experiences. It aligns design and code resources to give designers and developers consistent, detailed documentation and standardized libraries of symbols and coded components. ## Overview -Text +Using the system allows designers and developers to collaborate more easily and efficiently on creating on-brand and accessible digital experiences. Spend more time improving our digital products for customers and less time redrawing or rebuilding basic user interface elements. ## Topics -### Group +### Components -- ``Symbol`` \ No newline at end of file +- ``Badge`` +- ``Button`` +- ``TextLink`` +- ``TextLinkCaret`` +- ``CheckboxGroup`` +- ``Checkbox`` +- ``Label`` +- ``RadioBoxGroup`` +- ``RadioBox`` +- ``RadioButtonGroup`` +- ``RadioButton`` +- ``RadioSwatchGroup`` +- ``RadioSwatch`` +- ``Toggle`` From aacc2ca760d487b1197a593d6df7beb58347a258 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 1 Dec 2022 11:34:24 -0600 Subject: [PATCH 7/9] added property to help with instrinsic size Signed-off-by: Matt Bruce --- VDS/Components/Buttons/TextLink/TextLink.swift | 1 + VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/VDS/Components/Buttons/TextLink/TextLink.swift b/VDS/Components/Buttons/TextLink/TextLink.swift index f3e8a6f3..516131cf 100644 --- a/VDS/Components/Buttons/TextLink/TextLink.swift +++ b/VDS/Components/Buttons/TextLink/TextLink.swift @@ -76,6 +76,7 @@ open class TextLink: Control, Buttonable { //pin stackview to edges label.pinToSuperView() label.numberOfLines = 1 + label.preferredMaxLayoutWidth = 0 heightConstraint = heightAnchor.constraint(equalToConstant: height) heightConstraint?.isActive = true } diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index 577ff332..80c6dc99 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -86,6 +86,7 @@ open class TextLinkCaret: Control, Buttonable { label.pinToSuperView() label.numberOfLines = 1 + label.preferredMaxLayoutWidth = 0 } From 718acd90c56c40dd53b9606a10abbd6cb3d80e36 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 1 Dec 2022 11:34:51 -0600 Subject: [PATCH 8/9] updated for better spacing Signed-off-by: Matt Bruce --- .../Buttons/ButtonGroup/ButtonGroup.swift | 18 +- .../ButtonGroupPositionLayout.swift | 266 +++++++++++++++--- 2 files changed, 241 insertions(+), 43 deletions(-) diff --git a/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift b/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift index d15b34a3..28ddebd3 100644 --- a/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift +++ b/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift @@ -149,6 +149,8 @@ open class ButtonGroup: View, UICollectionViewDataSource, UICollectionViewDelega cell.subviews.forEach { $0.removeFromSuperview() } cell.addSubview(button) button.pinToSuperView() +// cell.layer.borderColor = UIColor.black.cgColor +// cell.layer.borderWidth = 1 return cell } @@ -156,11 +158,19 @@ open class ButtonGroup: View, UICollectionViewDataSource, UICollectionViewDelega buttons[indexPath.row].intrinsicContentSize } + public func collectionView(_ collectionView: UICollectionView, isButtonTypeForItemAtIndexPath indexPath: IndexPath) -> Bool { + if let _ = buttons[indexPath.row] as? Button { + return true + } else { + return false + } + } + + public func collectionView(_ collectionView: UICollectionView, buttonableAtIndexPath indexPath: IndexPath) -> Buttonable { + buttons[indexPath.row] + } + public func collectionView(_ collectionView: UICollectionView, insetsForItemsInSection section: Int) -> UIEdgeInsets { UIEdgeInsets.zero } - - public func collectionView(_ collectionView: UICollectionView, itemSpacingInSection section: Int) -> CGFloat { - itemSpacing - } } diff --git a/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift b/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift index 1a017135..a0f6cad4 100644 --- a/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift +++ b/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift @@ -8,27 +8,55 @@ import Foundation import UIKit +class ButtonLayoutAttributes: UICollectionViewLayoutAttributes{ + var spacing: CGFloat = 0 + var isButton: Bool = false + convenience init(spacing: CGFloat, + forCellWith indexPath: IndexPath) { + self.init(forCellWith: indexPath) + self.spacing = spacing + } +} + class ButtonCollectionViewRow { - var attributes = [UICollectionViewLayoutAttributes]() + var attributes = [ButtonLayoutAttributes]() var spacing: CGFloat = 0 init(spacing: CGFloat) { self.spacing = spacing } - func add(attribute: UICollectionViewLayoutAttributes) { + func add(attribute: ButtonLayoutAttributes) { attributes.append(attribute) } + + var hasButtons: Bool { + attributes.contains(where: { $0.isButton }) + } var rowWidth: CGFloat { return attributes.reduce(0, { result, attribute -> CGFloat in - return result + attribute.frame.width - }) + CGFloat(attributes.count - 1) * spacing + return result + attribute.frame.width + attribute.spacing + }) + } + + var rowHeight: CGFloat { + attributes.compactMap{$0.frame.height}.max() ?? 0 + } + + var rowY: CGFloat = 0 { + didSet { + for attribute in attributes { + attribute.frame.origin.y = rowY + } + } } func layout(for position: ButtonPosition, with collectionViewWidth: CGFloat){ var offset = 0.0 + attributes.last?.spacing = 0 + switch position { case .left: break @@ -40,7 +68,7 @@ class ButtonCollectionViewRow { for attribute in attributes { attribute.frame.origin.x = offset - offset += attribute.frame.width + spacing + offset += attribute.frame.width + attribute.spacing } } } @@ -51,8 +79,8 @@ public enum ButtonPosition: String, CaseIterable { protocol ButtongGroupPositionLayoutDelegate: AnyObject { func collectionView(_ collectionView: UICollectionView, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize + func collectionView(_ collectionView: UICollectionView, buttonableAtIndexPath indexPath: IndexPath) -> any Buttonable func collectionView(_ collectionView: UICollectionView, insetsForItemsInSection section: Int) -> UIEdgeInsets - func collectionView(_ collectionView: UICollectionView, itemSpacingInSection section: Int) -> CGFloat } class ButtonGroupPositionLayout: UICollectionViewLayout { @@ -62,58 +90,74 @@ class ButtonGroupPositionLayout: UICollectionViewLayout { // Total height of the content. Will be used to configure the scrollview content var layoutHeight: CGFloat = 0.0 var position: ButtonPosition = .left - private var itemCache: [UICollectionViewLayoutAttributes] = [] + private var itemCache: [ButtonLayoutAttributes] = [] override func prepare() { super.prepare() - + + let rowSpacingButton = 12.0 + let rowSpacingTextLink = 12.0 + itemCache.removeAll() layoutHeight = 0.0 - - guard let collectionView = collectionView else { + + guard let collectionView, let delegate else { return } var itemSpacing = 0.0 // Variable to track the width of the layout at the current state when the item is being drawn var layoutWidthIterator: CGFloat = 0.0 - for section in 0.. collectionView.frame.width { - // If the current row width (after this item being laid out) is exceeding the width of the collection view content, put it in the next line - layoutWidthIterator = 0.0 - layoutHeight += itemSize.height + interItemSpacing - } - - let frame = CGRect(x: layoutWidthIterator + insets.left, y: layoutHeight, width: itemSize.width, height: itemSize.height) - let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) - attributes.frame = frame - itemCache.append(attributes) - layoutWidthIterator = layoutWidthIterator + frame.width + interItemSpacing + itemSize = delegate.collectionView(collectionView, sizeForItemAtIndexPath: indexPath) + + if (layoutWidthIterator + itemSize.width + insets.left + insets.right) > collectionView.frame.width { + // If the current row width (after this item being laid out) is exceeding the width of the collection view content, put it in the next line + layoutWidthIterator = 0.0 + layoutHeight += itemSize.height + rowSpacing + } + + let itemButtonable = delegate.collectionView(collectionView, buttonableAtIndexPath: indexPath) + + let nextItem = item + 1 + if nextItem < totalItems { + let neighbor = delegate.collectionView(collectionView, buttonableAtIndexPath: IndexPath(item: nextItem, section: section)) + itemSpacing = getHorizontalSpacing(for: itemButtonable, neighboring: neighbor) } - layoutHeight += itemSize.height + insets.bottom - layoutWidthIterator = 0.0 + let frame = CGRect(x: layoutWidthIterator + insets.left, y: layoutHeight, width: itemSize.width, height: itemSize.height) + print(frame) + let attributes = ButtonLayoutAttributes(spacing: itemSpacing, forCellWith: indexPath) + attributes.frame = frame + attributes.isButton = isButton(buttonable: itemButtonable) + itemCache.append(attributes) + + layoutWidthIterator = layoutWidthIterator + frame.width + itemSpacing } + print("*******") + layoutHeight += itemSize.height + insets.bottom + layoutWidthIterator = 0.0 + //Turn into rows and re-calculate var rows = [ButtonCollectionViewRow]() var currentRowY: CGFloat = -1 - + for attribute in itemCache { if currentRowY != attribute.frame.midY { currentRowY = attribute.frame.midY @@ -121,12 +165,156 @@ class ButtonGroupPositionLayout: UICollectionViewLayout { } rows.last?.add(attribute: attribute) } - + //recalculate rows based off of positions rows.forEach { $0.layout(for: position, with: collectionView.frame.width) } let rowAttributes = rows.flatMap { $0.attributes } + + layoutHeight = insets.top + for item in 0.. 0 && item < rows.count { + rowSpacing = row.hasButtons ? rowSpacingButton : rowSpacingTextLink + } + + if item > 0 { + row.rowY = layoutHeight + rowSpacing + layoutHeight += rowSpacing + } + + layoutHeight += row.rowHeight + } + layoutHeight += insets.bottom + + itemCache = rowAttributes - + + } + + func isButton(buttonable: Buttonable) -> Bool{ + if let _ = buttonable as? Button { + return true + } else { + return false + } + } + + func getHorizontalSpacing(for primary: Buttonable, neighboring: Buttonable) -> CGFloat { + let defaultSpace = 12.0 + //large button + if let button = primary as? Button, button.size == .large { + if let neighboringButton = neighboring as? Button, neighboringButton.size == .large { + return 12.0 + } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large { + return 16.0 + } else if let _ = neighboring as? TextLinkCaret { + return 24.0 + } else { + return defaultSpace + } + } + //large text link + else if let textLink = primary as? TextLink, textLink.size == .large { + if let neighboringButton = neighboring as? Button, neighboringButton.size == .large { + return 16.0 + } else if let _ = neighboring as? TextLinkCaret { + return 24.0 + } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large { + return 16.0 + } else { + return defaultSpace + } + } + //text link caret + else if let _ = primary as? TextLinkCaret { + if let _ = neighboring as? TextLinkCaret { + return 24.0 + } else { + return defaultSpace + } + } + //small button + else if let button = primary as? Button, button.size == .small { + if let neighboringButton = neighboring as? Button, neighboringButton.size == .small { + return 12.0 + } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small { + return 16.0 + } else { + return defaultSpace + } + } + //small text link + else if let textLink = primary as? TextLink, textLink.size == .small { + if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small { + return 16.0 + } else { + return defaultSpace + } + } + //return defaultSpace + else { + return defaultSpace + } + } + + func getVerticalSpacing(for primary: Buttonable, neighboring: Buttonable) -> CGFloat { + let defaultSpace = 12.0 + //large button + if let button = primary as? Button, button.size == .large { + if let neighboringButton = neighboring as? Button, neighboringButton.size == .large { + return 12.0 + } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large { + return 16.0 + } else if let _ = neighboring as? TextLinkCaret { + return 24.0 + } else { + return defaultSpace + } + } + //large text link + else if let textLink = primary as? TextLink, textLink.size == .large { + if let neighboringButton = neighboring as? Button, neighboringButton.size == .large { + return 16.0 + } else if let _ = neighboring as? TextLinkCaret { + return 24.0 + } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .large { + return 24.0 + } else { + return defaultSpace + } + } + //text link caret + else if let _ = primary as? TextLinkCaret { + if let _ = neighboring as? TextLinkCaret { + return 24.0 + } else { + return defaultSpace + } + } + //small button + else if let button = primary as? Button, button.size == .small { + if let neighboringButton = neighboring as? Button, neighboringButton.size == .small { + return 12.0 + } else if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small { + return 24.0 + } else { + return defaultSpace + } + } + //small text link + else if let textLink = primary as? TextLink, textLink.size == .small { + if let neighboringTextLink = neighboring as? TextLink, neighboringTextLink.size == .small { + return 32.0 + } else { + return defaultSpace + } + } + //return defaultSpace + else { + return defaultSpace + } } override func layoutAttributesForElements(in rect: CGRect)-> [UICollectionViewLayoutAttributes]? { From 2050896483fd3e39e469c986e930bd1eb17b3263 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 1 Dec 2022 12:03:41 -0600 Subject: [PATCH 9/9] removed print Signed-off-by: Matt Bruce --- .../Buttons/ButtonGroup/ButtonGroupPositionLayout.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift b/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift index a0f6cad4..64751ca2 100644 --- a/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift +++ b/VDS/Components/Buttons/ButtonGroup/ButtonGroupPositionLayout.swift @@ -141,7 +141,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout { } let frame = CGRect(x: layoutWidthIterator + insets.left, y: layoutHeight, width: itemSize.width, height: itemSize.height) - print(frame) + //print(frame) let attributes = ButtonLayoutAttributes(spacing: itemSpacing, forCellWith: indexPath) attributes.frame = frame attributes.isButton = isButton(buttonable: itemButtonable) @@ -149,7 +149,7 @@ class ButtonGroupPositionLayout: UICollectionViewLayout { layoutWidthIterator = layoutWidthIterator + frame.width + itemSpacing } - print("*******") + //print("*******") layoutHeight += itemSize.height + insets.bottom layoutWidthIterator = 0.0