// // TrailingTooltipLabel.swift // VDS // // Created by Matt Bruce on 4/14/23. // import Foundation import UIKit import Combine /// A trailing tooltip is view that contains a label that has a tooltip overlay /// applied at the last character of the text. @objc(VDSTrailingTooltipLabel) open class TrailingTooltipLabel: View, TooltipLaunchable { //-------------------------------------------------- // 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: - Private Properties //-------------------------------------------------- private let tooltipAction = PassthroughSubject() //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- /// Label used to render the label text. open var label = Label() /// Text used to render the label. open var labelText: String? { didSet { if oldValue != labelText { setNeedsUpdate() } } } /// Attributes used to render the label. open var labelAttributes: [any LabelAttributeModel]? { didSet { if oldValue != labelAttributes { setNeedsUpdate() } } } /// Text style used to render the label. open var labelTextStyle: TextStyle = .defaultStyle { didSet { if oldValue != labelTextStyle { setNeedsUpdate() } } } /// Text position used to render the label. open var labelTextAlignment: TextAlignment = .left { didSet { if oldValue != labelTextAlignment { setNeedsUpdate() } } } /// Color configuration set for the label. public lazy var textColorConfiguration: AnyColorable = { label.textColorConfiguration }() { didSet { setNeedsUpdate() } } /// Will render the text for Close button for tooltip dialog when on mobile devices open var tooltipCloseButtonText: String = "Close" { didSet { if oldValue != tooltipCloseButtonText { setNeedsUpdate() } } } /// Text rendered for the title of the tooltip open var tooltipTitle: String? { didSet { if oldValue != tooltipTitle { setNeedsUpdate() } } } /// Text rendered for the content of the tooltip open var tooltipContent: String? { didSet { if oldValue != tooltipContent { setNeedsUpdate() } } } /// UIView rendered for the content area of the tooltip open var tooltipContentView: UIView? { didSet { if oldValue != tooltipContentView { setNeedsUpdate() } } } //-------------------------------------------------- // 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() addSubview(label) label.pinToSuperView() //create the tooltip click event tooltipAction.sink { [weak self] in guard let self else { return } self.presentTooltip(surface: self.surface, title: self.tooltipTitle, content: self.tooltipContent, closeButtonText: self.tooltipCloseButtonText, presenter: self) }.store(in: &subscribers) } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() label.text = labelText label.textStyle = labelTextStyle label.textAlignment = labelTextAlignment.value label.attributes = labelAttributes label.surface = surface label.isEnabled = isEnabled //add tooltip if let labelText, !labelText.isEmpty { label.addTooltip(model: .init(surface: surface, closeButtonText: tooltipCloseButtonText, title: tooltipTitle, content: tooltipContent, contentView: tooltipContentView)) } } /// Resets to default settings. open override func reset() { super.reset() shouldUpdateView = false labelText = nil labelAttributes = nil labelTextStyle = .defaultStyle labelTextAlignment = .left tooltipCloseButtonText = "Close" tooltipTitle = "" tooltipContent = "" shouldUpdateView = true setNeedsUpdate() } } extension Label { /// Model used to represent the tooltip. public struct TooltipModel { /// Current Surface and this is used to pass down to child objects that implement Surfacable public var surface: Surface public var closeButtonText: String public var title: String? public var content: String? public var contentView: UIView? public init(surface: Surface = .light, closeButtonText: String = "Close", title: String?, content: String?, contentView: UIView?) { self.surface = surface self.closeButtonText = closeButtonText self.title = title self.content = content self.contentView = contentView } } /// Helper to add a tool tip attribute to an existing label. public func addTooltip(model: TooltipModel) { var newAttributes: [any LabelAttributeModel] = [] if let attributes { attributes.forEach { attribute in if type(of: attribute) != TooltipLabelAttribute.self { newAttributes.append(attribute) } } } if let text = text, !text.isEmpty { let tooltip = TooltipLabelAttribute(surface: surface, closeButtonText: model.closeButtonText, title: model.title, content: model.content, contentView: model.contentView, presenter: self) newAttributes.append(tooltip) } if !newAttributes.isEmpty { attributes = newAttributes } } }