// // TooltipAlertViewController.swift // VDS // // Created by Matt Bruce on 4/14/23. // import Foundation import UIKit import Combine import VDSColorTokens open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollViewDelegate { /// Set of Subscribers for any Publishers for this Control public var subscribers = Set() //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var onClickSubscriber: AnyCancellable? { willSet { if let onClickSubscriber { onClickSubscriber.cancel() } } } private var scrollView = UIScrollView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.backgroundColor = .clear } private let modalView = View().with { $0.layer.cornerRadius = 8 } public var contentView: UIView? = nil private let contentStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical $0.distribution = .fillProportionally $0.spacing = 0 } private var line = Line().with { instance in instance.lineViewColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).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 = .bodyLarge } 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 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) private let containerViewInset = VDSLayout.Spacing.space4X.value private var containerBottomConstraint: NSLayoutConstraint? private var containerHeightConstraint: NSLayoutConstraint? private var contentStackViewBottomConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func viewDidLoad() { super.viewDidLoad() isModalInPresentation = true setup() } open func setup() { //left-right swipe view.publisher(for: UISwipeGestureRecognizer().with{ $0.direction = .right }) .sink { [weak self] swipe in guard let self else { return } self.dismiss(animated: true, completion: nil) }.store(in: &subscribers) //tapping in background view.publisher(for: UITapGestureRecognizer().with{ $0.numberOfTapsRequired = 1 }) .sink { [weak self] swipe in guard let self else { return } self.dismiss(animated: true, completion: nil) }.store(in: &subscribers) //clicking button onClickSubscriber = closeButton.publisher(for: .touchUpInside) .sink {[weak self] button in guard let self else { return } self.dismiss(animated: true, completion: nil) } contentStackView.addArrangedSubview(titleLabel) contentStackView.addArrangedSubview(contentLabel) scrollView.addSubview(contentStackView) modalView.addSubview(scrollView) modalView.addSubview(line) modalView.addSubview(closeButton) view.addSubview(modalView) // Activate constraints NSLayoutConstraint.activate([ // Constraints for the floating modal view modalView.centerXAnchor.constraint(equalTo: view.centerXAnchor), modalView.centerYAnchor.constraint(equalTo: view.centerYAnchor), modalView.widthAnchor.constraint(equalToConstant: 296), modalView.heightAnchor.constraint(greaterThanOrEqualToConstant: 96), modalView.heightAnchor.constraint(lessThanOrEqualToConstant: 312), // Constraints for the scroll view scrollView.topAnchor.constraint(equalTo: modalView.topAnchor, constant: VDSLayout.Spacing.space4X.value), scrollView.leadingAnchor.constraint(equalTo: modalView.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: modalView.trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: line.topAnchor), line.leadingAnchor.constraint(equalTo: modalView.leadingAnchor), line.trailingAnchor.constraint(equalTo: modalView.trailingAnchor), closeButton.topAnchor.constraint(equalTo: line.bottomAnchor), closeButton.leadingAnchor.constraint(equalTo: modalView.leadingAnchor), closeButton.trailingAnchor.constraint(equalTo: modalView.trailingAnchor), closeButton.bottomAnchor.constraint(equalTo: modalView.bottomAnchor), closeButton.heightAnchor.constraint(equalToConstant: 44.0), contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor), contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: containerViewInset), contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -containerViewInset), contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -containerViewInset), contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -(containerViewInset * 2)), contentStackView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.heightAnchor, constant: -(containerViewInset * 2)) ]) } open func updateView() { view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.3) modalView.backgroundColor = containerViewBackgroundColorConfiguration.getColor(self) scrollView.indicatorStyle = surface == .light ? .black : .white titleLabel.removeFromSuperview() contentLabel.removeFromSuperview() contentView?.removeFromSuperview() titleLabel.surface = surface contentLabel.surface = surface line.surface = surface titleLabel.text = titleText contentLabel.text = contentText titleLabel.sizeToFit() contentLabel.sizeToFit() var addedTitle = false if let titleText, !titleText.isEmpty { contentStackView.addArrangedSubview(titleLabel) addedTitle = true } var addedContent = false if let contentText, !contentText.isEmpty { contentStackView.addArrangedSubview(contentLabel) addedContent = true } else if let contentView { contentView.translatesAutoresizingMaskIntoConstraints = false if var surfaceable = contentView as? Surfaceable { surfaceable.surface = surface } let wrapper = View() wrapper.addSubview(contentView) contentView.pinTop() contentView.pinLeading() contentView.pinBottom() contentView.pinTrailingLessThanOrEqualTo() contentView.setNeedsLayout() contentStackView.addArrangedSubview(wrapper) addedContent = true } if addedTitle && addedContent { contentStackView.setCustomSpacing(VDSLayout.Spacing.space1X.value, after: titleLabel) } let closeButtonTextColor = closeButtonTextColorConfiguration.getColor(self) closeButton.setTitleColor(closeButtonTextColor, for: .normal) closeButton.setTitleColor(closeButtonTextColor, for: .highlighted) closeButton.setTitle(closeButtonText, for: .normal) scrollView.layoutIfNeeded() } }