added initial files for Tooltip

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2023-04-14 14:44:44 -05:00
parent 06bb9d38da
commit 4c88dce9f6
5 changed files with 464 additions and 0 deletions

View File

@ -78,6 +78,10 @@
EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CC28ABE76000DAE764 /* Withable.swift */; };
EAB1D2CF28ABEF2B00DAE764 /* Typography.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */; };
EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */; };
EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB2375C29E8789100AABE9A /* Tooltip.swift */; };
EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB2376129E9880400AABE9A /* TrailingTooltipLabel.swift */; };
EAB2376629E9952D00AABE9A /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB2376529E9952D00AABE9A /* UIApplication.swift */; };
EAB2376829E9992800AABE9A /* TooltipAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB2376729E9992800AABE9A /* TooltipAlertViewController.swift */; };
EAB5FED429267EB300998C17 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FED329267EB300998C17 /* UIView.swift */; };
EAB5FEED2927E1B200998C17 /* ButtonGroupPositionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEEC2927E1B200998C17 /* ButtonGroupPositionLayout.swift */; };
EAB5FEF12927F4AA00998C17 /* SelfSizingCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */; };
@ -196,6 +200,10 @@
EAB1D2CC28ABE76000DAE764 /* Withable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Withable.swift; sourceTree = "<group>"; };
EAB1D2CE28ABEF2B00DAE764 /* Typography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typography.swift; sourceTree = "<group>"; };
EAB1D2E928AE84AA00DAE764 /* UIControlPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControlPublisher.swift; sourceTree = "<group>"; };
EAB2375C29E8789100AABE9A /* Tooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tooltip.swift; sourceTree = "<group>"; };
EAB2376129E9880400AABE9A /* TrailingTooltipLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrailingTooltipLabel.swift; sourceTree = "<group>"; };
EAB2376529E9952D00AABE9A /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = "<group>"; };
EAB2376729E9992800AABE9A /* TooltipAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipAlertViewController.swift; sourceTree = "<group>"; };
EAB5FED329267EB300998C17 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
EAB5FEEC2927E1B200998C17 /* ButtonGroupPositionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupPositionLayout.swift; sourceTree = "<group>"; };
EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizingCollectionView.swift; sourceTree = "<group>"; };
@ -381,6 +389,7 @@
EA5E3056295105930082B959 /* Tilelet */,
EA5E30512950DD8D0082B959 /* TitleLockup */,
EA3361A0288B1E6F0071C351 /* Toggle */,
EAB2375B29E8786100AABE9A /* Tooltip */,
);
path = Components;
sourceTree = "<group>";
@ -398,6 +407,7 @@
children = (
EAF7F0992899B17200B287F5 /* CATransaction.swift */,
EA33622D2891EA3C0071C351 /* DispatchQueue+Once.swift */,
EAB2376529E9952D00AABE9A /* UIApplication.swift */,
EA3361A7288B23300071C351 /* UIColor.swift */,
EA33623D2892EE950071C351 /* UIDevice.swift */,
EAF7F0B4289C126F00B287F5 /* UILabel.swift */,
@ -580,6 +590,16 @@
path = Publishers;
sourceTree = "<group>";
};
EAB2375B29E8786100AABE9A /* Tooltip */ = {
isa = PBXGroup;
children = (
EAB2375C29E8789100AABE9A /* Tooltip.swift */,
EAB2376129E9880400AABE9A /* TrailingTooltipLabel.swift */,
EAB2376729E9992800AABE9A /* TooltipAlertViewController.swift */,
);
path = Tooltip;
sourceTree = "<group>";
};
EAC9257E29119B5D00091998 /* TextLink */ = {
isa = PBXGroup;
children = (
@ -783,6 +803,8 @@
EA89201328B568D8006B9984 /* RadioBox.swift in Sources */,
EAC9258C2911C9DE00091998 /* InputField.swift in Sources */,
EA3362402892EF6C0071C351 /* Label.swift in Sources */,
EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */,
EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */,
EA985C23296E033A00F2FF2E /* TextArea.swift in Sources */,
EAF7F0B3289B1ADC00B287F5 /* ActionLabelAttribute.swift in Sources */,
EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */,
@ -848,8 +870,10 @@
EA3361B6288B2A410071C351 /* Control.swift in Sources */,
5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */,
EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */,
EAB2376629E9952D00AABE9A /* UIApplication.swift in Sources */,
EA985BF9296C710100F2FF2E /* IconColor.swift in Sources */,
EAB5FED429267EB300998C17 /* UIView.swift in Sources */,
EAB2376829E9992800AABE9A /* TooltipAlertViewController.swift in Sources */,
EA33623E2892EE950071C351 /* UIDevice.swift in Sources */,
EA985C692971B90B00F2FF2E /* IconSize.swift in Sources */,
EA985C672970C21600F2FF2E /* VDSLayout.swift in Sources */,

View File

@ -0,0 +1,162 @@
//
// 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 {
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
public enum FillColor: String, CaseIterable {
case primary, secondary, brandHighlight
}
public enum Size: String, CaseIterable {
case small
case medium
public var dimensions: CGSize {
switch self {
case .small:
return .init(width: 13.33, height: 13.33)
case .medium:
return .init(width: 16.67, height: 16.67)
}
}
}
//--------------------------------------------------
// 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 { didChange() }}
open var fillColor: FillColor = .primary { didSet { didChange() }}
open var size: Size = .medium { didSet { didChange() }}
open var title: String = "" { didSet { didChange() }}
open var content: String = "" { didSet { didChange() }}
//--------------------------------------------------
// MARK: - Configuration
//--------------------------------------------------
private var iconColorConfig: AnyColorable = {
let config = KeyedColorConfiguration<Tooltip, FillColor>(keyPath: \.fillColor)
config.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forKey: .primary)
config.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forKey: .secondary)
config.setSurfaceColors(VDSColor.elementsBrandhighlight, VDSColor.elementsBrandhighlight, forKey: .brandHighlight)
return config.eraseToAnyColorable()
}()
//--------------------------------------------------
// 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.dimensions.height)
heightConstraint?.isActive = true
widthConstraint = imageView.widthAnchor.constraint(equalToConstant: size.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()
})
}
open override func reset() {
super.reset()
size = .medium
title = ""
content = ""
fillColor = .primary
closeButtonText = "Close"
imageView.image = nil
}
open override func updateView() {
super.updateView()
//set the dimensions
let dimensions = size.dimensions
heightConstraint?.constant = dimensions.height
widthConstraint?.constant = dimensions.width
//get the color for the image
let imageColor = iconColorConfig.getColor(self)
imageView.image = infoImage.withTintColor(imageColor)
accessibilityLabel = "Tooltip: \(title)"
}
private func presentTooltip() {
if let presenting = UIApplication.topViewController() {
let tooltipViewController = TooltipAlertViewController(nibName: nil, bundle: nil).with {
$0.surface = surface
$0.titleText = title
$0.contentText = content
$0.closeButtonText = closeButtonText
$0.modalPresentationStyle = .overCurrentContext
$0.modalTransitionStyle = .crossDissolve
}
presenting.present(tooltipViewController, animated: true)
}
}
}

