153 lines
5.8 KiB
Swift
153 lines
5.8 KiB
Swift
//
|
|
// TextLinkCaret.swift
|
|
// VDS
|
|
//
|
|
// Created by Matt Bruce on 11/1/22.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import VDSColorTokens
|
|
import VDSFormControlsTokens
|
|
import Combine
|
|
|
|
/// A text link caret is an interactive element that always brings a customer to another page. It's used for navigation,
|
|
/// like "Back", or a call-to-action for a product or feature, like "Shop smartphones". This class can be used within a ``ButtonGroup``.
|
|
///
|
|
/// If you are using AutoLayoutConstraints you have a combination of Leading/Left and Trailing/Right NSLayoutConstraints,
|
|
/// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges
|
|
/// to its parent this object will stretch to the parent's width.
|
|
@objc(VDSTextLinkCaret)
|
|
open class TextLinkCaret: ButtonBase {
|
|
//--------------------------------------------------
|
|
// MARK: - Initializers
|
|
//--------------------------------------------------
|
|
required public init() {
|
|
super.init(frame: .zero)
|
|
}
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: .zero)
|
|
}
|
|
|
|
public required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Enums
|
|
//--------------------------------------------------
|
|
/// Enum used to describe the position of the icon in relation to the title label.
|
|
public enum IconPosition: String, CaseIterable {
|
|
case left, right
|
|
var image: UIImage {
|
|
UIImage.image(for: self == .left ? .leftCaretBold : .rightCaretBold)!
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Private Properties
|
|
//--------------------------------------------------
|
|
private var textColorConfiguration = ControlColorConfiguration().with {
|
|
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
|
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled)
|
|
$0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted)
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Public Properties
|
|
//--------------------------------------------------
|
|
/// Determines icon position of Caret.
|
|
open var iconPosition: IconPosition = .right { didSet { setNeedsUpdate() } }
|
|
|
|
/// UIColor used on the titleLabel text.
|
|
open override var textColor: UIColor {
|
|
textColorConfiguration.getColor(self)
|
|
}
|
|
|
|
open override var textStyle: TextStyle {
|
|
TextStyle.boldBodyLarge
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Overrides
|
|
//--------------------------------------------------
|
|
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
|
|
open override func setup() {
|
|
super.setup()
|
|
|
|
//left align titleLabel in case this is pinned leading/trailing
|
|
//default is always set to center
|
|
contentHorizontalAlignment = .left
|
|
|
|
accessibilityTraits = .link
|
|
titleLabel?.numberOfLines = 0
|
|
titleLabel?.lineBreakMode = .byWordWrapping
|
|
}
|
|
|
|
/// Used to make changes to the View based off a change events or from local properties.
|
|
open override func updateView() {
|
|
setImage(iconPosition.image.withTintColor(textColor), for: .normal)
|
|
super.updateView()
|
|
}
|
|
|
|
/// Resets to default settings.
|
|
open override func reset() {
|
|
super.reset()
|
|
iconPosition = .right
|
|
text = nil
|
|
}
|
|
|
|
/// The natural size for the receiving view, considering only properties of the view itself.
|
|
// Property to specify the icon size
|
|
private var imageSize: CGSize = Icon.Size.xsmall.dimensions
|
|
|
|
open override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
guard let titleLabel = titleLabel, let imageView = imageView else { return }
|
|
|
|
// Adjust imageView size based on the imageSize property
|
|
imageView.frame.size = imageSize
|
|
|
|
let space: CGFloat = 5 // Space between the icon and the text
|
|
|
|
// Adjust icon and text positions based on the iconPosition
|
|
switch iconPosition {
|
|
case .left:
|
|
imageView.frame.origin.x = bounds.minX + contentEdgeInsets.left
|
|
imageView.frame.origin.y = titleLabel.frame.minY + (textStyle.lineHeight - imageSize.height) / 2.0
|
|
titleLabel.frame.origin.x = imageView.frame.maxX + space
|
|
|
|
case .right:
|
|
|
|
guard let attribtedText = titleLabel.attributedText else { return }
|
|
|
|
let textContainer = NSTextContainer(size: CGSize(width: titleLabel.bounds.width, height: CGFloat.greatestFiniteMagnitude))
|
|
textContainer.lineFragmentPadding = 0
|
|
|
|
let layoutManager = NSLayoutManager()
|
|
layoutManager.addTextContainer(textContainer)
|
|
|
|
let textStorage = NSTextStorage(attributedString: attribtedText)
|
|
textStorage.addLayoutManager(layoutManager)
|
|
|
|
let lastGlyphIndex = layoutManager.glyphIndexForCharacter(at: attribtedText.string.utf16.count - 1)
|
|
var lastGlyphRect = layoutManager.boundingRect(forGlyphRange: NSRange(location: lastGlyphIndex, length: 1), in: textContainer)
|
|
|
|
lastGlyphRect.origin.x += titleLabel.frame.origin.x
|
|
lastGlyphRect.origin.y += titleLabel.frame.origin.y
|
|
|
|
imageView.frame.origin.x = lastGlyphRect.maxX + space
|
|
imageView.frame.origin.y = lastGlyphRect.midY - imageSize.height / 2
|
|
|
|
}
|
|
|
|
imageView.contentMode = .scaleAspectFit
|
|
}
|
|
|
|
private var space: CGFloat {
|
|
return 5 // Space between the icon and text, used in multiple places
|
|
}
|
|
}
|
|
|