From ea28526689ee6958a2a1c55bc8662413b9c34049 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 1 Nov 2022 15:12:55 -0500 Subject: [PATCH 1/6] don't default to .black Signed-off-by: Matt Bruce --- VDS/Components/Label/Attributes/LabelAttributeUnderline.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/Label/Attributes/LabelAttributeUnderline.swift b/VDS/Components/Label/Attributes/LabelAttributeUnderline.swift index d7c302cc..e1358c2e 100644 --- a/VDS/Components/Label/Attributes/LabelAttributeUnderline.swift +++ b/VDS/Components/Label/Attributes/LabelAttributeUnderline.swift @@ -33,7 +33,7 @@ public struct LabelAttributeUnderline: LabelAttributeModel { } } - public init(location: Int, length: Int, style: UnderlineStyle = .single, color: UIColor = .black, pattern: UnderlineStyle.Pattern? = nil) { + public init(location: Int, length: Int, style: UnderlineStyle = .single, color: UIColor? = nil, pattern: UnderlineStyle.Pattern? = nil) { self.location = location self.length = length self.color = color From ced2bec5c23e8fa7b94b41bd82c54036f41d0414 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 1 Nov 2022 15:13:11 -0500 Subject: [PATCH 2/6] added TextLink/TextLinkCaret Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 24 ++++ VDS/Components/TextLink/TextLink.swift | 103 ++++++++++++++++++ .../TextLinkCaret/TextLinkCaret.swift | 98 +++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 VDS/Components/TextLink/TextLink.swift create mode 100644 VDS/Components/TextLinkCaret/TextLinkCaret.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 22a08b39..ad2df115 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -54,6 +54,8 @@ EAB1D2CF28ABEF2B00DAE764 /* Typography.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */; }; EAB1D2E628AE842000DAE764 /* Publisher+Bind.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2E328AE842000DAE764 /* Publisher+Bind.swift */; }; EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */; }; + EAC9257D29119B5400091998 /* TextLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9257C29119B5400091998 /* TextLink.swift */; }; + EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC925822911B35300091998 /* TextLinkCaret.swift */; }; EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */; }; EAF7F0952899861000B287F5 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* Checkbox.swift */; }; EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; }; @@ -133,6 +135,8 @@ EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typography.swift; sourceTree = ""; }; EAB1D2E328AE842000DAE764 /* Publisher+Bind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Publisher+Bind.swift"; sourceTree = ""; }; EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControlPublisher.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 = ""; }; EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Publisher.swift"; sourceTree = ""; }; EAF7F0932899861000B287F5 /* Checkbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = ""; }; @@ -257,6 +261,8 @@ EA89200B28B530F0006B9984 /* RadioBox */, EAF7F11428A1470D00B287F5 /* RadioButton */, EA1F265F28B945070033E859 /* RadioSwatch */, + EAC9257E29119B5D00091998 /* TextLink */, + EAC925812911B34300091998 /* TextLinkCaret */, EA3361A0288B1E6F0071C351 /* Toggle */, ); path = Components; @@ -399,6 +405,22 @@ path = Publishers; sourceTree = ""; }; + EAC9257E29119B5D00091998 /* TextLink */ = { + isa = PBXGroup; + children = ( + EAC9257C29119B5400091998 /* TextLink.swift */, + ); + path = TextLink; + sourceTree = ""; + }; + EAC925812911B34300091998 /* TextLinkCaret */ = { + isa = PBXGroup; + children = ( + EAC925822911B35300091998 /* TextLinkCaret.swift */, + ); + path = TextLinkCaret; + sourceTree = ""; + }; EAF7F092289985E200B287F5 /* Checkbox */ = { isa = PBXGroup; children = ( @@ -553,6 +575,7 @@ EA89201328B568D8006B9984 /* RadioBox.swift in Sources */, EA3362402892EF6C0071C351 /* Label.swift in Sources */, EAF7F0B3289B1ADC00B287F5 /* LabelAttributeAction.swift in Sources */, + EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */, EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */, EA4DB2FD28D3D0CA00103EE3 /* AnyEquatable.swift in Sources */, EAF7F0AF289B144C00B287F5 /* LabelAttributeUnderline.swift in Sources */, @@ -597,6 +620,7 @@ EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */, EA3361BF288B2EA60071C351 /* Handlerable.swift in Sources */, EA3361A8288B23300071C351 /* UIColor.swift in Sources */, + EAC9257D29119B5400091998 /* TextLink.swift in Sources */, EA1F266628B945070033E859 /* RadioSwatchGroup.swift in Sources */, 5FC35BE328D51405004EBEAC /* Button.swift in Sources */, ); diff --git a/VDS/Components/TextLink/TextLink.swift b/VDS/Components/TextLink/TextLink.swift new file mode 100644 index 00000000..13da46d2 --- /dev/null +++ b/VDS/Components/TextLink/TextLink.swift @@ -0,0 +1,103 @@ +// +// TextLink.swift +// VDS +// +// Created by Matt Bruce on 11/1/22. +// + +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +@objc(VDSTextLink) +open class TextLink: Control { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var heightConstraint: NSLayoutConstraint? + private var label = Label() + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + open var text: String? { didSet { didChange() } } + + open var size: ButtonSize = .large { didSet { didChange() }} + + private var height: CGFloat { + switch size { + case .large: + return 44 + case .small: + return 32 + } + } + + //-------------------------------------------------- + // 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() + + 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.topAnchor.constraint(equalTo: topAnchor).isActive = true + label.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + + heightConstraint = heightAnchor.constraint(equalToConstant: height) + heightConstraint?.isActive = true + } + + open override func reset() { + super.reset() + size = .large + accessibilityCustomActions = [] + accessibilityTraits = .staticText + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + open override func updateView() { + label.surface = surface + label.disabled = disabled + label.typograpicalStyle = size == .large ? TypographicalStyle.BodyLarge : TypographicalStyle.BodySmall + label.text = text ?? "" + label.attributes = [LabelAttributeUnderline(location: 0, length: label.text!.count)] + heightConstraint?.constant = height + } + +} diff --git a/VDS/Components/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/TextLinkCaret/TextLinkCaret.swift new file mode 100644 index 00000000..9ea3f4e5 --- /dev/null +++ b/VDS/Components/TextLinkCaret/TextLinkCaret.swift @@ -0,0 +1,98 @@ +// +// TextLinkCaret.swift +// VDS +// +// Created by Matt Bruce on 11/1/22. +// + +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine + +public enum TextLinkIconPosition: String, CaseIterable { + case left, right +} + +@objc(VDSTextLinkCaret) +open class TextLinkCaret: Control { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var heightConstraint: NSLayoutConstraint? + private var label = Label() + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + open var text: String? { didSet { didChange() } } + + open var iconPosition: TextLinkIconPosition = .right { didSet { didChange() } } + + private var height: CGFloat { + 44 + } + + //-------------------------------------------------- + // 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() + + 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.topAnchor.constraint(equalTo: topAnchor).isActive = true + label.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + + heightAnchor.constraint(equalToConstant: height).isActive = true + } + + open override func reset() { + super.reset() + accessibilityCustomActions = [] + accessibilityTraits = .staticText + } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + open override func updateView() { + label.surface = surface + label.disabled = disabled + label.typograpicalStyle = TypographicalStyle.BoldBodyLarge + label.text = text ?? "" + } + +} From 10dc7e880aaf41f5a4a37def43ee7d3fcf47bf72 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 2 Nov 2022 15:14:14 -0500 Subject: [PATCH 3/6] updated lextLinkCaret Signed-off-by: Matt Bruce --- .../Buttons/TextLinkCaret/TextLinkCaret.swift | 236 ++++++++++++++++-- 1 file changed, 222 insertions(+), 14 deletions(-) diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index 9ea3f4e5..c3103cc7 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -11,7 +11,7 @@ import VDSColorTokens import VDSFormControlsTokens import Combine -public enum TextLinkIconPosition: String, CaseIterable { +public enum TextLinkCaretPosition: String, CaseIterable { case left, right } @@ -22,14 +22,26 @@ open class TextLinkCaret: Control { // MARK: - Private Properties //-------------------------------------------------- private var heightConstraint: NSLayoutConstraint? - private var label = Label() + + private let containerView = UIView().with{ + $0.translatesAutoresizingMaskIntoConstraints = false + } + + private var label = Label().with { + $0.typograpicalStyle = TypographicalStyle.BoldBodyLarge + } + + private var caretView = CaretView().with { + $0.size = CaretView.CaretSize.small(.vertical) + $0.lineWidth = 2 + } //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- open var text: String? { didSet { didChange() } } - open var iconPosition: TextLinkIconPosition = .right { didSet { didChange() } } + open var iconPosition: TextLinkCaretPosition = .right { didSet { didChange() } } private var height: CGFloat { 44 @@ -62,21 +74,55 @@ open class TextLinkCaret: Control { 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.topAnchor.constraint(equalTo: topAnchor).isActive = true - label.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + + addSubview(containerView) + containerView.addSubview(label) + containerView.addSubview(caretView) + + //constraints heightAnchor.constraint(equalToConstant: height).isActive = true + + containerView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + containerView.heightAnchor.constraint(lessThanOrEqualToConstant: height).isActive = true + + label.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true + label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true + label.widthAnchor.constraint(lessThanOrEqualTo: containerView.widthAnchor, multiplier: 0.90).isActive = true + caretView.bottomAnchor.constraint(lessThanOrEqualTo: label.bottomAnchor, constant: -3).isActive = true + caretView.setConstraints() + } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + private var caretLeadingConstraint: NSLayoutConstraint? + private var caretTrailingConstraint: NSLayoutConstraint? + private var labelConstraint: NSLayoutConstraint? + + private func setConstraints(){ + caretLeadingConstraint?.isActive = false + caretTrailingConstraint?.isActive = false + labelConstraint?.isActive = false + if iconPosition == .right { + labelConstraint = label.leadingAnchor.constraint(equalTo: containerView.leadingAnchor) + caretLeadingConstraint = caretView.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 4) + caretTrailingConstraint = caretView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor) + } else { + labelConstraint = label.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor) + caretLeadingConstraint = caretView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor) + caretTrailingConstraint = caretView.trailingAnchor.constraint(equalTo: label.leadingAnchor, constant: -4) + } + caretTrailingConstraint?.isActive = true + caretLeadingConstraint?.isActive = true + labelConstraint?.isActive = true + self.layoutIfNeeded() } open override func reset() { @@ -91,8 +137,170 @@ open class TextLinkCaret: Control { open override func updateView() { label.surface = surface label.disabled = disabled - label.typograpicalStyle = TypographicalStyle.BoldBodyLarge - label.text = text ?? "" + label.text = text + + caretView.direction = iconPosition == .right ? CaretView.Direction.right : CaretView.Direction.left + setConstraints() } } + +internal class CaretView: View { + //------------------------------------------------------ + // MARK: - Properties + //------------------------------------------------------ + private var caretPath: UIBezierPath = UIBezierPath() + + public var lineWidth: CGFloat = 1 { didSet{ didChange() } } + + public var direction: Direction = .right { didSet{ didChange() } } + + public var size: CaretSize? { didSet{ didChange() } } + + public var colorConfiguration: AnyColorable = DisabledSurfaceColorConfiguration().with { + $0.disabled.lightColor = VDSColor.elementsSecondaryOnlight + $0.disabled.darkColor = VDSColor.elementsSecondaryOndark + $0.enabled.lightColor = VDSColor.elementsPrimaryOnlight + $0.enabled.darkColor = VDSColor.elementsPrimaryOndark + }.eraseToAnyColorable() + + + //------------------------------------------------------ + // MARK: - Constraints + //------------------------------------------------------ + + /// Sizes of CaretView are derived from InVision design specs. They are provided for convenience. + public enum CaretSize { + case small(Orientation) + case medium(Orientation) + case large(Orientation) + + /// Orientation based on the longest line of the view. + public enum Orientation { + case vertical + case horizontal + } + + /// Dimensions of container; provided by InVision design. + func dimensions() -> CGSize { + + switch self { + case .small(let o): + return o == .vertical ? CGSize(width: 6.9, height: 10.96) : CGSize(width: 10.96, height: 6.9) + + case .medium(let o): + return o == .vertical ? CGSize(width: 9.9, height: 16.96) : CGSize(width: 16.96, height: 9.9) + + case .large(let o): + return o == .vertical ? CGSize(width: 14.9, height: 24.96) : CGSize(width: 24.96, height: 14.9) + } + } + } + + //------------------------------------------------------ + // MARK: - Initialization + //------------------------------------------------------ + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + public convenience init(lineWidth: CGFloat) { + self.init(frame: .zero) + self.lineWidth = lineWidth + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("CaretView xib not supported.") + } + + required public convenience init() { + self.init(frame: .zero) + } + + //------------------------------------------------------ + // MARK: - Setup + //------------------------------------------------------ + + override open func setup() { + super.setup() + defaultState() + } + + //------------------------------------------------------ + // MARK: - Drawing + //------------------------------------------------------ + + /// The direction the caret will be pointing to. + public enum Direction: Int { + case left + case right + case down + case up + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + + caretPath.removeAllPoints() + caretPath.lineJoinStyle = .miter + caretPath.lineWidth = lineWidth + + let inset = lineWidth / 2 + let halfWidth = frame.size.width / 2 + let halfHeight = frame.size.height / 2 + + switch direction { + case .up: + caretPath.move(to: CGPoint(x: inset, y: frame.size.height - inset)) + caretPath.addLine(to: CGPoint(x: halfWidth, y: inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height)) + + case .right: + caretPath.move(to: CGPoint(x: inset, y: inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: halfHeight)) + caretPath.addLine(to: CGPoint(x: inset, y: frame.size.height - inset)) + + case .down: + caretPath.move(to: CGPoint(x: inset, y: inset)) + caretPath.addLine(to: CGPoint(x: halfWidth, y: frame.size.height - inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: inset)) + + case .left: + caretPath.move(to: CGPoint(x: frame.size.width - inset, y: inset)) + caretPath.addLine(to: CGPoint(x: inset, y: halfHeight)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: frame.size.height - inset)) + } + + let color = colorConfiguration.getColor(self) + color.setStroke() + caretPath.stroke() + } + + override func updateView() { + setNeedsDisplay() + } + + //------------------------------------------------------ + // MARK: - Methods + //------------------------------------------------------ + public func setLineColor(_ color: UIColor) { + setNeedsDisplay() + } + + public func defaultState() { + isOpaque = false + isHidden = false + backgroundColor = .clear + } + + /// Ensure you have defined a CaretSize with Orientation before calling. + public func setConstraints() { + + guard let dimensions = size?.dimensions() else { return } + + heightAnchor.constraint(equalToConstant: dimensions.height).isActive = true + widthAnchor.constraint(equalToConstant: dimensions.width).isActive = true + } +} From 97130fc5e4a3fc5518729ab5ec5438431ff946f6 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 2 Nov 2022 15:17:16 -0500 Subject: [PATCH 4/6] updated view Signed-off-by: Matt Bruce --- VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index c3103cc7..00e94c9e 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -139,6 +139,8 @@ open class TextLinkCaret: Control { label.disabled = disabled label.text = text + caretView.surface = surface + caretView.disabled = disabled caretView.direction = iconPosition == .right ? CaretView.Direction.right : CaretView.Direction.left setConstraints() } From 2b1bff40786d6cabd6ceeb36be33cb01e39618fb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 2 Nov 2022 16:01:11 -0500 Subject: [PATCH 5/6] updated to use image text attribute Signed-off-by: Matt Bruce --- .../Buttons/TextLinkCaret/TextLinkCaret.swift | 83 +++++++++---------- .../Attributes/ImageLabelAttribute.swift | 38 ++++++--- 2 files changed, 68 insertions(+), 53 deletions(-) diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index 00e94c9e..2a0c2949 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -23,10 +23,6 @@ open class TextLinkCaret: Control { //-------------------------------------------------- private var heightConstraint: NSLayoutConstraint? - private let containerView = UIView().with{ - $0.translatesAutoresizingMaskIntoConstraints = false - } - private var label = Label().with { $0.typograpicalStyle = TypographicalStyle.BoldBodyLarge } @@ -78,25 +74,18 @@ open class TextLinkCaret: Control { publisher(for: UITapGestureRecognizer()).sink { [weak self] _ in self?.sendActions(for: .touchUpInside) }.store(in: &subscribers) - - - addSubview(containerView) - containerView.addSubview(label) - containerView.addSubview(caretView) //constraints heightAnchor.constraint(equalToConstant: height).isActive = true - containerView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - containerView.heightAnchor.constraint(lessThanOrEqualToConstant: height).isActive = true - - label.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true - label.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true - label.widthAnchor.constraint(lessThanOrEqualTo: containerView.widthAnchor, multiplier: 0.90).isActive = true - caretView.bottomAnchor.constraint(lessThanOrEqualTo: label.bottomAnchor, constant: -3).isActive = true - caretView.setConstraints() + let size = caretView.size!.dimensions() + caretView.frame = .init(x: 0, y: 0, width: size.width, height: size.height) + addSubview(label) + label.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + label.topAnchor.constraint(equalTo: topAnchor).isActive = true + label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } //-------------------------------------------------- @@ -105,25 +94,6 @@ open class TextLinkCaret: Control { private var caretLeadingConstraint: NSLayoutConstraint? private var caretTrailingConstraint: NSLayoutConstraint? private var labelConstraint: NSLayoutConstraint? - - private func setConstraints(){ - caretLeadingConstraint?.isActive = false - caretTrailingConstraint?.isActive = false - labelConstraint?.isActive = false - if iconPosition == .right { - labelConstraint = label.leadingAnchor.constraint(equalTo: containerView.leadingAnchor) - caretLeadingConstraint = caretView.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 4) - caretTrailingConstraint = caretView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor) - } else { - labelConstraint = label.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor) - caretLeadingConstraint = caretView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor) - caretTrailingConstraint = caretView.trailingAnchor.constraint(equalTo: label.leadingAnchor, constant: -4) - } - caretTrailingConstraint?.isActive = true - caretLeadingConstraint?.isActive = true - labelConstraint?.isActive = true - self.layoutIfNeeded() - } open override func reset() { super.reset() @@ -135,16 +105,37 @@ open class TextLinkCaret: Control { // MARK: - Overrides //-------------------------------------------------- open override func updateView() { - label.surface = surface - label.disabled = disabled - label.text = text + var updatedText = text ?? "" + caretView.surface = surface caretView.disabled = disabled caretView.direction = iconPosition == .right ? CaretView.Direction.right : CaretView.Direction.left - setConstraints() - } + + let image = caretView.getImage() + let location = iconPosition == .right ? updatedText.count + 1 : 0 + let textColor = label.textColorConfiguration.getColor(self) + let imageAttribute = ImageLabelAttribute(location: location, + length: 1, + image: image, + frame: .init(x: 0, y: 0, width: image.size.width, height: image.size.height), + tintColor: textColor) + label.surface = surface + label.disabled = disabled + label.text = iconPosition == .right ? "\(updatedText) " : " \(updatedText)" + label.attributes = [imageAttribute] + } + +} +extension UIView { + public func getImage() -> UIImage { + let renderer = UIGraphicsImageRenderer(size: self.bounds.size) + let image = renderer.image { ctx in + self.drawHierarchy(in: self.bounds, afterScreenUpdates: true) + } + return image + } } internal class CaretView: View { @@ -221,6 +212,12 @@ internal class CaretView: View { self.init(frame: .zero) } + public convenience init(size: CaretSize){ + let dimensions = size.dimensions() + self.init(frame: .init(x: 0, y: 0, width: dimensions.width, height: dimensions.height)) + self.size = size + } + //------------------------------------------------------ // MARK: - Setup //------------------------------------------------------ diff --git a/VDS/Components/Label/Attributes/ImageLabelAttribute.swift b/VDS/Components/Label/Attributes/ImageLabelAttribute.swift index 9bba6287..ca62f602 100644 --- a/VDS/Components/Label/Attributes/ImageLabelAttribute.swift +++ b/VDS/Components/Label/Attributes/ImageLabelAttribute.swift @@ -12,11 +12,13 @@ public struct ImageLabelAttribute: AttachmentLabelAttributeModel { public enum Error: Swift.Error { case bundleNotFound case imageNotFound(String) + case imageNotSet } public var id = UUID() public var location: Int public var length: Int - public var imageName: String + public var imageName: String? + public var image: UIImage? public var frame: CGRect public var tintColor: UIColor public static func == (lhs: ImageLabelAttribute, rhs: ImageLabelAttribute) -> Bool { @@ -27,18 +29,34 @@ public struct ImageLabelAttribute: AttachmentLabelAttributeModel { return id == equatable.id && range == equatable.range && imageName == equatable.imageName } - public func getAttachment() throws -> NSTextAttachment { - guard let bundle = Bundle(identifier: "com.vzw.vds") else { - throw Error.bundleNotFound - } - - guard let image = UIImage(named: imageName, in: bundle, with: nil) else { - throw Error.imageNotFound(imageName) - } - + private func imageAttachment(image: UIImage) -> NSTextAttachment { let attachment = NSTextAttachment() attachment.image = image.withTintColor(tintColor) attachment.bounds = frame return attachment } + + public func getAttachment() throws -> NSTextAttachment { + + //get a local asset + if let imageName { + guard let bundle = Bundle(identifier: "com.vzw.vds") else { + throw Error.bundleNotFound + } + + guard let image = UIImage(named: imageName, in: bundle, with: nil) else { + throw Error.imageNotFound(imageName) + } + + return imageAttachment(image: image) + + } //get from set image + else if let image { + return imageAttachment(image: image) + + } else { + throw Error.imageNotSet + } + + } } From 6d34211042356f50c268b381d754f1cb8520e1f2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 2 Nov 2022 16:50:25 -0500 Subject: [PATCH 6/6] updated height constraint Signed-off-by: Matt Bruce --- VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index 2a0c2949..daed1a42 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -76,7 +76,7 @@ open class TextLinkCaret: Control { }.store(in: &subscribers) //constraints - heightAnchor.constraint(equalToConstant: height).isActive = true + heightAnchor.constraint(greaterThanOrEqualToConstant: height).isActive = true let size = caretView.size!.dimensions() caretView.frame = .init(x: 0, y: 0, width: size.width, height: size.height) @@ -85,7 +85,6 @@ open class TextLinkCaret: Control { label.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true label.topAnchor.constraint(equalTo: topAnchor).isActive = true label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - } //-------------------------------------------------- @@ -124,7 +123,7 @@ open class TextLinkCaret: Control { label.disabled = disabled label.text = iconPosition == .right ? "\(updatedText) " : " \(updatedText)" label.attributes = [imageAttribute] - } + } }