// // Tooltip.swift // VDS // // Created by Matt Bruce on 4/13/23. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine @objc(VDSTooltip) open class Tooltip: Control, TooltipLaunchable { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- public enum FillColor: String, CaseIterable { case primary, secondary, brandHighlight } 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 //-------------------------------------------------- open var imageView = UIImageView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.contentMode = .scaleAspectFill $0.clipsToBounds = true } open var closeButtonText: String = "Close" { didSet { setNeedsUpdate() }} open var fillColor: FillColor = .primary { didSet { setNeedsUpdate() }} open var size: Size = .medium { didSet { setNeedsUpdate() }} open var title: String = "" { didSet { setNeedsUpdate() }} open var content: String = "" { 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: - 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: - Lifecycle //-------------------------------------------------- open override func setup() { super.setup() if let image = BundleManager.shared.image(for: "info") { infoImage = image } addSubview(imageView) imageView.pinToSuperView() heightConstraint = imageView.heightAnchor.constraint(equalToConstant: size.value.dimensions.height) heightConstraint?.isActive = true widthConstraint = imageView.widthAnchor.constraint(equalToConstant: size.value.dimensions.width) widthConstraint?.isActive = true backgroundColor = .clear isAccessibilityElement = true accessibilityTraits = .link 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, closeButtonText: tooltip.closeButtonText) }) } open override func reset() { super.reset() shouldUpdateView = false size = .medium title = "" content = "" fillColor = .primary closeButtonText = "Close" imageView.image = nil shouldUpdateView = true setNeedsUpdate() } open override func updateView() { super.updateView() //set the dimensions let dimensions = size.value.dimensions heightConstraint?.constant = dimensions.height widthConstraint?.constant = dimensions.width //get the color for the image let imageColor = iconColorConfiguration.getColor(self) imageView.image = infoImage.withTintColor(imageColor) accessibilityLabel = "Tooltip: \(title)" } } // MARK: AppleGuidlinesTouchable extension Tooltip: AppleGuidlinesTouchable { override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { Self.acceptablyOutsideBounds(point: point, bounds: bounds) } }