View File

@ -0,0 +1,162 @@
//
// TooltipAlertViewController.swift
// VDS
//
// Created by Matt Bruce on 4/14/23.
//
import Foundation
import UIKit
import Combine
import VDSColorTokens
open class TooltipAlertViewController: UIViewController, Surfaceable {
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var onClickSubscriber: AnyCancellable? {
willSet {
if let onClickSubscriber {
onClickSubscriber.cancel()
}
}
}
private var scrollView = UIScrollView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .clear
$0.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: -5)
}
private let containerView = View().with {
$0.layer.cornerRadius = 8
$0.layer.shadowColor = UIColor.black.cgColor
$0.layer.shadowOpacity = 0.5
$0.layer.shadowOffset = CGSize.zero
$0.layer.shadowRadius = 5
}
private var line = Line().with { instance in
instance.lineViewColorConfig = SurfaceColorConfiguration(VDSColor.elementsLowContrastOnLight, VDSColor.elementsLowContrastOnLight).eraseToAnyColorable()
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var surface: Surface = .light { didSet { updateView() }}
open var titleText: String = "" { didSet { updateView() }}
open var titleLabel = Label().with { label in
label.textStyle = .boldTitleMedium
}
open var contentText: String = "" { didSet { updateView() }}
open var contentLabel = Label().with { label in
label.textStyle = .bodyMedium
}
open var closeButtonText: String = "Close" { didSet { updateView() }}
open lazy var closeButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = .clear
button.setTitle("Close", for: .normal)
button.titleLabel?.font = TextStyle.bodyLarge.font
button.translatesAutoresizingMaskIntoConstraints = false
onClickSubscriber = button.publisher(for: .touchUpInside).sink {[weak self] button in
guard let self else { return }
self.dismiss(animated: true, completion: nil)
}
return button
}()
//--------------------------------------------------
// MARK: - Configuration
//--------------------------------------------------
private let containerViewBackgroundColorConfiguration = SurfaceColorConfiguration().with { instance in
instance.lightColor = .white
instance.darkColor = .black
}
private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight)
private let closeButtonTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func viewDidLoad() {
super.viewDidLoad()
isModalInPresentation = true
setup()
}
open func setup() {
scrollView.addSubview(titleLabel)
scrollView.addSubview(contentLabel)
containerView.addSubview(scrollView)
containerView.addSubview(line)
containerView.addSubview(closeButton)
view.addSubview(containerView)
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: VDSLayout.Spacing.space8X.value),
containerView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -VDSLayout.Spacing.space8X.value),
containerView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor),
containerView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor),
containerView.heightAnchor.constraint(equalToConstant: 312),
containerView.widthAnchor.constraint(equalToConstant: 296),
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
scrollView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: VDSLayout.Spacing.space4X.value),
scrollView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: VDSLayout.Spacing.space4X.value),
scrollView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -VDSLayout.Spacing.space4X.value),
scrollView.bottomAnchor.constraint(equalTo: line.topAnchor, constant: -VDSLayout.Spacing.space4X.value),
titleLabel.topAnchor.constraint(equalTo: scrollView.topAnchor),
titleLabel.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
titleLabel.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
contentLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: VDSLayout.Spacing.space1X.value),
contentLabel.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
contentLabel.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
contentLabel.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentLabel.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
line.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
line.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
closeButton.topAnchor.constraint(equalTo: line.bottomAnchor),
closeButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
closeButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
closeButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
closeButton.heightAnchor.constraint(equalToConstant: 44.0)
])
}
open func updateView() {
view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.3)
containerView.backgroundColor = containerViewBackgroundColorConfiguration.getColor(self)
scrollView.indicatorStyle = surface == .light ? .black : .white
titleLabel.surface = surface
contentLabel.surface = surface
line.surface = surface
titleLabel.text = titleText
contentLabel.text = contentText
titleLabel.sizeToFit()
contentLabel.sizeToFit()
let closeButtonTextColor = closeButtonTextColorConfiguration.getColor(self)
closeButton.setTitleColor(closeButtonTextColor, for: .normal)
closeButton.setTitleColor(closeButtonTextColor, for: .highlighted)
closeButton.setTitle(closeButtonText, for: .normal)
}
}

