diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 10ade0c0..1f086e68 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -106,6 +106,10 @@ EAC925842911C63100091998 /* Colorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5EEDF28F49DB3003B3210 /* Colorable.swift */; }; EAC9258C2911C9DE00091998 /* InputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC925872911C9DE00091998 /* InputField.swift */; }; EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9258B2911C9DE00091998 /* EntryField.swift */; }; + EAD062A72A3B67770015965D /* UIView+CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD062A62A3B67770015965D /* UIView+CALayer.swift */; }; + EAD062A92A3B67B10015965D /* NSLayoutAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD062A82A3B67B10015965D /* NSLayoutAnchor.swift */; }; + EAD062AB2A3B67D00015965D /* NSLayoutDimension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD062AA2A3B67D00015965D /* NSLayoutDimension.swift */; }; + EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD062AF2A3B873E0015965D /* BadgeIndicator.swift */; }; EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */; }; EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9829D4850E00101452 /* Clickable.swift */; }; EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; @@ -237,6 +241,10 @@ EAC925822911B35300091998 /* TextLinkCaret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLinkCaret.swift; sourceTree = ""; }; EAC925872911C9DE00091998 /* InputField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputField.swift; sourceTree = ""; }; EAC9258B2911C9DE00091998 /* EntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = ""; }; + EAD062A62A3B67770015965D /* UIView+CALayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+CALayer.swift"; sourceTree = ""; }; + EAD062A82A3B67B10015965D /* NSLayoutAnchor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutAnchor.swift; sourceTree = ""; }; + EAD062AA2A3B67D00015965D /* NSLayoutDimension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutDimension.swift; sourceTree = ""; }; + EAD062AF2A3B873E0015965D /* BadgeIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeIndicator.swift; sourceTree = ""; }; EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Publisher.swift"; sourceTree = ""; }; EAF1FE9829D4850E00101452 /* Clickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clickable.swift; sourceTree = ""; }; EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = ""; }; @@ -394,6 +402,7 @@ EA33619D288B1E330071C351 /* Components */ = { isa = PBXGroup; children = ( + EAD062AE2A3B87210015965D /* BadgeIndicator */, EA4DB2FE28DCBC1900103EE3 /* Badge */, EA0FC2BE2912D18200DF80B4 /* Buttons */, EAF7F092289985E200B287F5 /* Checkbox */, @@ -429,12 +438,15 @@ EAF7F0992899B17200B287F5 /* CATransaction.swift */, EA33622D2891EA3C0071C351 /* DispatchQueue+Once.swift */, EABFEB632A26473700C4C106 /* NSAttributedString.swift */, + EAD062A82A3B67B10015965D /* NSLayoutAnchor.swift */, + EAD062AA2A3B67D00015965D /* NSLayoutDimension.swift */, EAB2376529E9952D00AABE9A /* UIApplication.swift */, EA3361A7288B23300071C351 /* UIColor.swift */, EA81410F2A127066004F60D2 /* UIColor+VDSColor.swift */, EA33623D2892EE950071C351 /* UIDevice.swift */, EAF7F0B6289C12A600B287F5 /* UITapGestureRecognizer.swift */, EAB5FED329267EB300998C17 /* UIView.swift */, + EAD062A62A3B67770015965D /* UIView+CALayer.swift */, EAB5FF0029424ACB00998C17 /* UIControl.swift */, EA985C662970C21600F2FF2E /* VDSLayout.swift */, ); @@ -686,6 +698,14 @@ path = EntryField; sourceTree = ""; }; + EAD062AE2A3B87210015965D /* BadgeIndicator */ = { + isa = PBXGroup; + children = ( + EAD062AF2A3B873E0015965D /* BadgeIndicator.swift */, + ); + path = BadgeIndicator; + sourceTree = ""; + }; EAF7F092289985E200B287F5 /* Checkbox */ = { isa = PBXGroup; children = ( @@ -884,6 +904,7 @@ EA33624728931B050071C351 /* Initable.swift in Sources */, EAF7F0A4289B017C00B287F5 /* LabelAttributeModel.swift in Sources */, EA5F86D02A1F936100BC83E4 /* TabsContainer.swift in Sources */, + EAD062A92A3B67B10015965D /* NSLayoutAnchor.swift in Sources */, EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */, EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */, EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, @@ -917,6 +938,7 @@ EA336171288B19200071C351 /* VDS.docc in Sources */, EA985BF02968A93600F2FF2E /* TitleLockupEyebrowModel.swift in Sources */, EA5E30532950DDA60082B959 /* TitleLockup.swift in Sources */, + EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */, EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */, EAB5FF0129424ACB00998C17 /* UIControl.swift in Sources */, EA985BF5296C60C000F2FF2E /* Icon.swift in Sources */, @@ -927,6 +949,7 @@ EAB2376629E9952D00AABE9A /* UIApplication.swift in Sources */, EAB5FED429267EB300998C17 /* UIView.swift in Sources */, EAB2376829E9992800AABE9A /* TooltipAlertViewController.swift in Sources */, + EAD062AB2A3B67D00015965D /* NSLayoutDimension.swift in Sources */, EA33623E2892EE950071C351 /* UIDevice.swift in Sources */, EA985C692971B90B00F2FF2E /* IconSize.swift in Sources */, EA985C672970C21600F2FF2E /* VDSLayout.swift in Sources */, @@ -941,6 +964,7 @@ EA1F266628B945070033E859 /* RadioSwatchGroup.swift in Sources */, EA596ABF2A16B4F500300C4B /* Tabs.swift in Sources */, EAC71A212A2E1DC000E47A9F /* SelectorItemBase.swift in Sources */, + EAD062A72A3B67770015965D /* UIView+CALayer.swift in Sources */, EA985BEC2968A91200F2FF2E /* TitleLockupTitleModel.swift in Sources */, 5FC35BE328D51405004EBEAC /* Button.swift in Sources */, ); diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift new file mode 100644 index 00000000..0bafa46d --- /dev/null +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -0,0 +1,329 @@ +// +// Badge.swift +// VDS +// +// Created by Matt Bruce on 9/22/22. +// + +import Foundation +import UIKit +import VDSColorTokens +import VDSFormControlsTokens +import Combine +import VDSTypographyTokens + +/// Badges are visual labels used to convey status or highlight supplemental information. +@objc(VDSBadgeIndicator) +open class BadgeIndicator: View { + //-------------------------------------------------- + // MARK: - Enums + //-------------------------------------------------- + public enum FillColor: String, CaseIterable { + case red, yellow, green, orange, blue, gray, grayLowContrast, black, white + } + + public enum Kind: String, CaseIterable { + case simple, numbered + } + + public enum MaxDigits: String, CaseIterable { + case one + case two + case three + case four + case five + case six + + public var value: Int { + switch self { + case .two: + return 2 + case .one: + return 1 + case .three: + return 3 + case .four: + return 4 + case .five: + return 5 + case .six: + return 6 + } + } + } + + public enum TextSize: String, CaseIterable { + case xxlarge = "2XLarge" + case xlarge = "XLarge" + case large = "Large" + case medium = "Medium" + case small = "Small" + + public var minimumSize: CGFloat { + switch self { + case .xxlarge: + return 29 + case .xlarge: + return 24 + case .large: + return 20 + case .medium: + return 18 + case .small: + return 16 + } + } + + public var padding: CGFloat { + switch self { + case .xxlarge: + return 8 + case .xlarge: + return 6 + case .large: + return 6 + case .medium: + return 6 + case .small: + return 4 + } + } + + public var textStyle: TextStyle { + let style = TextStyle.bodySmall + var pointSize: CGFloat = VDSTypography.fontSizeBody12 + + switch self { + case .xxlarge: + pointSize = VDSTypography.fontSizeTitle24 + + case .xlarge: + pointSize = VDSTypography.fontSizeTitle20 + + case .large: + pointSize = VDSTypography.fontSizeBody16 + + case .medium: + pointSize = VDSTypography.fontSizeBody14 + + case .small: + pointSize = VDSTypography.fontSizeBody12 + + } + + return TextStyle(rawValue: "\(self.rawValue)BadgeIndicator", + fontFace: style.fontFace, + pointSize: pointSize, + lineHeight: style.lineHeight, + letterSpacing: style.letterSpacing) + } + + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var label = Label().with { + $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.adjustsFontSizeToFitWidth = false + $0.lineBreakMode = .byTruncatingTail + $0.textPosition = .center + $0.numberOfLines = 1 + } + + open var borderColorLight: UIColor? { + didSet { + if let borderColorLight { + borderColorConfiguration.lightColor = borderColorLight + } else { + borderColorConfiguration.lightColor = VDSColor.paletteWhite + } + setNeedsUpdate() + } + } + + open var borderColorDark: UIColor? { + didSet { + if let borderColorDark { + borderColorConfiguration.darkColor = borderColorDark + } else { + borderColorConfiguration.darkColor = VDSColor.paletteBlack + } + setNeedsUpdate() + } + } + + open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }} + + open var number: Int? { didSet { setNeedsUpdate() }} + + open var kind: Kind = .simple { didSet { setNeedsUpdate() }} + + open var leadingCharacter: String? { didSet { setNeedsUpdate() }} + + open var textSize: TextSize = .xxlarge { didSet { setNeedsUpdate() }} + + open var hideDot: Bool = false { didSet { setNeedsUpdate() }} + + open var hideBorder: Bool = false { didSet { setNeedsUpdate() }} + + open var maxDigits: MaxDigits = .two { didSet { setNeedsUpdate() }} + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + private var labelWidthConstraint: NSLayoutConstraint? + private var labelHeightConstraint: NSLayoutConstraint? + private var defaultBadgeSize: CGFloat = 16 + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setup() { + super.setup() + + accessibilityElements = [label] + + addSubview(label) + label.pinToSuperView() + + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: centerXAnchor), + label.centerYAnchor.constraint(equalTo: centerYAnchor) + ]) + + labelWidthConstraint = label.widthGreaterThanEqualTo(constant: defaultBadgeSize).activate() + labelHeightConstraint = label.heightGreaterThanEqualTo(constant: defaultBadgeSize).activate() + } + + open override func reset() { + super.reset() + shouldUpdateView = false + label.reset() + label.lineBreakMode = .byTruncatingTail + label.textPosition = .center + fillColor = .red + number = nil + shouldUpdateView = true + setNeedsUpdate() + } + + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + private var borderColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteBlack) + + private var backgroundColorConfiguration: AnyColorable = { + let config = KeyedColorConfiguration(keyPath: \.fillColor) + config.setSurfaceColors(VDSColor.backgroundBrandhighlight, VDSColor.backgroundBrandhighlight, forKey: .red) + config.setSurfaceColors(VDSColor.paletteYellow53, VDSColor.paletteYellow53, forKey: .yellow) + config.setSurfaceColors(VDSColor.paletteGreen26, VDSColor.paletteGreen36, forKey: .green) + config.setSurfaceColors(VDSColor.paletteOrange41, VDSColor.paletteOrange58, forKey: .orange) + config.setSurfaceColors(VDSColor.paletteBlue38, VDSColor.paletteBlue46, forKey: .blue) + config.setSurfaceColors(VDSColor.paletteGray44, VDSColor.paletteGray65, forKey: .gray) + config.setSurfaceColors(VDSColor.paletteGray85, VDSColor.paletteGray20, forKey: .grayLowContrast) + config.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryDark, forKey: .black) + config.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forKey: .white) + return config.eraseToAnyColorable() + }() + + private var textColorConfiguration = ViewColorConfiguration() + + public func updateTextColorConfig() { + textColorConfiguration.reset() + + switch fillColor { + + case .red, .black, .gray, .grayLowContrast: + textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: false) + textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOndark, forDisabled: true) + + case .yellow, .white: + textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: false) + textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forDisabled: true) + + case .orange, .green, .blue: + textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: false) + textColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forDisabled: true) + } + } + + //-------------------------------------------------- + // MARK: - State + //-------------------------------------------------- + open override func updateView() { + updateTextColorConfig() + + backgroundColor = backgroundColorConfiguration.getColor(self) + + label.useAttributedText = true + label.edgeInset = .init(top: 0, left: textSize.padding, bottom: 0, right: textSize.padding) + label.font = textSize.textStyle.font + label.textColor = textColorConfiguration.getColor(self) + label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() + label.text = getText() + label.surface = surface + label.disabled = disabled + label.sizeToFit() + setNeedsLayout() + layoutIfNeeded() + } + + private func getText() -> String { + let badgeCount = number ?? 0 + var text: String = "" + if kind == .numbered && badgeCount >= 0 { + let maxBadgetCount = limitDigits(number: badgeCount, maxDigits: maxDigits.value) + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + text = formatter.string(from: .init(integerLiteral: maxBadgetCount))! + if maxDigits.value < "\(badgeCount)".count { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + text = "\(text)+" + } + if let leadingCharacter { + text = "\(leadingCharacter)\(text)" + } else { + text = "\(text)" + } + } + return text + } + + private func limitDigits(number: Int, maxDigits: Int) -> Int { + let maxNumber = Int(pow(10.0, Double(maxDigits))) - 1 + return min(number, maxNumber) + } + + open override func layoutSubviews() { + super.layoutSubviews() + labelWidthConstraint?.constant = textSize.minimumSize + labelHeightConstraint?.constant = textSize.minimumSize + layer.cornerRadius = frame.size.height / 2 + + if hideBorder { + layer.borderWidth = 0 + } else { + layer.borderColor = borderColorConfiguration.getColor(surface).cgColor + layer.borderWidth = 1 + } + + layer.remove(layerName: "dot") + if kind == .simple && !hideDot { + let dotSize: CGFloat = bounds.width * 0.1875 + let dotLayer = CAShapeLayer() + dotLayer.name = "dot" + + let centerX = (bounds.width - dotSize) / 2.0 + let centerY = (bounds.width - dotSize) / 2.0 + + dotLayer.frame = .init(x: centerX, y: centerY, width: dotSize, height: dotSize) + dotLayer.path = UIBezierPath(ovalIn: .init(origin: .zero, size: .init(width: dotSize, height: dotSize))).cgPath + dotLayer.fillColor = textColorConfiguration.getColor(self).cgColor + + layer.addSublayer(dotLayer) + } + } +} + diff --git a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift index 57e5c7f6..12db5a56 100644 --- a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift +++ b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift @@ -172,13 +172,13 @@ open class ButtonIcon: Control { SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable() }() var shadowColorConfiguration: AnyColorable = { - SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable() + SurfaceColorConfiguration(VDSColor.paletteBlack, .clear).eraseToAnyColorable() }() - var shadowOpacity: CGFloat = 0.5 - var shadowOffset: CGSize = .init(width: 1, height: 1) + var shadowOpacity: CGFloat = 0.16 + var shadowOffset: CGSize = .init(width: 0, height: 2) var shadowRadius: CGFloat = 2 } - + private struct HighContrastConfiguration: Configuration { var kind: Kind = .highContrast var surfaceType: SurfaceType = .colorFill @@ -280,7 +280,6 @@ open class ButtonIcon: Control { } else { icon.reset() } - setNeedsLayout() } @@ -316,26 +315,15 @@ open class ButtonIcon: Control { if let borderable = currentConfig as? Borderable { layer.borderColor = borderable.borderColorConfiguration.getColor(self).cgColor layer.borderWidth = borderable.borderWidth - icon.layer.borderWidth = borderable.borderWidth } else { layer.borderColor = nil layer.borderWidth = 0 - icon.layer.borderWidth = 0 } if let dropshadowable = currentConfig as? Dropshadowable { - layer.masksToBounds = false - layer.shadowColor = dropshadowable.shadowColorConfiguration.getColor(self).cgColor - layer.shadowOpacity = Float(dropshadowable.shadowOpacity) - layer.shadowOffset = dropshadowable.shadowOffset - layer.shadowRadius = dropshadowable.shadowRadius - layer.shadowPath = UIBezierPath(rect: bounds).cgPath - layer.shouldRasterize = true - layer.rasterizationScale = UIScreen.main.scale + addDropShadow(config: dropshadowable) } else { - layer.shadowOpacity = 0 - layer.shadowRadius = 0 - layer.shadowPath = nil + removeDropShadow() } } @@ -360,6 +348,25 @@ extension ButtonIcon: AppleGuidlinesTouchable { } +extension UIView { + fileprivate func addDropShadow(config: Dropshadowable) { + layer.masksToBounds = false + layer.shadowColor = config.shadowColorConfiguration.getColor(self).cgColor + layer.shadowOpacity = Float(config.shadowOpacity) + layer.shadowOffset = config.shadowOffset + layer.shadowRadius = config.shadowRadius + layer.shouldRasterize = true + layer.rasterizationScale = UIScreen.main.scale + layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath + } + + fileprivate func removeDropShadow() { + layer.shadowOpacity = 0 + layer.shadowRadius = 0 + layer.shadowPath = nil + } +} + private protocol Borderable { var borderWidth: CGFloat { get set } var borderColorConfiguration: AnyColorable { get set } diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index c88ab853..c460530b 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -39,6 +39,14 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { open var userInfo = [String: Primitive]() + open var edgeInset: UIEdgeInsets = .zero { + didSet { + setNeedsUpdate() + + } + + } + override open var text: String? { didSet { attributes = nil @@ -192,7 +200,16 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { attributedString.addAttribute( .paragraphStyle, value: paragraph, range: entireRange) } } - + + open override func drawText(in rect: CGRect) { + super.drawText(in: rect.inset(by: edgeInset)) + } + + open override var intrinsicContentSize: CGSize { + let size = super.intrinsicContentSize + return CGSize(width: size.width + edgeInset.left + edgeInset.right, height: size.height + edgeInset.top + edgeInset.bottom) + } + //-------------------------------------------------- // MARK: - Actionable //-------------------------------------------------- diff --git a/VDS/Components/TitleLockup/TitleLockupTextStyle.swift b/VDS/Components/TitleLockup/TitleLockupTextStyle.swift index e8dc0319..a09491ae 100644 --- a/VDS/Components/TitleLockup/TitleLockupTextStyle.swift +++ b/VDS/Components/TitleLockup/TitleLockupTextStyle.swift @@ -11,7 +11,7 @@ extension TitleLockup { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- - public enum TitleTextStyle: String, EnumSubset { + public enum TitleTextStyle: String, CaseIterable { case featureMedium case boldFeatureMedium @@ -32,9 +32,14 @@ extension TitleLockup { case boldTitleSmall public var defaultValue: TextStyle {.boldFeatureXSmall } + + public var value: TextStyle { + TextStyle.textStyle(for: self.rawValue) ?? defaultValue + } + } - public enum OtherTextStyle: String, EnumSubset { + public enum OtherTextStyle: String, CaseIterable { case bodyLarge case boldBodyLarge case bodyMedium @@ -43,6 +48,9 @@ extension TitleLockup { case boldBodySmall public var defaultValue: TextStyle {.bodyLarge } + + public var value: TextStyle { + TextStyle.textStyle(for: self.rawValue) ?? defaultValue + } } - } diff --git a/VDS/Extensions/NSLayoutAnchor.swift b/VDS/Extensions/NSLayoutAnchor.swift new file mode 100644 index 00000000..21c526e6 --- /dev/null +++ b/VDS/Extensions/NSLayoutAnchor.swift @@ -0,0 +1,58 @@ +// +// NSLayoutAnchor.swift +// VDS +// +// Created by Matt Bruce on 6/15/23. +// + +import Foundation +import UIKit + +//-------------------------------------------------- +// MARK: - NSLayoutAnchor +//-------------------------------------------------- +extension NSLayoutAnchor { + // These methods return an inactive constraint of the form thisAnchor = otherAnchor. + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutAnchor, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(equalTo: anchor) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutAnchor, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(greaterThanOrEqualTo: anchor) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutAnchor, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(lessThanOrEqualTo: anchor) + constraint.identifier = identifier + return constraint + } + + + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutAnchor, constant: CGFloat, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(equalTo: anchor, constant: constant) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutAnchor, constant: CGFloat, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(greaterThanOrEqualTo: anchor, constant: constant) + constraint.identifier = identifier + return constraint + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutAnchor, constant: CGFloat, identifier: String) -> NSLayoutConstraint { + let constraint = self.constraint(lessThanOrEqualTo: anchor, constant: constant) + constraint.identifier = identifier + return constraint + } +} diff --git a/VDS/Extensions/NSLayoutDimension.swift b/VDS/Extensions/NSLayoutDimension.swift new file mode 100644 index 00000000..d8eacf79 --- /dev/null +++ b/VDS/Extensions/NSLayoutDimension.swift @@ -0,0 +1,84 @@ +// +// NSLayoutDimension.swift +// VDS +// +// Created by Matt Bruce on 6/15/23. +// + +import Foundation +import UIKit + +//-------------------------------------------------- +// MARK: - NSLayoutDimension +//-------------------------------------------------- +extension NSLayoutDimension { + // These methods return an inactive constraint of the form thisVariable = constant. + @discardableResult + @objc public func constraint(equalToConstant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(equalToConstant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualToConstant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(greaterThanOrEqualToConstant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(lessThanOrEqualToConstant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(lessThanOrEqualToConstant: c) + lc.identifier = identifier + return lc + } + + + // These methods return an inactive constraint of the form thisAnchor = otherAnchor * multiplier. + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutDimension, multiplier m: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(equalTo: anchor, multiplier: m) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(greaterThanOrEqualTo: anchor ,multiplier: m) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(lessThanOrEqualTo: anchor, multiplier: m) + lc.identifier = identifier + return lc + } + + + // These methods return an inactive constraint of the form thisAnchor = otherAnchor * multiplier + constant. + @discardableResult + @objc public func constraint(equalTo anchor: NSLayoutDimension, multiplier m: CGFloat, constant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(equalTo: anchor, multiplier: m, constant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(greaterThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, constant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(greaterThanOrEqualTo: anchor, multiplier: m, constant: c) + lc.identifier = identifier + return lc + } + + @discardableResult + @objc public func constraint(lessThanOrEqualTo anchor: NSLayoutDimension, multiplier m: CGFloat, constant c: CGFloat, identifier: String) -> NSLayoutConstraint { + let lc = constraint(lessThanOrEqualTo: anchor, multiplier: m, constant: c) + lc.identifier = identifier + return lc + } + +} + diff --git a/VDS/Extensions/UIColor+VDSColor.swift b/VDS/Extensions/UIColor+VDSColor.swift index ad05efc1..e06c4f50 100644 --- a/VDS/Extensions/UIColor+VDSColor.swift +++ b/VDS/Extensions/UIColor+VDSColor.swift @@ -119,4 +119,11 @@ extension UIColor { guard let _ = found else { return false} return true } + + public func toVDSColor() -> VDSColor? { + guard let hex = hexString else { return nil } + let found = VDSColor.allCases.first{ $0.uiColor.hexString == hex } + guard let found else { return nil} + return found + } } diff --git a/VDS/Extensions/UIView+CALayer.swift b/VDS/Extensions/UIView+CALayer.swift new file mode 100644 index 00000000..2ad15363 --- /dev/null +++ b/VDS/Extensions/UIView+CALayer.swift @@ -0,0 +1,140 @@ +// +// UIView+CALayer.swift +// VDS +// +// Created by Matt Bruce on 6/15/23. +// + +import Foundation +import UIKit +import VDSFormControlsTokens + +//-------------------------------------------------- +// MARK: - Debug Borders +//-------------------------------------------------- +extension UIView { + + internal func removeDebugBorder() { + layer.remove(layerName: "debug") + } + + internal func addDebugBorder(color: UIColor = .red) { + //ensure you remove existing + removeDebugBorder() + + //add bounds border + let borderLayer = CALayer() + borderLayer.name = "debugAreaLayer" + borderLayer.frame = bounds + borderLayer.bounds = bounds + borderLayer.borderWidth = VDSFormControls.widthBorder + borderLayer.borderColor = color.cgColor + layer.addSublayer(borderLayer) + + //add touchborder if applicable + if type(of: self) is AppleGuidlinesTouchable.Type { + let faultToleranceX: CGFloat = max((45 - bounds.size.width) / 2.0, 0) + let faultToleranceY: CGFloat = max((45 - bounds.size.height) / 2.0, 0) + + let touchableAreaPath = UIBezierPath(rect: bounds.insetBy(dx: -faultToleranceX, dy: -faultToleranceY)) + let touchLayer = CAShapeLayer() + touchLayer.path = touchableAreaPath.cgPath + touchLayer.strokeColor = color.cgColor + touchLayer.fillColor = UIColor.clear.cgColor + touchLayer.lineWidth = VDSFormControls.widthBorder + touchLayer.opacity = 1.0 + touchLayer.name = "debugTouchableAreaLayer" + touchLayer.zPosition = 100 + touchLayer.frame = bounds + touchLayer.bounds = bounds + layer.addSublayer(touchLayer) + } + } + + public var hasDebugBorder: Bool { + guard let layers = layer.sublayers else { return false } + return layers.compactMap{$0.name}.filter{$0.hasPrefix("debug")}.count > 0 + } + + public func debugBorder(show shouldShow: Bool = true, color: UIColor = .red) { + if shouldShow { + addDebugBorder(color: color) + } else { + removeDebugBorder() + } + if let view = self as? Handlerable { + view.updateView() + } + } +} + +//-------------------------------------------------- +// MARK: - CALayer +//-------------------------------------------------- +extension CALayer { + func remove(layerName: String) { + guard let sublayers = sublayers else { + return + } + + sublayers.forEach({ layer in + if layer.name?.hasPrefix(layerName) ?? false { + layer.removeFromSuperlayer() + } + }) + } +} + +//-------------------------------------------------- +// MARK: - Borders +//-------------------------------------------------- +extension UIView { + + public func addBorder(side: UIRectEdge, width: CGFloat, color: UIColor, offset: CGFloat = 0) { + let layerName = borderLayerName(for: side) + layer.remove(layerName: layerName) + + let borderLayer = CALayer() + borderLayer.backgroundColor = color.cgColor + borderLayer.name = layerName + + switch side { + case .left: + borderLayer.frame = CGRect(x: 0, y: 0, width: width, height: frame.height) + case .right: + borderLayer.frame = CGRect(x: frame.width - width - offset, y: 0, width: width, height: frame.height) + case .top: + borderLayer.frame = CGRect(x: 0, y: 0, width: frame.width, height: width) + case .bottom: + borderLayer.frame = CGRect(x: 0, y: frame.height - width - offset, width: frame.width, height: width) + default: + break + } + + layer.addSublayer(borderLayer) + } + + public func removeBorders() { + layer.borderWidth = 0 + layer.borderColor = nil + layer.remove(layerName: borderLayerName(for: .top)) + layer.remove(layerName: borderLayerName(for: .left)) + layer.remove(layerName: borderLayerName(for: .right)) + layer.remove(layerName: borderLayerName(for: .bottom)) + } + + private func borderLayerName(for side: UIRectEdge) -> String { + switch side { + case .left: + return "leftBorderLayer" + case .right: + return "rightBorderLayer" + case .top: + return "topBorderLayer" + case .bottom: + return "bottomBorderLayer" + default: + return "" + } + } +} diff --git a/VDS/Extensions/UIView.swift b/VDS/Extensions/UIView.swift index 327849fc..517ef104 100644 --- a/VDS/Extensions/UIView.swift +++ b/VDS/Extensions/UIView.swift @@ -10,56 +10,118 @@ import UIKit import VDSFormControlsTokens extension UIView { - public func pin(_ view: UIView, with edges: UIEdgeInsets = UIEdgeInsets.zero) { - leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: edges.left).isActive = true - trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -edges.right).isActive = true - topAnchor.constraint(equalTo: view.topAnchor, constant: edges.top).isActive = true - bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -edges.bottom).isActive = true + + public func constraint(with identifier: String) -> NSLayoutConstraint? { + return constraints.first { $0.identifier == identifier } } - - public func pinToSuperView(_ edges: UIEdgeInsets = UIEdgeInsets.zero) { +} + +//-------------------------------------------------- +// MARK: - Pinning +//-------------------------------------------------- +extension UIView { + @discardableResult + public func pin(_ view: UIView, with edges: UIEdgeInsets = UIEdgeInsets.zero) -> Self { + pinLeading(view.leadingAnchor, edges.left) + pinTrailing(view.trailingAnchor, edges.right) + pinTop(view.topAnchor, edges.top) + pinBottom(view.bottomAnchor, edges.bottom) + return self + } + + @discardableResult + public func pinToSuperView(_ edges: UIEdgeInsets = UIEdgeInsets.zero) -> Self { if let superview { pin(superview, with: edges) } + return self } +} + +//-------------------------------------------------- +// MARK: - HeightAnchor +//-------------------------------------------------- +extension UIView { @discardableResult public func height(_ constant: CGFloat) -> Self { - heightAnchor.constraint(equalToConstant: constant).isActive = true + height(constant: constant) return self } @discardableResult - public func heightGreaterThanEqual(_ constant: CGFloat) -> Self { - heightAnchor.constraint(greaterThanOrEqualToConstant: constant).isActive = true + public func heightGreaterThanEqualTo(_ constant: CGFloat) -> Self { + heightGreaterThanEqualTo(constant: constant) return self } - - @discardableResult - public func heightLessThanEqual(_ constant: CGFloat) -> Self { - heightAnchor.constraint(lessThanOrEqualToConstant: constant).isActive = true - return self - } - - @discardableResult - public func width(_ constant: CGFloat) -> Self { - widthAnchor.constraint(equalToConstant: constant).isActive = true - return self - } - @discardableResult - public func widthGreaterThanEqual(_ constant: CGFloat) -> Self { - widthAnchor.constraint(greaterThanOrEqualToConstant: constant).isActive = true + public func heightLessThanEqualTo(_ constant: CGFloat) -> Self { + heightLessThanEqualTo(constant: constant) return self } + + @discardableResult + public func height(constant: CGFloat) -> NSLayoutConstraint { + heightAnchor.constraint(equalToConstant: constant).activate() + } @discardableResult - public func widthLessThanEqual(_ constant: CGFloat) -> Self { - widthAnchor.constraint(lessThanOrEqualToConstant: constant).isActive = true + public func heightGreaterThanEqualTo(constant: CGFloat) -> NSLayoutConstraint { + heightAnchor.constraint(greaterThanOrEqualToConstant: constant).activate() + } + + @discardableResult + public func heightLessThanEqualTo(constant: CGFloat) -> NSLayoutConstraint { + heightAnchor.constraint(lessThanOrEqualToConstant: constant).activate() + } + +} + +//-------------------------------------------------- +// MARK: - WidthAnchor +//-------------------------------------------------- +extension UIView { + + @discardableResult + public func width(_ constant: CGFloat) -> Self { + width(constant: constant) return self } + + @discardableResult + public func widthGreaterThanEqualTo(_ constant: CGFloat) -> Self { + widthGreaterThanEqualTo(constant: constant) + return self + } + + @discardableResult + public func widthLessThanEqualTo(_ constant: CGFloat) -> Self { + widthLessThanEqualTo(constant: constant) + return self + } + + @discardableResult + public func width(constant: CGFloat) -> NSLayoutConstraint { + widthAnchor.constraint(equalToConstant: constant).activate() + } + + @discardableResult + public func widthGreaterThanEqualTo(constant: CGFloat) -> NSLayoutConstraint { + widthAnchor.constraint(greaterThanOrEqualToConstant: constant).activate() + } + + @discardableResult + public func widthLessThanEqualTo(constant: CGFloat) -> NSLayoutConstraint { + widthAnchor.constraint(lessThanOrEqualToConstant: constant).activate() + } +} +//-------------------------------------------------- +// MARK: - TopAnchor +//-------------------------------------------------- +extension UIView { + @discardableResult public func pinTop(_ constant: CGFloat = 0.0) -> Self { return pinTop(nil, constant) @@ -67,13 +129,49 @@ extension UIView { @discardableResult public func pinTop(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { - let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor - if let found { - topAnchor.constraint(equalTo: found, constant: constant).isActive = true - } + pinTop(anchor: anchor, constant: constant) return self } + + @discardableResult + public func pinTopLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinBottomLessThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinTopGreaterThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinTopGreaterThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinTop(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor + guard let found else { return nil } + return topAnchor.constraint(equalTo: found, constant: constant).activate() + } + + @discardableResult + public func pinTopLessThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor + guard let found else { return nil } + return topAnchor.constraint(lessThanOrEqualTo: found, constant: constant).activate() + } + + @discardableResult + public func pinTopGreaterThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.topAnchor + guard let found else { return nil } + return topAnchor.constraint(greaterThanOrEqualTo: found, constant: constant).activate() + } + +} +//-------------------------------------------------- +// MARK: - BottomAnchor +//-------------------------------------------------- +extension UIView { @discardableResult public func pinBottom(_ constant: CGFloat = 0.0) -> Self { return pinBottom(nil, constant) @@ -81,161 +179,167 @@ extension UIView { @discardableResult public func pinBottom(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { - let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor - if let found { - bottomAnchor.constraint(equalTo: found, constant: -constant).isActive = true - } + pinBottom(anchor: anchor, constant: constant) return self } + @discardableResult + public func pinBottomLessThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinBottomLessThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinBottomGreaterThanOrEqualTo(_ anchor: NSLayoutYAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinBottomGreaterThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinBottom(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor + guard let found else { return nil } + return bottomAnchor.constraint(equalTo: found, constant: -constant).activate() + } + + @discardableResult + public func pinBottomLessThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor + guard let found else { return nil } + return bottomAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).activate() + } + + @discardableResult + public func pinBottomGreaterThanOrEqualTo(anchor: NSLayoutYAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutYAxisAnchor? = anchor ?? superview?.bottomAnchor + guard let found else { return nil } + return bottomAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).activate() + } +} + +//-------------------------------------------------- +// MARK: - LeadingAnchor +//-------------------------------------------------- +extension UIView { + @discardableResult public func pinLeading(_ constant: CGFloat = 0.0) -> Self { return pinLeading(nil, constant) } - + @discardableResult public func pinLeading(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { - let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor - if let found { - leadingAnchor.constraint(equalTo: found, constant: constant).isActive = true - } + pinLeading(anchor: anchor, constant: constant) return self } + + @discardableResult + public func pinLeadingLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinLeadingLessThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinLeadingGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinLeadingGreaterThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinLeading(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor + guard let found else { return nil } + return leadingAnchor.constraint(equalTo: found, constant: constant).activate() + } + + @discardableResult + public func pinLeadingLessThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor + guard let found else { return nil } + return leadingAnchor.constraint(lessThanOrEqualTo: found, constant: constant).activate() + } + + @discardableResult + public func pinLeadingGreaterThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.leadingAnchor + guard let found else { return nil } + return leadingAnchor.constraint(greaterThanOrEqualTo: found, constant: constant).activate() + } +} +//-------------------------------------------------- +// MARK: - TrailingAnchor +//-------------------------------------------------- +extension UIView { + @discardableResult public func pinTrailing(_ constant: CGFloat = 0.0) -> Self { - return pinTrailing(nil, constant) + pinTrailing(nil, constant) } @discardableResult public func pinTrailing(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { - let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor - if let found { - trailingAnchor.constraint(equalTo: found, constant: -constant).isActive = true - } + pinTrailing(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinTrailingLessThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinTrailingLessThanOrEqualTo(anchor: anchor, constant: constant) return self } -} + @discardableResult + public func pinTrailingGreaterThanOrEqualTo(_ anchor: NSLayoutXAxisAnchor? = nil, _ constant: CGFloat = 0.0) -> Self { + pinTrailingGreaterThanOrEqualTo(anchor: anchor, constant: constant) + return self + } + + @discardableResult + public func pinTrailing(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor + guard let found else { return nil } + return trailingAnchor.constraint(equalTo: found, constant: -constant).activate() + } + + @discardableResult + public func pinTrailingLessThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor + guard let found else { return nil } + return trailingAnchor.constraint(lessThanOrEqualTo: found, constant: -constant).activate() + } -extension UIView { - - internal func removeDebugBorder() { - layer.remove(layerName: "debug") - } - - internal func addDebugBorder(color: UIColor = .red) { - //ensure you remove existing - removeDebugBorder() - - //add bounds border - let borderLayer = CALayer() - borderLayer.name = "debugAreaLayer" - borderLayer.frame = bounds - borderLayer.bounds = bounds - borderLayer.borderWidth = VDSFormControls.widthBorder - borderLayer.borderColor = color.cgColor - layer.addSublayer(borderLayer) - - //add touchborder if applicable - if type(of: self) is AppleGuidlinesTouchable.Type { - let faultToleranceX: CGFloat = max((45 - bounds.size.width) / 2.0, 0) - let faultToleranceY: CGFloat = max((45 - bounds.size.height) / 2.0, 0) - - let touchableAreaPath = UIBezierPath(rect: bounds.insetBy(dx: -faultToleranceX, dy: -faultToleranceY)) - let touchLayer = CAShapeLayer() - touchLayer.path = touchableAreaPath.cgPath - touchLayer.strokeColor = color.cgColor - touchLayer.fillColor = UIColor.clear.cgColor - touchLayer.lineWidth = VDSFormControls.widthBorder - touchLayer.opacity = 1.0 - touchLayer.name = "debugTouchableAreaLayer" - touchLayer.zPosition = 100 - touchLayer.frame = bounds - touchLayer.bounds = bounds - layer.addSublayer(touchLayer) - } - } - - public var hasDebugBorder: Bool { - guard let layers = layer.sublayers else { return false } - return layers.compactMap{$0.name}.filter{$0.hasPrefix("debug")}.count > 0 - } - - public func debugBorder(show shouldShow: Bool = true, color: UIColor = .red) { - if shouldShow { - addDebugBorder(color: color) - } else { - removeDebugBorder() - } - if let view = self as? Handlerable { - view.updateView() - } + @discardableResult + public func pinTrailingGreaterThanOrEqualTo(anchor: NSLayoutXAxisAnchor?, constant: CGFloat = 0.0) -> NSLayoutConstraint? { + let found: NSLayoutXAxisAnchor? = anchor ?? superview?.trailingAnchor + guard let found else { return nil } + return trailingAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).activate() } } -extension CALayer { - func remove(layerName: String) { - guard let sublayers = sublayers else { - return - } - - sublayers.forEach({ layer in - if layer.name?.hasPrefix(layerName) ?? false { - layer.removeFromSuperlayer() - } - }) - } -} - - -extension UIView { +extension NSLayoutConstraint { - public func addBorder(side: UIRectEdge, width: CGFloat, color: UIColor, offset: CGFloat = 0) { - let layerName = borderLayerName(for: side) - layer.remove(layerName: layerName) - - let borderLayer = CALayer() - borderLayer.backgroundColor = color.cgColor - borderLayer.name = layerName - - switch side { - case .left: - borderLayer.frame = CGRect(x: 0, y: 0, width: width, height: frame.height) - case .right: - borderLayer.frame = CGRect(x: frame.width - width - offset, y: 0, width: width, height: frame.height) - case .top: - borderLayer.frame = CGRect(x: 0, y: 0, width: frame.width, height: width) - case .bottom: - borderLayer.frame = CGRect(x: 0, y: frame.height - width - offset, width: frame.width, height: width) - default: - break - } - - layer.addSublayer(borderLayer) - } - - public func removeBorders() { - layer.borderWidth = 0 - layer.borderColor = nil - layer.remove(layerName: borderLayerName(for: .top)) - layer.remove(layerName: borderLayerName(for: .left)) - layer.remove(layerName: borderLayerName(for: .right)) - layer.remove(layerName: borderLayerName(for: .bottom)) + @discardableResult + public func activate() -> Self{ + isActive = true + return self } - private func borderLayerName(for side: UIRectEdge) -> String { - switch side { - case .left: - return "leftBorderLayer" - case .right: - return "rightBorderLayer" - case .top: - return "topBorderLayer" - case .bottom: - return "bottomBorderLayer" - default: - return "" - } + @discardableResult + public func deactivate() -> Self{ + isActive = false + return self } + + public class Container { + public var topConstraint: NSLayoutConstraint? + public var leadingConstraint: NSLayoutConstraint? + public var trailingConstraint: NSLayoutConstraint? + public var bottomConstraint: NSLayoutConstraint? + public var widthConstraint: NSLayoutConstraint? + public var heightConstraint: NSLayoutConstraint? + + public init(){} + } + } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/amex.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/amex.imageset/Contents.json index 610ffbd7..d806b247 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/amex.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/amex.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/credit-card.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/credit-card.imageset/Contents.json index df2d7bca..24a71037 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/credit-card.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/credit-card.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json index b4949ace..01c460d2 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub.imageset/Contents.json index 6a22c2dd..32adfd39 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/discover.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/discover.imageset/Contents.json index ae99049b..28cfbca3 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/discover.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/discover.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json index 49581273..28c8b212 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/Contents.json index b0026068..a647b1a8 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/jcb.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/jcb.imageset/Contents.json index 5798433a..8a4c248a 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/jcb.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/jcb.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/mastercard.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/mastercard.imageset/Contents.json index 3ee10a5c..e5a22fd7 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/mastercard.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/mastercard.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-inverted.imageset/Contents.json index caf02da3..5b516479 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-inverted.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json index d039c34d..9b2b69ae 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode-inverted.imageset/Contents.json index 2c8a3508..248b9279 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode-inverted.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode.imageset/Contents.json index 21980ced..b7838a59 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCode.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex-inverted.imageset/Contents.json index 4bc9d730..39edd17c 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex-inverted.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex.imageset/Contents.json index c4dc8123..24cb9017 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/securityCodeAmex.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa-inverted.imageset/Contents.json index f1e909a2..8f71dfc4 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa-inverted.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa.imageset/Contents.json index 680fa238..87260339 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/visa.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt-bold.imageset/Contents.json index efc51701..f2f866f7 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt.imageset/Contents.json index 50ccb6e9..cae26f91 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-alt.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-bold.imageset/Contents.json index 14a052ea..4dab2e00 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark.imageset/Contents.json index 6b69c1c8..bb8da90b 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/checkmark.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/close-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/close-bold.imageset/Contents.json index f7e08c7b..5fa7f126 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/close-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/close-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/close.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/close.imageset/Contents.json index 308f9c8f..d6edae4a 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/close.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/close.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret-bold.imageset/Contents.json index 97881155..752ccaed 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret.imageset/Contents.json index fca125b2..1376a186 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/down-caret.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/error-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/error-bold.imageset/Contents.json index 90962db0..1abe2a2d 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/error-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/error-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/error.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/error.imageset/Contents.json index a2f87247..bd94a6e4 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/error.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/error.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/info-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/info-bold.imageset/Contents.json index ffefe268..4dee1808 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/info-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/info-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/info.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/info.imageset/Contents.json index adcd08a2..32919952 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/info.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/info.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/left-arrow.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/left-arrow.imageset/Contents.json index 5b9739ba..1496eefd 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/left-arrow.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/left-arrow.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret-bold.imageset/Contents.json index 1e4c1c7e..d8517e5a 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret.imageset/Contents.json index 7dcf67e0..388b021c 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/left-caret.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/multiple-documents.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/multiple-documents.imageset/Contents.json index a8edc344..a32733a4 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/multiple-documents.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/multiple-documents.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-arrow.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-arrow.imageset/Contents.json index 84274b84..6944ac1a 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-arrow.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-arrow.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-caret.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-caret.imageset/Contents.json index b7e824be..6fd7077b 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-caret.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-left-caret.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-arrow.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-arrow.imageset/Contents.json index 67994793..82073c3d 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-arrow.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-arrow.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-caret.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-caret.imageset/Contents.json index ac5bf0f8..f13140e6 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-caret.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-right-caret.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/right-arrow.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/right-arrow.imageset/Contents.json index de24a6e9..36cbff60 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/right-arrow.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/right-arrow.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret-bold.imageset/Contents.json index 9c8e052c..e57ccc0d 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret.imageset/Contents.json index 616090e3..1e745e81 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/right-caret.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/warning-bold.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/warning-bold.imageset/Contents.json index 079e7cc3..55dd1b81 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/warning-bold.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/warning-bold.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/warning.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/warning.imageset/Contents.json index b9aa7c20..86da0a6d 100644 --- a/VDS/SupportingFiles/Icons.xcassets/Restricted/warning.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/warning.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/VDS/Typography/Typography.swift b/VDS/Typography/Typography.swift index 0df38e84..8d815ad3 100644 --- a/VDS/Typography/Typography.swift +++ b/VDS/Typography/Typography.swift @@ -23,45 +23,230 @@ public enum TextPosition: String, CaseIterable { } } -public enum TextStyle: String, CaseIterable { +public struct TextStyle: Equatable { + public let rawValue: String + public let pointSize: CGFloat + public let lineHeight: CGFloat + public let letterSpacing: CGFloat + public let fontFace: Fonts - case featureXLarge - case boldFeatureXLarge - case featureLarge - case boldFeatureLarge - 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 - - case bodyLarge - case boldBodyLarge - case bodyMedium - case boldBodyMedium - case bodySmall - case boldBodySmall - - case micro - case boldMicro - - public static var defaultStyle: TextStyle { - return .bodyLarge + public init(rawValue: String, fontFace: Fonts, pointSize: CGFloat, lineHeight: CGFloat, letterSpacing: CGFloat) { + self.rawValue = rawValue + self.fontFace = fontFace + self.pointSize = pointSize + self.lineHeight = lineHeight + self.letterSpacing = letterSpacing } } +//MARK: Definitions +extension TextStyle { + + // Static properties for different text styles + public static let featureXLarge = TextStyle(rawValue: "featureXLarge", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature144 : VDSTypography.fontSizeFeature96, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature136 : VDSTypography.lineHeightFeature88, + letterSpacing: 0.25) + + public static let boldFeatureXLarge = TextStyle(rawValue: "boldFeatureXLarge", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature144 : VDSTypography.fontSizeFeature96, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature136 : VDSTypography.lineHeightFeature88, + letterSpacing: 0.25) + + public static let featureLarge = TextStyle(rawValue: "featureLarge", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature128 : VDSTypography.fontSizeFeature80, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature120 : VDSTypography.lineHeightFeature76, + letterSpacing: 0.25) + + public static let boldFeatureLarge = TextStyle(rawValue: "boldFeatureLarge", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature128 : VDSTypography.fontSizeFeature80, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature120 : VDSTypography.lineHeightFeature76, + letterSpacing: 0.25) + + public static let featureMedium = TextStyle(rawValue: "featureMedium", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature96 : VDSTypography.fontSizeFeature64, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature88 : VDSTypography.lineHeightFeature64, + letterSpacing: 0.25) + + public static let boldFeatureMedium = TextStyle(rawValue: "boldFeatureMedium", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature96 : VDSTypography.fontSizeFeature64, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature88 : VDSTypography.lineHeightFeature64, + letterSpacing: 0.25) + + public static let featureSmall = TextStyle(rawValue: "featureSmall", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature80 : VDSTypography.fontSizeFeature48, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature76 : VDSTypography.lineHeightFeature48, + letterSpacing: 0.25) + + public static let boldFeatureSmall = TextStyle(rawValue: "boldFeatureSmall", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature80 : VDSTypography.fontSizeFeature48, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature76 : VDSTypography.lineHeightFeature48, + letterSpacing: 0.25) + + public static let featureXSmall = TextStyle(rawValue: "featureXSmall", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature64 : VDSTypography.fontSizeFeature40, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature64 : VDSTypography.lineHeightFeature40, + letterSpacing: 0.25) + + public static let boldFeatureXSmall = TextStyle(rawValue: "boldFeatureXSmall", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeFeature64 : VDSTypography.fontSizeFeature40, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightFeature64 : VDSTypography.lineHeightFeature40, + letterSpacing: 0.25) + + public static let title2XLarge = TextStyle(rawValue: "title2XLarge", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle64 : VDSTypography.fontSizeTitle40, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle64 : VDSTypography.lineHeightTitle40, + letterSpacing: 0.25) + + public static let boldTitle2XLarge = TextStyle(rawValue: "boldTitle2XLarge", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle64 : VDSTypography.fontSizeTitle40, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle64 : VDSTypography.lineHeightTitle40, + letterSpacing: 0.25) + + public static let titleXLarge = TextStyle(rawValue: "titleXLarge", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle48 : VDSTypography.fontSizeTitle32, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle48 : VDSTypography.lineHeightTitle36, + letterSpacing: 0.25) + + public static let boldTitleXLarge = TextStyle(rawValue: "boldTitleXLarge", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle48 : VDSTypography.fontSizeTitle32, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle48 : VDSTypography.lineHeightTitle36, + letterSpacing: 0.25) + + public static let titleLarge = TextStyle(rawValue: "titleLarge", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle32 : VDSTypography.fontSizeTitle24, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle36 : VDSTypography.lineHeightTitle28, + letterSpacing: 0.25) + + public static let boldTitleLarge = TextStyle(rawValue: "boldTitleLarge", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle32 : VDSTypography.fontSizeTitle24, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle36 : VDSTypography.lineHeightTitle28, + letterSpacing: 0.25) + + public static let titleMedium = TextStyle(rawValue: "titleMedium", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle24 : VDSTypography.fontSizeTitle20, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle28 : VDSTypography.lineHeightTitle24, + letterSpacing: 0.25) + + public static let boldTitleMedium = TextStyle(rawValue: "boldTitleMedium", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle24 : VDSTypography.fontSizeTitle20, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle28 : VDSTypography.lineHeightTitle24, + letterSpacing: 0.25) + + public static let titleSmall = TextStyle(rawValue: "titleSmall", + fontFace: .dsLight, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle20 : VDSTypography.fontSizeTitle16, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle24 : VDSTypography.lineHeightTitle20, + letterSpacing: 0.25) + + public static let boldTitleSmall = TextStyle(rawValue: "boldTitleSmall", + fontFace: .dsBold, + pointSize: UIDevice.isIPad ? VDSTypography.fontSizeTitle20 : VDSTypography.fontSizeTitle16, + lineHeight: UIDevice.isIPad ? VDSTypography.lineHeightTitle24 : VDSTypography.lineHeightTitle20, + letterSpacing: 0.25) + + public static let bodyLarge = TextStyle(rawValue: "bodyLarge", + fontFace: .dsRegular, + pointSize: VDSTypography.fontSizeBody16, + lineHeight: VDSTypography.lineHeightBody20, + letterSpacing: 0.5) + + public static let boldBodyLarge = TextStyle(rawValue: "boldBodyLarge", + fontFace: .dsBold, + pointSize: VDSTypography.fontSizeBody16, + lineHeight: VDSTypography.lineHeightBody20, + letterSpacing: 0.5) + + public static let bodyMedium = TextStyle(rawValue: "bodyMedium", + fontFace: .dsRegular, + pointSize: VDSTypography.fontSizeBody14, + lineHeight: VDSTypography.lineHeightBody18, + letterSpacing: 0.5) + + public static let boldBodyMedium = TextStyle(rawValue: "boldBodyMedium", + fontFace: .dsBold, + pointSize: VDSTypography.fontSizeBody14, + lineHeight: VDSTypography.lineHeightBody18, + letterSpacing: 0.5) + + public static let bodySmall = TextStyle(rawValue: "bodySmall", + fontFace: .dsRegular, + pointSize: VDSTypography.fontSizeBody12, + lineHeight: VDSTypography.lineHeightBody16, + letterSpacing: 0.25) + + public static let boldBodySmall = TextStyle(rawValue: "boldBodySmall", + fontFace: .dsBold, + pointSize: VDSTypography.fontSizeBody12, + lineHeight: VDSTypography.lineHeightBody16, + letterSpacing: 0.5) + + public static let micro = TextStyle(rawValue: "micro", + fontFace: .dsRegular, + pointSize: VDSTypography.fontSizeMicro11, + lineHeight: VDSTypography.lineHeightMicro16, + letterSpacing: 0.25) + + public static let boldMicro = TextStyle(rawValue: "boldMicro", + fontFace: .dsBold, + pointSize: VDSTypography.fontSizeMicro11, + lineHeight: VDSTypography.lineHeightMicro16, + letterSpacing: 0.5) + + public static var allCases: [TextStyle] { + return [ + featureXLarge, + boldFeatureXLarge, + featureLarge, + boldFeatureLarge, + featureMedium, + boldFeatureMedium, + featureSmall, + boldFeatureSmall, + featureXSmall, + boldFeatureXSmall, + title2XLarge, + boldTitle2XLarge, + titleXLarge, + boldTitleXLarge, + titleLarge, + boldTitleLarge, + titleMedium, + boldTitleMedium, + titleSmall, + boldTitleSmall, + bodyLarge, + boldBodyLarge, + bodyMedium, + boldBodyMedium, + bodySmall, + boldBodySmall, + micro, + boldMicro + ] + } + +} + + //MARK: FontCategory extension TextStyle { public enum FontCategory: String, CaseIterable { @@ -91,7 +276,8 @@ extension TextStyle { } else { styleName = "\(rawValue)\(fontSize?.rawValue ?? "")" } - guard let style = TextStyle(rawValue: styleName) else { + + guard let style = TextStyle.textStyle(for: styleName) else { return nil } return style @@ -111,102 +297,6 @@ extension TextStyle { } } -//MARK: PointSize -extension TextStyle { - public var pointSize: CGFloat { - switch self { - case .featureXLarge, .boldFeatureXLarge: - return UIDevice.isIPad ? VDSTypography.fontSizeFeature144 : VDSTypography.fontSizeFeature96 - case .featureLarge, .boldFeatureLarge: - return UIDevice.isIPad ? VDSTypography.fontSizeFeature128 : VDSTypography.fontSizeFeature80 - case .featureMedium, .boldFeatureMedium: - return UIDevice.isIPad ? VDSTypography.fontSizeFeature96 : VDSTypography.fontSizeFeature64 - case .featureSmall, .boldFeatureSmall: - return UIDevice.isIPad ? VDSTypography.fontSizeFeature80 : VDSTypography.fontSizeFeature48 - case .featureXSmall, .boldFeatureXSmall: - return UIDevice.isIPad ? VDSTypography.fontSizeFeature64 : VDSTypography.fontSizeFeature40 - case .title2XLarge, .boldTitle2XLarge: - return UIDevice.isIPad ? VDSTypography.fontSizeTitle64 : VDSTypography.fontSizeTitle40 - case .titleXLarge, .boldTitleXLarge: - return UIDevice.isIPad ? VDSTypography.fontSizeTitle48 : VDSTypography.fontSizeTitle32 - case .titleLarge, .boldTitleLarge: - return UIDevice.isIPad ? VDSTypography.fontSizeTitle32 : VDSTypography.fontSizeTitle24 - case .titleMedium, .boldTitleMedium: - return UIDevice.isIPad ? VDSTypography.fontSizeTitle24 : VDSTypography.fontSizeTitle20 - case .titleSmall, .boldTitleSmall: - return UIDevice.isIPad ? VDSTypography.fontSizeTitle20 : VDSTypography.fontSizeTitle16 - case .bodyLarge, .boldBodyLarge: - return VDSTypography.fontSizeBody16 - case .bodyMedium, .boldBodyMedium: - return VDSTypography.fontSizeBody14 - case .bodySmall, .boldBodySmall: - return VDSTypography.fontSizeBody12 - case .micro, .boldMicro: - return VDSTypography.fontSizeMicro11 - } - } -} - -//MARK: LineHeight -extension TextStyle { - public var lineHeight: CGFloat { - switch self { - case .featureXLarge, .boldFeatureXLarge: - return UIDevice.isIPad ? VDSTypography.lineHeightFeature136 : VDSTypography.lineHeightFeature88 - case .featureLarge, .boldFeatureLarge: - return UIDevice.isIPad ? VDSTypography.lineHeightFeature120 : VDSTypography.lineHeightFeature76 - case .featureMedium, .boldFeatureMedium: - return UIDevice.isIPad ? VDSTypography.lineHeightFeature88 : VDSTypography.lineHeightFeature64 - case .featureSmall, .boldFeatureSmall: - return UIDevice.isIPad ? VDSTypography.lineHeightFeature76 : VDSTypography.lineHeightFeature48 - case .featureXSmall, .boldFeatureXSmall: - return UIDevice.isIPad ? VDSTypography.lineHeightFeature64 : VDSTypography.lineHeightFeature40 - case .title2XLarge, .boldTitle2XLarge: - return UIDevice.isIPad ? VDSTypography.lineHeightTitle64 : VDSTypography.lineHeightTitle40 - case .titleXLarge, .boldTitleXLarge: - return UIDevice.isIPad ? VDSTypography.lineHeightTitle48 : VDSTypography.lineHeightTitle36 - case .titleLarge, .boldTitleLarge: - return UIDevice.isIPad ? VDSTypography.lineHeightTitle36 : VDSTypography.lineHeightTitle28 - case .titleMedium, .boldTitleMedium: - return UIDevice.isIPad ? VDSTypography.lineHeightTitle28 : VDSTypography.lineHeightTitle24 - case .titleSmall, .boldTitleSmall: - return UIDevice.isIPad ? VDSTypography.lineHeightTitle24 : VDSTypography.lineHeightTitle20 - case .bodyLarge, .boldBodyLarge: - return VDSTypography.lineHeightBody20 - case .bodyMedium, .boldBodyMedium: - return VDSTypography.lineHeightBody18 - case .bodySmall, .boldBodySmall: - return VDSTypography.lineHeightBody16 - case .micro, .boldMicro: - return VDSTypography.lineHeightMicro16 - } - } -} - -//MARK: LetterSpacing -extension TextStyle { - public var letterSpacing: CGFloat { - switch self { - case .featureXLarge, - .featureLarge, - .featureMedium, - .featureSmall, - .featureXSmall, - .title2XLarge, - .titleXLarge, - .titleLarge: - return 0.25 - - case .boldBodyLarge, .bodyLarge, - .boldBodyMedium, .bodyMedium: - return 0.5 - - default: - return 0.0 - } - } -} - //MARK: Alignments extension TextStyle { public var aligments: [TextPosition] { @@ -216,55 +306,9 @@ extension TextStyle { //MARK: Fonts extension TextStyle { - public var fontFace: Fonts { - switch self { - case .boldFeatureXLarge, - .boldFeatureLarge, - .boldFeatureMedium, - .boldFeatureSmall, - .boldFeatureXSmall, - .boldTitle2XLarge, - .boldTitleXLarge, - .boldTitleLarge, - .boldTitleMedium, - .boldTitleSmall, - .boldBodyLarge, - .boldBodyMedium: - return .dsBold - - case .featureXLarge, - .featureLarge, - .featureMedium, - .featureSmall, - .featureXSmall, - .title2XLarge, - .titleXLarge: - return .dsLight - - case .titleLarge, - .titleMedium, - .titleSmall, - .bodyLarge, - .bodyMedium: - return .dsRegular - - case .boldBodySmall, - .boldMicro: - return .txBold - - case .bodySmall, - .micro: - return .txRegular - } - } - public var font: UIFont { return fontFace.font(ofSize: pointSize) } - - public var superScriptFont: UIFont { - return fontFace.font(ofSize: pointSize / 2) - } } extension TextStyle { @@ -335,3 +379,12 @@ extension TextStyle: CustomDebugStringConvertible { "Name: \(self.rawValue) FontFace: \(font.fontName) FontWeight: \(self.rawValue.hasPrefix("bold") ? "bold" : "normal") PointSize: \(font.pointSize) LetterSpacing: \(letterSpacing) LineHeight: \(lineHeight)" } } + +extension TextStyle { + public static var defaultStyle: TextStyle { return bodyLarge } + + public static func textStyle(for name: String) -> TextStyle? { + guard let style = TextStyle.allCases.first(where: {$0.rawValue == name }) else { return nil } + return style + } +}