// // Tooltip.swift // VDS // // Created by Matt Bruce on 4/13/23. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine /// A tooltip is an overlay that clarifies another component or content /// element. It is triggered when a customer hovers, clicks or taps /// the tooltip icon. @objc(VDSTooltip) open class Tooltip: Control, 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: - Enums //-------------------------------------------------- /// Enum used to describe the color of the tooltip. public enum FillColor: String, CaseIterable { case primary, secondary, brandHighlight } /// Enum used to describe the size of the icon. public enum Size: String, EnumSubset { case small case medium public var defaultValue: Icon.Size { .small } } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var widthConstraint: NSLayoutConstraint? private var heightConstraint: NSLayoutConstraint? private var infoImage = UIImage() //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- /// Icon used to render the tooltip image. open var icon = Icon().with { $0.name = .info $0.size = .small $0.isUserInteractionEnabled = false } /// Will render the text for Close button for tooltip dialog when on mobile devices open var closeButtonText: String = "Close" { didSet { setNeedsUpdate() } } /// Will render icon in brand colors. open var fillColor: FillColor = .primary { didSet { setNeedsUpdate() } } /// Size of the icon open var size: Size = .medium { didSet { setNeedsUpdate() } } /// Text rendered for the title of the tooltip open var title: String? { didSet { setNeedsUpdate() } } /// Text rendered for the content of the tooltip open var content: String? { didSet { setNeedsUpdate() } } /// UIView rendered for the content area of the tooltip open var contentView: UIView? { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- private var iconColorConfiguration: AnyColorable { switch fillColor { case .primary: return primaryColorConfiguration.eraseToAnyColorable() case .secondary: return secondaryColorConfiguration.eraseToAnyColorable() case .brandHighlight: return brandHighlightColorConfiguration.eraseToAnyColorable() } } private var primaryColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) } private var secondaryColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forState: .normal) $0.setSurfaceColors(VDSColor.paletteGray65, VDSColor.paletteGray65, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) } private var brandHighlightColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsBrandhighlight, VDSColor.elementsBrandhighlight, forState: .normal) $0.setSurfaceColors(VDSColor.elementsBrandhighlight, VDSColor.elementsBrandhighlight, forState: .highlighted) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) } //-------------------------------------------------- // 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(icon) icon.pinToSuperView() backgroundColor = .clear isAccessibilityElement = true accessibilityTraits = .button onClickSubscriber = publisher(for: .touchUpInside) .sink(receiveValue: { [weak self] tooltip in guard let self else { return} self.presentTooltip(surface: tooltip.surface, title: tooltip.title, content: tooltip.content, contentView: tooltip.contentView, closeButtonText: tooltip.closeButtonText, presenter: self) }) } /// Resets to default settings. open override func reset() { super.reset() shouldUpdateView = false size = .medium title = "" content = "" fillColor = .primary closeButtonText = "Close" shouldUpdateView = true setNeedsUpdate() } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() //get the size icon.size = size.value //get the color for the image icon.color = iconColorConfiguration.getColor(self) } /// Used to update any Accessibility properties. open override func updateAccessibility() { super.updateAccessibility() var label = title if label == nil { label = content } if let label, !label.isEmpty { accessibilityLabel = label } else { accessibilityLabel = "Tooltip" } accessibilityHint = isEnabled ? "Click to open Tooltip." : "" accessibilityValue = "collapsed" } public static func accessibleText(for title: String?, content: String?, closeButtonText: String) -> String { var label = "" if let title { label = title } if let content { if !label.isEmpty { label += "," } label += content } return label } } // MARK: AppleGuidelinesTouchable extension Tooltip: AppleGuidelinesTouchable { /// Overrides to ensure that the touch point meets a minimum of the minimumTappableArea. override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { Self.acceptablyOutsideBounds(point: point, bounds: bounds) } }