vds_ios/VDS/Components/TitleLockup/TitleLockup.swift
Matt Bruce fbca279f7e updated for typography spacing config
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2022-12-20 11:10:54 -06:00

339 lines
14 KiB
Swift

//
// TitleLockup.swift
// VDS
//
// Created by Matt Bruce on 12/19/22.
//
import Foundation
import UIKit
import VDSColorTokens
import Combine
public enum TitleLockupTextPosition: String, Codable, CaseIterable {
case left, center
var labelTextPosition: TextPosition {
switch self {
case .left:
return .left
case .center:
return .center
}
}
}
public enum TitleLockupTitleTypographicalStyle: String, Codable, EnumSubset {
case FeatureMedium
case BoldFeatureMedium
case FeatureSmall
case BoldFeatureSmall
case FeatureXSmall
case BoldFeatureXSmall
case Title2XLarge
case BoldTitle2XLarge
case TitleXLarge
case BoldTitleXLarge
case TitleLarge
case BoldTitleLarge
case TitleMedium
case BoldTitleMedium
case TitleSmall
case BoldTitleSmall
public var defaultValue: TypographicalStyle {.BoldFeatureXSmall }
}
public enum TitleLockupOtherTypographicalStyle: String, Codable, EnumSubset {
case BodyLarge
case BoldBodyLarge
case BodyMedium
case BoldBodyMedium
case BodySmall
case BoldBodySmall
public var defaultValue: TypographicalStyle {.BodyLarge }
}
@objc(VDSTitleLockup)
open class TitleLockup: 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: - Private Properties
//--------------------------------------------------
private var stackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
$0.distribution = .fill
}
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
// Sizes are from InVision design specs.
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var textPosition: TitleLockupTextPosition = .left { didSet { didChange() }}
//style
open var titleTypograpicalStyle: TitleLockupTitleTypographicalStyle = .BoldFeatureXSmall { didSet { didChange() }}
open var otherTypograpicalStyle: TitleLockupOtherTypographicalStyle = UIDevice.isIPad ? .BodyLarge : .BodyMedium { didSet { didChange() }}
//first row
open var eyebrowLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
}
open var eyebrowText: String = "" { didSet { didChange() }}
open var eyebrowTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
//second row
open var titleLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
}
open var titleText: String = "" { didSet { didChange() }}
open var titleTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
//third row
open var subTitleLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
}
open var subTitleText: String = "" { didSet { didChange() }}
open var subTitleTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }}
open var subTitleColor: Use = .primary { didSet { didChange() }}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func setup() {
super.setup()
isAccessibilityElement = true
accessibilityTraits = .button
addSubview(stackView)
stackView.spacing = 0.0
stackView.addArrangedSubview(eyebrowLabel)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(subTitleLabel)
//pin stackview to edges
stackView.pinToSuperView()
}
public override func reset() {
super.reset()
titleLabel.reset()
eyebrowLabel.reset()
subTitleLabel.reset()
textPosition = .left
eyebrowText = ""
eyebrowTextAttributes = nil
titleText = ""
titleTextAttributes = nil
subTitleText = ""
subTitleTextAttributes = nil
titleTextAttributes = nil
titleTypograpicalStyle = .BoldFeatureXSmall
otherTypograpicalStyle = .BodyLarge
}
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView() {
super.updateView()
let allLabelsTextPosition = textPosition.labelTextPosition
eyebrowLabel.textPosition = allLabelsTextPosition
eyebrowLabel.typograpicalStyle = otherTypograpicalStyle.value
eyebrowLabel.text = eyebrowText
eyebrowLabel.attributes = eyebrowTextAttributes
eyebrowLabel.surface = surface
titleLabel.textPosition = allLabelsTextPosition
titleLabel.typograpicalStyle = titleTypograpicalStyle.value
titleLabel.text = titleText
titleLabel.attributes = titleTextAttributes
titleLabel.surface = surface
subTitleLabel.textPosition = allLabelsTextPosition
subTitleLabel.typograpicalStyle = otherTypograpicalStyle.value
subTitleLabel.text = subTitleText
subTitleLabel.attributes = subTitleTextAttributes
subTitleLabel.surface = surface
subTitleLabel.disabled = subTitleColor == .secondary
//if both first 2 rows not empty set spacing
if !eyebrowText.isEmpty && !titleText.isEmpty {
stackView.spacing = getTopSpacing()
} else {
stackView.spacing = 0.0
}
//if either first 2 rows not empty and subtile not empty, create space else collapse
if (!eyebrowText.isEmpty || !titleText.isEmpty) && !subTitleText.isEmpty {
stackView.setCustomSpacing(getBottomSpacing(), after: titleLabel)
} else if (!eyebrowText.isEmpty || !titleText.isEmpty) && subTitleText.isEmpty {
stackView.setCustomSpacing(0.0, after: titleLabel)
}
}
internal var topTypographicalStyleSpacingConfig: TypographicalStyleSpacingConfig = {
let configs = [
TypographicalStyleDeviceSpacingConfig([.BoldTitleLarge, .TitleLarge],
neighboring: [.BodySmall, .BodyMedium, .BodyLarge],
spacing: 12.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitleXLarge, .TitleXLarge],
neighboring: [.TitleMedium, .BodyLarge],
spacing: 12.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitle2XLarge, .Title2XLarge, .BoldFeatureXSmall, .FeatureXSmall],
neighboring: [.TitleMedium, .TitleLarge],
spacing: 16.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitle2XLarge, .Title2XLarge, .BoldFeatureXSmall, .FeatureXSmall],
neighboring: [.BodyLarge],
spacing: 12.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureSmall, .FeatureSmall, .BoldFeatureMedium, .FeatureMedium],
neighboring: [.TitleMedium, .TitleLarge],
spacing: 16.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureSmall, .FeatureSmall, .BoldFeatureMedium, .FeatureMedium],
neighboring: [.BodyLarge],
spacing: 12.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitleXLarge, .TitleXLarge],
neighboring: [.BodyLarge, .BodyMedium, .BodySmall, .TitleMedium],
spacing: 12.0,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldTitle2XLarge, .Title2XLarge, .BoldFeatureXSmall, .FeatureXSmall],
neighboring: [.BodyLarge, .BodyMedium, .TitleMedium],
spacing: 12.0,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureSmall, .FeatureSmall],
neighboring: [.TitleLarge, .BodyLarge],
spacing: 12.0,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureMedium, .FeatureMedium],
neighboring: [.TitleLarge, .TitleXLarge],
spacing: 16.0,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureMedium, .FeatureMedium],
neighboring: [.BodyLarge],
spacing: 12.0,
deviceType: .iPhone)
]
return TypographicalStyleSpacingConfig(configs: configs)
}()
internal var bottomTypographicalStyleSpacingConfig: TypographicalStyleSpacingConfig = {
let configs = [
TypographicalStyleDeviceSpacingConfig([.BoldTitleLarge, .TitleLarge],
neighboring: [.BodySmall, .BodyMedium, .BodyLarge],
spacing: 12.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitleXLarge, .TitleXLarge],
neighboring: [.TitleMedium, .BodyLarge],
spacing: 16.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitle2XLarge, .Title2XLarge, .BoldFeatureXSmall, .FeatureXSmall],
neighboring: [.TitleMedium, .TitleLarge],
spacing: 24.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitle2XLarge, .Title2XLarge, .BoldFeatureXSmall, .FeatureXSmall],
neighboring: [.BodyLarge],
spacing: 24.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureSmall, .FeatureSmall, .BoldFeatureMedium, .FeatureMedium],
neighboring: [.TitleMedium, .TitleLarge],
spacing: 24.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureSmall, .FeatureSmall, .BoldFeatureMedium, .FeatureMedium],
neighboring: [.BodyLarge],
spacing: 24.0,
deviceType: .iPad),
TypographicalStyleDeviceSpacingConfig([.BoldTitleXLarge, .TitleXLarge],
neighboring: [.BodyLarge, .BodyMedium, .BodySmall, .TitleMedium],
spacing: 12.0,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldTitle2XLarge, .Title2XLarge, .BoldFeatureXSmall, .FeatureXSmall],
neighboring: [.BodyLarge, .BodyMedium, .TitleMedium],
spacing: 16,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureSmall, .FeatureSmall],
neighboring: [.TitleLarge, .BodyLarge],
spacing: 16.0,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureMedium, .FeatureMedium],
neighboring: [.TitleLarge, .TitleXLarge],
spacing: 24.0,
deviceType: .iPhone),
TypographicalStyleDeviceSpacingConfig([.BoldFeatureMedium, .FeatureMedium],
neighboring: [.BodyLarge],
spacing: 24.0,
deviceType: .iPhone)
]
return TypographicalStyleSpacingConfig(configs: configs)
}()
open func getTopSpacing() -> CGFloat {
topTypographicalStyleSpacingConfig.spacing(for: titleTypograpicalStyle.value, neighboring: otherTypograpicalStyle.value)
}
open func getBottomSpacing() -> CGFloat {
bottomTypographicalStyleSpacingConfig.spacing(for: titleTypograpicalStyle.value, neighboring: otherTypograpicalStyle.value)
}
}
extension TypographicalStyle {
func isWithin(_ collection: [TypographicalStyle]) -> Bool {
(collection.first(where: {$0 == self}) != nil)
}
}