Merge branch 'feature/badgeIndicator' into 'develop'

updated images for vector

See merge request BPHV_MIPS/vds_ios!80
This commit is contained in:
Bruce, Matt R 2023-06-15 23:59:45 +00:00
commit bc65e0e186
53 changed files with 1316 additions and 359 deletions

View File

@ -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 = "<group>"; };
EAC925872911C9DE00091998 /* InputField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputField.swift; sourceTree = "<group>"; };
EAC9258B2911C9DE00091998 /* EntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
EAD062A62A3B67770015965D /* UIView+CALayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+CALayer.swift"; sourceTree = "<group>"; };
EAD062A82A3B67B10015965D /* NSLayoutAnchor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutAnchor.swift; sourceTree = "<group>"; };
EAD062AA2A3B67D00015965D /* NSLayoutDimension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutDimension.swift; sourceTree = "<group>"; };
EAD062AF2A3B873E0015965D /* BadgeIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeIndicator.swift; sourceTree = "<group>"; };
EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Publisher.swift"; sourceTree = "<group>"; };
EAF1FE9829D4850E00101452 /* Clickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clickable.swift; sourceTree = "<group>"; };
EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
};
EAD062AE2A3B87210015965D /* BadgeIndicator */ = {
isa = PBXGroup;
children = (
EAD062AF2A3B873E0015965D /* BadgeIndicator.swift */,
);
path = BadgeIndicator;
sourceTree = "<group>";
};
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 */,
);

View File

@ -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<BadgeIndicator, FillColor>(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)
}
}
}

View File

@ -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 }

View File

@ -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
//--------------------------------------------------

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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 ""
}
}
}

View File

@ -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(){}
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -8,5 +8,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@ -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
}
}