// // TextView.swift // VDS // // Created by Matt Bruce on 2/29/24. // import Foundation import UIKit import Combine import VDSTokens @objc(VDSTextView) open class TextView: UITextView, ViewProtocol { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero, textContainer: nil) initialSetup() } public override init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) 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() //-------------------------------------------------- // 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 var isEnabled: Bool = true { didSet { setNeedsUpdate() } } 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 { } } override public var text: String! { get { super.text } set { super.text = newValue updateLabel() } } override public 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 updateLabel() } } } //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open func initialSetup() { if !initialSetupPerformed { initialSetupPerformed = true backgroundColor = .clear translatesAutoresizingMaskIntoConstraints = false setup() setNeedsUpdate() } } open func setup() { translatesAutoresizingMaskIntoConstraints = false let doneToolbar: UIToolbar = UIToolbar() doneToolbar.translatesAutoresizingMaskIntoConstraints = false doneToolbar.barStyle = .default let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction)) done.accessibilityHint = "Double tap to finish editing." doneToolbar.items = [flexSpace, done] doneToolbar.sizeToFit() inputAccessoryView = doneToolbar } @objc func doneButtonAction() { // Resigns the first responder status when 'Done' is tapped resignFirstResponder() } open func updateView() { updateLabel() } open func updateAccessibility() {} open func reset() { shouldUpdateView = false surface = .light text = nil shouldUpdateView = true setNeedsUpdate() } //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- private func updateLabel() { //clear the arrays holding actions accessibilityCustomActions = [] if let text, !text.isEmpty { //create the primary string let mutableText = NSMutableAttributedString.mutableText(for: text, textStyle: textStyle, useScaledFont: useScaledFont, textColor: textColor!, alignment: textAlignment, lineBreakMode: .byWordWrapping) //apply any attributes if let attributes = textAttributes { mutableText.apply(attributes: attributes) } attributedText = mutableText } else { attributedText = nil } } }