// // Tilet.swift // VDS // // Created by Matt Bruce on 12/19/22. // import Foundation import Foundation import VDSColorTokens import UIKit @objc(VDSTilet) open class Tilet: TileContainer { public enum TextPosition { case top case bottom } //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) initialSetup() } public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) initialSetup() } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var stackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical $0.distribution = .fill $0.spacing = 5 } private var titleLockupContainerView = UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false } private var titleLockup = TitleLockup().with { let configs = [ TypographicalStyleDeviceSpacingConfig([.TitleSmall, .BoldTitleSmall], neighboring: [ .BodySmall, .BoldBodySmall, .BodyMedium, .BoldBodyMedium ], spacing: 8.0, deviceType: .iPhone), TypographicalStyleDeviceSpacingConfig([.TitleMedium, .BoldTitleMedium, .TitleLarge, .BoldTitleLarge], neighboring: [ .BodySmall, .BoldBodySmall, .BodyMedium, .BoldBodyMedium, .BodyLarge, .BoldBodyLarge], spacing: 8.0, deviceType: .iPhone), TypographicalStyleDeviceSpacingConfig([.TitleXLarge, .BoldTitleXLarge], neighboring: [ .BodySmall, .BoldBodySmall, .BodyMedium, .BoldBodyMedium, .BodyLarge, .BoldBodyLarge, .TitleMedium, .BoldTitleMedium ], spacing: 12.0, deviceType: .iPhone), TypographicalStyleDeviceSpacingConfig([.TitleSmall, .BoldTitleSmall, .TitleMedium, .BoldTitleMedium], neighboring: [ .BodySmall, .BoldBodySmall, .BodyMedium, .BoldBodyMedium, .BodyLarge, .BoldBodyLarge ], spacing: 8.0, deviceType: .iPad), TypographicalStyleDeviceSpacingConfig([.TitleLarge, .BoldTitleLarge], neighboring: [ .BodySmall, .BoldBodySmall, .BodyMedium, .BoldBodyMedium, .BodyLarge, .BoldBodyLarge, .TitleSmall, .BoldTitleSmall ], spacing: 12.0, deviceType: .iPad), TypographicalStyleDeviceSpacingConfig([.TitleXLarge, .BoldTitleXLarge], neighboring: [ .BodyLarge, .BoldBodyLarge, .TitleMedium, .BoldTitleMedium ], spacing: 16.0, deviceType: .iPad) ] $0.bottomTypographicalStyleSpacingConfig = TypographicalStyleSpacingConfig(configs: configs) } private var badgeContainerView = UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false } private var badge = Badge().with { $0.fillColor = .red } private let iconContainerView = UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.backgroundColor = .clear } private var descriptiveIcon = Icon() private var directionalIcon = Icon().with { $0.name = .rightArrow } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- //style private var _textWidth: CGFloat? open var textWidth: CGFloat? { get { _textWidth } set { if let newValue, newValue > 44.0 && newValue <= width { _textWidth = newValue if _textPercentage != nil { _textPercentage = nil } } else { _textWidth = nil } didChange() } } private var _textPercentage: CGFloat? open var textPercentage: CGFloat? { get { _textPercentage } set { if let newValue, newValue >= 5 && newValue <= 100.0 { _textPercentage = newValue if textWidth != nil { _textWidth = nil } } else { _textPercentage = nil } didChange() } } open var textPostion: TextPosition = .top { didSet { didChange() }} //models public var badgeModel: TiletBadgeModel? { didSet { didChange() }} public var titleModel: TiletTitleModel? { didSet { didChange() }} public var subTitleModel: TiletSubTitleModel? { didSet { didChange() }} //only 1 Icon can be active private var _descriptiveIconModel: TiletDescriptiveIcon? public var descriptiveIconModel: TiletDescriptiveIcon? { get { _descriptiveIconModel } set { _descriptiveIconModel = newValue; _directionalIconModel = nil didChange() } } private var _directionalIconModel: TiletDirectionalIcon? public var directionalIconModel: TiletDirectionalIcon? { get { _directionalIconModel } set { _directionalIconModel = newValue; _descriptiveIconModel = nil didChange() } } //icons //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- internal var titleLockupWidthConstraint: NSLayoutConstraint? internal var titleLockupTrailingConstraint: NSLayoutConstraint? //functions //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setup() { super.setup() width = 100 aspectRatio = .none containerBackgroundColor = .black let view = UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false } view.addSubview(stackView) stackView.pinTop() stackView.pinLeading() stackView.pinTrailing() stackView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor).isActive = true addContentView(view) //badge badgeContainerView.addSubview(badge) badge .pinTop() .pinLeading() .pinBottom() badge.trailingAnchor.constraint(lessThanOrEqualTo: badgeContainerView.trailingAnchor).isActive = true titleLockupContainerView.addSubview(titleLockup) titleLockup .pinTop() .pinLeading() .pinBottom() titleLockupTrailingConstraint = titleLockup.trailingAnchor.constraint(equalTo: titleLockupContainerView.trailingAnchor) titleLockupTrailingConstraint?.isActive = true iconContainerView.addSubview(descriptiveIcon) iconContainerView.addSubview(directionalIcon) descriptiveIcon .pinLeading() .pinTop() .pinBottom() directionalIcon .pinTrailing() .pinTop() .pinBottom() } public override func reset() { super.reset() aspectRatio = .none surface = .light containerBackgroundColor = .black //models badgeModel = nil titleModel = nil subTitleModel = nil descriptiveIconModel = nil directionalIconModel = nil } //-------------------------------------------------- // MARK: - State //-------------------------------------------------- fileprivate func updateBadge() { if let badgeModel { badge.text = badgeModel.text badge.fillColor = badgeModel.fillColor badge.numberOfLines = badgeModel.numberOfLines badge.surface = badgeModel.surface badge.maxWidth = badgeModel.maxWidth if badgeContainerView.superview == nil { stackView.insertArrangedSubview(badgeContainerView, at: 0) } } else { badgeContainerView.removeFromSuperview() } } fileprivate func updateTitleLockup() { var showTitleLockup = false if let titleModel, !titleModel.text.isEmpty { showTitleLockup = true } if let subTitleModel, !subTitleModel.text.isEmpty { showTitleLockup = true } if showTitleLockup { //flip the surface for the titleLockup titleLockup.surface = containerBackgroundColor == .black ? .dark : .light //titleLockup if let textWidth { titleLockupTrailingConstraint?.isActive = false titleLockupWidthConstraint?.isActive = false titleLockupWidthConstraint = titleLockup.widthAnchor.constraint(equalToConstant: textWidth) titleLockupWidthConstraint?.isActive = true } else if let textPercentage { titleLockupTrailingConstraint?.isActive = false titleLockupWidthConstraint?.isActive = false titleLockupWidthConstraint = NSLayoutConstraint(item: titleLockup, attribute: .width, relatedBy: .equal, toItem: containerView, attribute: .width, multiplier: textPercentage / 100, constant: 0.0) titleLockupWidthConstraint?.isActive = true } else { titleLockupWidthConstraint?.isActive = false titleLockupTrailingConstraint?.isActive = true } //set models titleLockup.titleModel = titleModel?.toTitleLockupTitleModel() titleLockup.subTitleModel = subTitleModel?.toTitleLockupSubTitleModel() if let style = subTitleModel?.typographicalStyle.value { titleLockup.otherTypograpicalStyle = style } if titleLockupContainerView.superview == nil { stackView.insertArrangedSubview(titleLockupContainerView, at: badgeContainerView.superview == nil ? 0 : 1) } } else { titleLockupContainerView.removeFromSuperview() } } fileprivate func updateIcons() { //icons var showIconContainerView = false if let descriptiveIconModel { descriptiveIcon.name = descriptiveIconModel.name descriptiveIcon.size = descriptiveIconModel.size descriptiveIcon.surface = descriptiveIconModel.surface showIconContainerView = true } if let directionalIconModel { directionalIcon.size = directionalIconModel.size directionalIcon.surface = directionalIconModel.surface showIconContainerView = true } //iconContainer descriptiveIcon.isHidden = descriptiveIconModel == nil directionalIcon.isHidden = directionalIconModel == nil if showIconContainerView { //spacing before iconContainerView var view: UIView? if badgeContainerView.superview != nil { view = badgeContainerView } if titleLockupContainerView.superview != nil { view = titleLockupContainerView } if let view { stackView.setCustomSpacing(containerPadding.tiletSpacing, after: view) } if iconContainerView.superview == nil { stackView.addArrangedSubview(iconContainerView) } } else { iconContainerView.removeFromSuperview() } } open override func updateView() { super.updateView() updateBadge() updateTitleLockup() updateIcons() } } extension TileContainer.ContainerPadding { fileprivate var tiletSpacing: CGFloat { switch self { case .twelve: return 16 case .sixteen: return 24 case .twentyFour: return 32 case .thirtyTwo: return 48 case .fourtyEight: return 16 } } }