241 lines
9.9 KiB
Swift
241 lines
9.9 KiB
Swift
//
|
|
// ModalDialog.swift
|
|
// VDS
|
|
//
|
|
// Created by Kanamarlapudi, Vasavi on 09/09/24.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import VDSCoreTokens
|
|
|
|
@objcMembers
|
|
@objc(VDSModalDialog)
|
|
open class ModalDialog: View, UIScrollViewDelegate, ParentViewProtocol {
|
|
|
|
//--------------------------------------------------
|
|
// 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: - Private Properties
|
|
//--------------------------------------------------
|
|
private var scrollView = UIScrollView().with {
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
$0.backgroundColor = .clear
|
|
}
|
|
|
|
private let contentStackView = UIStackView().with {
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
$0.axis = .vertical
|
|
$0.alignment = .leading
|
|
$0.distribution = .fillProportionally
|
|
$0.spacing = 0
|
|
}
|
|
|
|
lazy var primaryAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self).with {
|
|
$0.accessibilityLabel = "Modal"
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Public Properties
|
|
//--------------------------------------------------
|
|
open var children: [any ViewProtocol] { [closeCrossButton, titleLabel, contentLabel, closeButton] }
|
|
|
|
open var modalModel = Modal.ModalModel() { didSet { setNeedsUpdate() } }
|
|
|
|
open var titleLabel = Label().with { label in
|
|
label.isAccessibilityElement = true
|
|
label.textStyle = .boldTitleMedium
|
|
}
|
|
|
|
open var contentLabel = Label().with { label in
|
|
label.isAccessibilityElement = true
|
|
label.textStyle = .bodyLarge
|
|
}
|
|
|
|
open lazy var closeCrossButton = ButtonIcon().with {
|
|
$0.kind = .ghost
|
|
$0.surfaceType = .colorFill
|
|
$0.iconName = .close
|
|
$0.size = .small
|
|
$0.customContainerSize = UIDevice.isIPad ? 48 : 48
|
|
$0.customIconSize = UIDevice.isIPad ? 32 : 32
|
|
}
|
|
open lazy var closeButton = Button().with{ $0.use = .secondary; $0.text = "Close"}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Configuration
|
|
//--------------------------------------------------
|
|
private var fullWidth: CGFloat = 0.0
|
|
private var minHeight: CGFloat = 232.0
|
|
private var maxHeight: CGFloat = 0.0
|
|
private var minWidth: CGFloat = 560.0
|
|
private var maxWidth: CGFloat = 0.0
|
|
|
|
private let containerViewInset = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space4X
|
|
private let contentLabelTopSpace = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X
|
|
private let contentLabelBottomSpace = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space12X
|
|
private let gapBetweenButtonItems = VDSLayout.space3X
|
|
|
|
private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark)
|
|
private let closeButtonTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
|
|
|
|
private var contentStackViewBottomConstraint: NSLayoutConstraint?
|
|
private var heightConstraint: NSLayoutConstraint?
|
|
|
|
//--------------------------------------------------
|
|
// 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()
|
|
|
|
fullWidth = UIScreen.main.bounds.size.width
|
|
|
|
// Max Width: 70% of viewport width.
|
|
// Maximum width is only applied to a provided width and not to the default.
|
|
maxWidth = fullWidth * (70/100)
|
|
|
|
// Max Height: Total window height 70% of viewport height.
|
|
// For Tablet: By default the model's height is dynamic and is based on the amount of content
|
|
// it contains. it can expand between a minimum and maximum height. The minimum height is determined by the minimum
|
|
// height of the content area plus the top and bottom padding.
|
|
maxHeight = UIScreen.main.bounds.size.height * (70/100)
|
|
|
|
titleLabel.accessibilityTraits = .header
|
|
|
|
layer.cornerRadius = 12
|
|
contentStackView.addArrangedSubview(titleLabel)
|
|
contentStackView.addArrangedSubview(contentLabel)
|
|
contentStackView.setCustomSpacing(contentLabelTopSpace, after: titleLabel)
|
|
|
|
scrollView.addSubview(contentStackView)
|
|
addSubview(closeCrossButton)
|
|
addSubview(scrollView)
|
|
addSubview(closeButton)
|
|
self.bringSubviewToFront(closeCrossButton)
|
|
let trailingSpace = UIDevice.isIPad ? containerViewInset/2 : containerViewInset
|
|
|
|
// Activate constraints
|
|
NSLayoutConstraint.activate([
|
|
widthAnchor.constraint(equalToConstant: maxWidth),
|
|
|
|
// Constraints for the closeCrossButton
|
|
closeCrossButton.topAnchor.constraint(equalTo: topAnchor),
|
|
closeCrossButton.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
|
|
closeCrossButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
closeCrossButton.heightAnchor.constraint(equalToConstant: 48.0),
|
|
closeCrossButton.widthAnchor.constraint(equalToConstant: 48.0),
|
|
|
|
// Constraints for the bottom button view
|
|
closeButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant:containerViewInset),
|
|
closeButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -containerViewInset),
|
|
closeButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -containerViewInset),
|
|
|
|
// Constraints for the scrollView
|
|
scrollView.topAnchor.constraint(equalTo: topAnchor, constant: containerViewInset),
|
|
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor, constant:containerViewInset),
|
|
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -(trailingSpace)),
|
|
scrollView.bottomAnchor.constraint(equalTo: closeButton.topAnchor, constant: -contentLabelBottomSpace),
|
|
|
|
// Constraints for the contentStackView
|
|
contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
|
|
contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
|
|
contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
|
|
contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -trailingSpace),
|
|
contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
|
|
])
|
|
|
|
contentStackViewBottomConstraint = contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
|
|
contentStackViewBottomConstraint?.activate()
|
|
heightConstraint = heightAnchor.constraint(equalToConstant: maxHeight)
|
|
heightConstraint?.activate()
|
|
}
|
|
|
|
/// Used to make changes to the View based off a change events or from local properties.
|
|
open override func updateView() {
|
|
super.updateView()
|
|
|
|
backgroundColor = backgroundColorConfiguration.getColor(self)
|
|
scrollView.indicatorStyle = surface == .light ? .black : .white
|
|
|
|
contentStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
titleLabel.surface = surface
|
|
contentLabel.surface = surface
|
|
|
|
titleLabel.text = modalModel.title
|
|
contentLabel.text = modalModel.content
|
|
|
|
titleLabel.sizeToFit()
|
|
contentLabel.sizeToFit()
|
|
|
|
var addedTitle = false
|
|
|
|
if let titleText = modalModel.title, !titleText.isEmpty {
|
|
contentStackView.addArrangedSubview(titleLabel)
|
|
addedTitle = true
|
|
}
|
|
|
|
var addedContent = false
|
|
if let contentText = modalModel.content, !contentText.isEmpty {
|
|
contentStackView.addArrangedSubview(contentLabel)
|
|
addedContent = true
|
|
} else if let contentView = modalModel.contentView {
|
|
contentView.translatesAutoresizingMaskIntoConstraints = false
|
|
if var surfaceable = contentView as? Surfaceable {
|
|
surfaceable.surface = surface
|
|
}
|
|
contentStackView.addArrangedSubview(contentView)
|
|
addedContent = true
|
|
}
|
|
|
|
if addedTitle && addedContent {
|
|
contentStackView.setCustomSpacing(contentLabelTopSpace, after: titleLabel)
|
|
}
|
|
|
|
let closeButtonTextColor = closeButtonTextColorConfiguration.getColor(self)
|
|
closeButton.setTitleColor(closeButtonTextColor, for: .normal)
|
|
closeButton.setTitleColor(closeButtonTextColor, for: .highlighted)
|
|
closeButton.setTitle(modalModel.closeButtonText, for: .normal)
|
|
closeButton.accessibilityLabel = modalModel.closeButtonText
|
|
|
|
contentStackView.setNeedsLayout()
|
|
contentStackView.layoutIfNeeded()
|
|
|
|
scrollView.setNeedsLayout()
|
|
scrollView.layoutIfNeeded()
|
|
}
|
|
|
|
/// Used to update any Accessibility properties.
|
|
open override func updateAccessibility() {
|
|
super.updateAccessibility()
|
|
primaryAccessibilityElement.accessibilityHint = "Double tap on the \(modalModel.closeButtonText) button to close."
|
|
primaryAccessibilityElement.accessibilityFrameInContainerSpace = .init(origin: .zero, size: frame.size)
|
|
}
|
|
|
|
open override var accessibilityElements: [Any]? {
|
|
get {
|
|
var elements: [Any] = [primaryAccessibilityElement]
|
|
contentStackView.arrangedSubviews.forEach{ elements.append($0) }
|
|
elements.append(closeButton)
|
|
|
|
return elements
|
|
}
|
|
set {}
|
|
}
|
|
|
|
}
|