diff --git a/VDS/Components/Tooltip/TooltipAlertViewController.swift b/VDS/Components/Tooltip/TooltipAlertViewController.swift index b645c424..66bf7eca 100644 --- a/VDS/Components/Tooltip/TooltipAlertViewController.swift +++ b/VDS/Components/Tooltip/TooltipAlertViewController.swift @@ -10,7 +10,7 @@ import UIKit import Combine import VDSColorTokens -open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollViewDelegate { +open class TooltipAlertViewController: UIViewController, Surfaceable { /// Set of Subscribers for any Publishers for this Control public var subscribers = Set() @@ -26,69 +26,22 @@ open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollVi } } - private var scrollView = UIScrollView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.backgroundColor = .clear - } + private let tooltipDialog = TooltipDialog() - 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 contentView: UIView? { didSet { updateView() }} 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 //-------------------------------------------------- @@ -114,57 +67,152 @@ open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollVi }.store(in: &subscribers) //clicking button - onClickSubscriber = closeButton.publisher(for: .touchUpInside) + onClickSubscriber = tooltipDialog.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) + view.addSubview(tooltipDialog) // 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)) - + tooltipDialog.centerXAnchor.constraint(equalTo: view.centerXAnchor), + tooltipDialog.centerYAnchor.constraint(equalTo: view.centerYAnchor), + tooltipDialog.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor), + tooltipDialog.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor), + tooltipDialog.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor), + tooltipDialog.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor) ]) } open func updateView() { view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.3) - modalView.backgroundColor = containerViewBackgroundColorConfiguration.getColor(self) + tooltipDialog.surface = surface + tooltipDialog.titleText = titleText + tooltipDialog.contentText = contentText + tooltipDialog.contentView = contentView + } +} + +open class TooltipDialog: View, UIScrollViewDelegate { + + private var scrollView = UIScrollView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + } + + 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 titleText: String? { didSet { setNeedsUpdate() }} + open var titleLabel = Label().with { label in + label.textStyle = .boldTitleMedium + } + + open var contentText: String? { didSet { setNeedsUpdate() }} + open var contentLabel = Label().with { label in + label.textStyle = .bodyLarge + } + + open var contentView: UIView? = nil + + open var closeButtonText: String = "Close" { didSet { setNeedsUpdate() }} + + 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 var closeButtonHeight: CGFloat = 44.0 + private var fullWidth: CGFloat = 296 + private var minHeight: CGFloat = 96.0 + private var maxHeight: CGFloat = 312.0 + + 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 contentStackViewBottomConstraint: NSLayoutConstraint? + + private var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setup() { + super.setup() + + layer.cornerRadius = 8 + + contentStackView.addArrangedSubview(titleLabel) + contentStackView.addArrangedSubview(contentLabel) + scrollView.addSubview(contentStackView) + addSubview(scrollView) + addSubview(line) + addSubview(closeButton) + + // Activate constraints + NSLayoutConstraint.activate([ + widthAnchor.constraint(equalToConstant: fullWidth), + + // Constraints for the scroll view + scrollView.topAnchor.constraint(equalTo: topAnchor, constant: VDSLayout.Spacing.space4X.value), + scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), + scrollView.bottomAnchor.constraint(equalTo: line.topAnchor), + + line.leadingAnchor.constraint(equalTo: leadingAnchor), + line.trailingAnchor.constraint(equalTo: trailingAnchor), + + closeButton.topAnchor.constraint(equalTo: line.bottomAnchor), + closeButton.leadingAnchor.constraint(equalTo: leadingAnchor), + closeButton.trailingAnchor.constraint(equalTo: trailingAnchor), + closeButton.bottomAnchor.constraint(equalTo: bottomAnchor), + closeButton.heightAnchor.constraint(equalToConstant: closeButtonHeight), + + contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor), + contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: containerViewInset), + contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -containerViewInset), + contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -(containerViewInset * 2)), + + ]) + contentStackViewBottomConstraint = contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor) + contentStackViewBottomConstraint?.activate() + + heightConstraint = heightAnchor.constraint(equalToConstant: minHeight) + heightConstraint?.activate() + } + + open override func updateView() { + super.updateView() + + backgroundColor = containerViewBackgroundColorConfiguration.getColor(self) scrollView.indicatorStyle = surface == .light ? .black : .white titleLabel.removeFromSuperview() @@ -217,7 +265,31 @@ open class TooltipAlertViewController: UIViewController, Surfaceable, UIScrollVi closeButton.setTitleColor(closeButtonTextColor, for: .highlighted) closeButton.setTitle(closeButtonText, for: .normal) + contentStackView.setNeedsLayout() + contentStackView.layoutIfNeeded() + + scrollView.setNeedsLayout() scrollView.layoutIfNeeded() - + + //dealing with height + //we can't really use the minMax height and set constraints for + //greaterThan or lessThan on the heightAnchor due to scrollView/stackView intrinsic size + //therefore we can do a little math and manually set the height based off all of the content + var contentHeight = closeButtonHeight + scrollView.contentSize.height + (containerViewInset * 2) + + //reset the bottomConstraint + contentStackViewBottomConstraint?.constant = 0 + + if contentHeight < minHeight { + contentHeight = minHeight + + } else if contentHeight > maxHeight { + contentHeight = maxHeight + //since we are now scrolling, add padding to the bottom of the + //stackView between the bottom of the scrollView + contentStackViewBottomConstraint?.constant = -containerViewInset + } + + heightConstraint?.constant = contentHeight } }