vds_ios/VDS/Components/TextFields/InputField/TextField.swift
Matt Bruce bd50e75f85 added TextField
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-03-13 13:40:37 -05:00

254 lines
8.1 KiB
Swift

//
// TextField.swift
// VDS
//
// Created by Matt Bruce on 3/13/24.
//
import Foundation
import UIKit
import Combine
import VDSColorTokens
@objc(VDSTextField)
open class TextField: UITextField, ViewProtocol {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
initialSetup()
}
public override init(frame: CGRect) {
super.init(frame: frame)
initialSetup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
/// Set of Subscribers for any Publishers for this Control.
open var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var initialSetupPerformed = false
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
/// Key of whether or not updateView() is called in setNeedsUpdate()
open var shouldUpdateView: Bool = true
open var surface: Surface = .light { didSet { setNeedsUpdate() } }
/// Array of LabelAttributeModel objects used in rendering the text.
open var textAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() } }
/// TextStyle used on the titleLabel.
open var textStyle: TextStyle { .defaultStyle }
/// Will determine if a scaled font should be used for the titleLabel font.
open var useScaledFont: Bool = false { didSet { setNeedsUpdate() } }
open override var isEnabled: Bool { didSet { setNeedsUpdate() } }
open override var isSelected: Bool { didSet { setNeedsUpdate() } }
/// State of animating isHighlight.
public var isHighlighting = false
/// Whether the Control should handle the isHighlighted state.
open var shouldHighlight: Bool { isHighlighting == false }
/// Whether the Control is highlighted or not.
open override var isHighlighted: Bool {
didSet {
if shouldHighlight {
isHighlighting = true
setNeedsUpdate()
isHighlighting = false
}
}
}
open var textColorConfiguration: AnyColorable = ViewColorConfiguration().with {
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true)
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false)
}.eraseToAnyColorable(){ didSet { setNeedsUpdate() }}
open override var textColor: UIColor? {
get { textColorConfiguration.getColor(self) }
set { }
}
private enum TextSetMode {
case text
case attributedText
}
private var textSetMode: TextSetMode = .text
/// :nodoc:
open override var text: String! {
get { super.text }
set {
// When text is set, we may need to re-style it as attributedText
// with the correct paragraph style to achieve the desired line height.
textSetMode = .text
styleText(newValue)
}
}
/// :nodoc:
open override var attributedText: NSAttributedString? {
get { super.attributedText }
set {
// When text is set, we may need to re-style it as attributedText
// with the correct paragraph style to achieve the desired line height.
textSetMode = .attributedText
styleAttributedText(newValue)
}
}
/// :nodoc:
open override var textAlignment: NSTextAlignment {
didSet {
if textAlignment != oldValue {
// Text alignment can be part of our paragraph style, so we may need to
// re-style when changed
restyleText()
}
}
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
accessibilityCustomActions = []
setup()
setNeedsUpdate()
}
}
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
}
open func updateView() {
restyleText()
}
open func updateAccessibility() {}
open func reset() {
shouldUpdateView = false
surface = .light
text = nil
accessibilityCustomActions = []
shouldUpdateView = true
setNeedsUpdate()
}
//--------------------------------------------------
// MARK: - Overrides Methods
//--------------------------------------------------
/// :nodoc
open override func textRect(forBounds bounds: CGRect) -> CGRect {
super.textRect(forBounds: bounds).inset(by: textStyle.edgeInsets)
}
/// :nodoc
open override func editingRect(forBounds bounds: CGRect) -> CGRect {
super.editingRect(forBounds: bounds).inset(by: textStyle.edgeInsets)
}
/// :nodoc
open override func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
super.clearButtonRect(forBounds: bounds).offsetBy(dx: -textStyle.edgeInsets.right, dy: 0)
}
/// :nodoc
open override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
super.leftViewRect(forBounds: bounds).offsetBy(dx: textStyle.edgeInsets.left, dy: 0)
}
/// :nodoc
open override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
super.rightViewRect(forBounds: bounds).offsetBy(dx: -textStyle.edgeInsets.right, dy: 0)
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
private func restyleText() {
if textSetMode == .text {
styleText(text)
} else {
styleAttributedText(attributedText)
}
}
private func styleText(_ newValue: String!) {
defer { invalidateIntrinsicContentSize() }
guard let newValue else {
// We don't need to use attributed text
super.attributedText = nil
super.text = newValue
return
}
accessibilityCustomActions = []
//create the primary string
let mutableText = NSMutableAttributedString.mutableText(for: newValue,
textStyle: textStyle,
useScaledFont: useScaledFont,
textColor: textColorConfiguration.getColor(self),
alignment: textAlignment,
lineBreakMode: .byWordWrapping)
applyAttributes(mutableText)
// Set attributed text to match typography
super.attributedText = mutableText
}
private func styleAttributedText(_ newValue: NSAttributedString?) {
defer { invalidateIntrinsicContentSize() }
guard let newValue = newValue else {
// We don't need any additional styling
super.attributedText = newValue
return
}
let mutableText = NSMutableAttributedString(attributedString: newValue)
applyAttributes(mutableText)
// Modify attributed text to match typography
super.attributedText = mutableText
}
private func applyAttributes(_ mutableAttributedString: NSMutableAttributedString) {
if let textAttributes {
mutableAttributedString.apply(attributes: textAttributes)
}
}
}