View File

@ -0,0 +1,84 @@
//
// TrailingTooltipLabel.swift
// VDS
//
// Created by Matt Bruce on 4/14/23.
//
import Foundation
import UIKit
import Combine
@objc(VDSTrailingTooltipLabel)
open class TrailingTooltipLabel: View {
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private let toolTipAction = PassthroughSubject<Void, Never>()
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var label = Label()
open var labelText: String? { didSet { didChange() }}
open var labelTextStyle: TextStyle = .defaultStyle { didSet { didChange() }}
public lazy var textColorConfiguration: AnyColorable = {
label.textColorConfiguration
}() { didSet { didChange() }}
open var closeButtonText: String? = "Close" { didSet { didChange() }}
open var tooltipSize: Tooltip.Size = .medium { didSet { didChange() }}
open var tooltipTitle: String = "" { didSet { didChange() }}
open var tooltipContent: String = "" { didSet { didChange() }}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func setup() {
super.setup()
addSubview(label)
label.pinToSuperView()
//create the tooltip click event
toolTipAction.sink { [weak self] in
self?.showToolTip()
}.store(in: &subscribers)
}
open override func updateView() {
super.updateView()
var attributes: [any LabelAttributeModel] = []
var updatedLabelText = labelText
//add the tool tip
if let oldText = updatedLabelText {
let toolTipUpdateText = "\(oldText) " //create a little space between the final character and tooltip image
let toolTipAttribute = ToolTipLabelAttribute(action: toolTipAction,
location: toolTipUpdateText.count - 1,
length: 1,
tintColor: textColorConfiguration.getColor(self))
updatedLabelText = toolTipUpdateText
attributes.append(toolTipAttribute)
}
//set the titleLabel
label.text = updatedLabelText
label.attributes = attributes
label.textStyle = labelTextStyle
label.surface = surface
label.disabled = disabled
}
private func showToolTip(){
print("You should show the tooltip now!")
}
}

View File

@ -0,0 +1,32 @@
//
// UIApplication.swift
// VDS
//
// Created by Matt Bruce on 4/14/23.
//
import Foundation
import UIKit
extension UIApplication {
public class func topViewController(controller: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
if let nav = controller as? UINavigationController {
return topViewController(controller: nav.visibleViewController)
}
if let tab = controller as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}