// // FootnoteGroup.swift // VDS // // Created by Kanamarlapudi, Vasavi on 29/08/24. // import Foundation import UIKit import VDSCoreTokens /// This must always be paired with one or more ``Footnote`` in a FootnoteGroup. @objcMembers @objc(VDSFootnoteGroup) open class FootnoteGroup: View { //-------------------------------------------------- // 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: - enums //-------------------------------------------------- /// Enum used to describe the width of a fixed value or percentage of parent's width. public enum Width { case percentage(CGFloat) case value(CGFloat) } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- /// Array of ``Footnote`` for the Footnote items. open var footnoteItems: [FootnoteItem] = [] { didSet { updateFootnoteItems() } } /// Any percentage or pixel value and cannot exceed container size. /// If there is a width that is larger than container size, the footnote will resize to container's width. open var width: Width? { get { _width } set { if let newValue { switch newValue { case .percentage(let percentage): if percentage <= 100.0 { _width = newValue } case .value(let value): if value > 0 { _width = newValue } } } else { _width = nil } updateContainerWidth() } } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var _width: Width? = nil private lazy var stackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical $0.distribution = .fill $0.spacing = VDSLayout.space3X $0.backgroundColor = .clear } //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- internal var maxWidth: CGFloat { horizontalPinnedWidth() ?? (superview?.frame.size.width ?? frame.size.width) } internal var minWidth: CGFloat { containerSize.width } internal var containerSize: CGSize { CGSize(width: 55, height: 44) } //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- internal var widthConstraint: 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() // add footnote item stackview. addSubview(stackView) stackView.pinToSuperView() widthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate() } open override func setDefaults() { super.setDefaults() width = nil footnoteItems = [] } internal func updateFootnoteItems() { // symbol containers are as wide as the widest symbol container in the group. var symbolMaxWidth = 0.0 footnoteItems.forEach { footnote in let separatorWidth = Label().with { $0.text = footnote.symbolType $0.textStyle = footnote.symbolLabel.textStyle $0.sizeToFit() }.intrinsicContentSize.width symbolMaxWidth = max(separatorWidth, symbolMaxWidth) } stackView.removeArrangedSubviews() // add symbol label, text label to stack. footnoteItems.forEach { footnote in footnote.symbolWidth = symbolMaxWidth footnote.surface = surface stackView.addArrangedSubview(footnote) } } /// Update container width after updating content. internal func updateContainerWidth() { var newWidth = 0.0 switch width { case .percentage(let percentage): newWidth = max(maxWidth * ((percentage) / 100), minWidth) case .value(let value): newWidth = value > maxWidth ? maxWidth : value case nil: break } widthConstraint?.deactivate() if newWidth > minWidth && newWidth < maxWidth { widthConstraint?.constant = newWidth widthConstraint?.activate() } } }