Merge branch 'feature/vds_batch_three' into 'develop'

VDS Batch Three

### Summary
VDS Label / TitleLockup Integration 

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-4349
https://onejira.verizon.com/browse/ONEAPP-6241

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1059
This commit is contained in:
Hedden, Kyle Matthew 2024-02-29 19:59:19 +00:00
commit 0977794160
14 changed files with 356 additions and 948 deletions

View File

@ -19,6 +19,7 @@ import UIKit
//-------------------------------------------------- //--------------------------------------------------
public private(set) var titleLabel: FormLabel = { public private(set) var titleLabel: FormLabel = {
let label = FormLabel() let label = FormLabel()
label.setFontStyle(.RegularMicro)
label.setContentCompressionResistancePriority(.required, for: .vertical) label.setContentCompressionResistancePriority(.required, for: .vertical)
return label return label
}() }()
@ -28,6 +29,7 @@ import UIKit
/// Provides contextual information on the TextField. /// Provides contextual information on the TextField.
public private(set) var feedbackLabel: FormLabel = { public private(set) var feedbackLabel: FormLabel = {
let label = FormLabel() let label = FormLabel()
label.setFontStyle(.RegularMicro)
label.setContentCompressionResistancePriority(.required, for: .vertical) label.setContentCompressionResistancePriority(.required, for: .vertical)
return label return label
}() }()
@ -276,15 +278,13 @@ import UIKit
backgroundColor = .clear backgroundColor = .clear
isAccessibilityElement = false isAccessibilityElement = false
titleLabel.font = Styler.Font.RegularMicro.getFont() titleLabel.setFontStyle(.RegularMicro)
titleLabel.textColor = .mvmBlack feedbackLabel.setFontStyle(.RegularMicro)
feedbackLabel.font = Styler.Font.RegularMicro.getFont() errorLabel.setFontStyle(.RegularMicro)
feedbackLabel.textColor = .mvmBlack titleLabel.text = nil
errorLabel.font = Styler.Font.RegularMicro.getFont() feedbackLabel.text = nil
errorLabel.textColor = .mvmBlack
errorLabel.text = nil errorLabel.text = nil
entryFieldContainer.disableAllBorders = false entryFieldContainer.disableAllBorders = false
feedbackLabel.text = nil
entryFieldContainer.reset() entryFieldContainer.reset()
entryFieldModel?.updateUI = nil entryFieldModel?.updateUI = nil
} }

View File

@ -9,11 +9,7 @@
import Foundation import Foundation
/// Subclass of label that helps with different states /// Subclass of label that helps with different states
public class FormLabel: Label { public class FormLabel: Label {
//properties used in setting label
private var delegateObject: MVMCoreUIDelegateObject?
private var additionalData: [AnyHashable: Any]?
//models that drive the label UI //models that drive the label UI
private var formModel: FormLabelModel! private var formModel: FormLabelModel!

View File

@ -8,15 +8,18 @@
// //
import MVMCore import MVMCore
import VDS
public typealias ActionBlock = () -> () public typealias ActionBlock = () -> ()
@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol, ViewMaskingProtocol { @objcMembers open class Label: VDS.Label, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol, ViewMaskingProtocol {
//------------------------------------------------------ //------------------------------------------------------
// MARK: - Properties // MARK: - Properties
//------------------------------------------------------ //------------------------------------------------------
open var viewModel: LabelModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
public var makeWholeViewClickable = false public var makeWholeViewClickable = false
@ -24,58 +27,23 @@ public typealias ActionBlock = () -> ()
public var standardFontSize: CGFloat = 0.0 public var standardFontSize: CGFloat = 0.0
/// Set this to use a custom sizing object during updateView instead of the standard. /// Set this to use a custom sizing object during updateView instead of the standard.
@available(*, deprecated, message: "VDS is maintaining scaleSize")
public var sizeObject: MFSizeObject? public var sizeObject: MFSizeObject?
@available(*, deprecated, message: "VDS is maintaining scaleSize")
public var scaleSize: NSNumber? public var scaleSize: NSNumber?
/// A specific text index to use as a unique marker. /// A specific text index to use as a unique marker.
public var hero: Int? public var hero: Int?
// Used for scaling the font in updateView.
private var originalAttributedString: NSAttributedString?
public var hasText: Bool {
guard let text = text, let attributedText = attributedText else { return false }
return !text.isEmpty || !attributedText.string.isEmpty
}
public var getRange: NSRange { public var getRange: NSRange {
NSRange(location: 0, length: text?.count ?? 0) NSRange(location: 0, length: text?.count ?? 0)
} }
public var shouldMaskWhileRecording: Bool = false public var shouldMaskWhileRecording: Bool = false
public var model: MoleculeModelProtocol? public var hasText: Bool {
//------------------------------------------------------ guard let text = text, let attributedText = attributedText else { return false }
// MARK: - Multi-Action Text return !text.isEmpty || !attributedText.string.isEmpty
//------------------------------------------------------
/// Data store of the tappable ranges of the text.
public var clauses: [ActionableClause] = [] {
didSet {
isUserInteractionEnabled = !clauses.isEmpty
if clauses.count > 1 {
clauses.sort { first, second in
return first.range.location < second.range.location
}
}
}
}
/// Used for tappable links in the text.
public struct ActionableClause {
public var range: NSRange
public var actionBlock: ActionBlock
public var accessibilityID: Int = 0
public func performAction() {
actionBlock()
}
public init(range: NSRange, actionBlock: @escaping ActionBlock, accessibilityID: Int = 0) {
self.range = range
self.actionBlock = actionBlock
self.accessibilityID = accessibilityID
}
} }
//------------------------------------------------------ //------------------------------------------------------
@ -84,49 +52,27 @@ public typealias ActionBlock = () -> ()
/// Sets the clauses array to empty. /// Sets the clauses array to empty.
@objc public func setEmptyClauses() { @objc public func setEmptyClauses() {
clauses = []
} }
//------------------------------------------------------ //------------------------------------------------------
// MARK: - Initialization // MARK: - Initialization
//------------------------------------------------------ //------------------------------------------------------
@objc public func setupView() { @objc public required init() {
backgroundColor = .clear super.init()
numberOfLines = 0
lineBreakMode = .byWordWrapping
translatesAutoresizingMaskIntoConstraints = false
clauses = []
accessibilityCustomActions = []
accessibilityTraits = .staticText
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(textLinkTapped))
tapGesture.numberOfTapsRequired = 1
addGestureRecognizer(tapGesture)
}
@objc public init() {
super.init(frame: .zero)
setupView()
} }
@objc required public init?(coder: NSCoder) { @objc required public init?(coder: NSCoder) {
super.init(coder: coder) super.init(coder: coder)
setupView()
} }
@objc override public init(frame: CGRect) { @objc override public init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
setupView()
} }
public init(fontStyle: Styler.Font, _ scale: Bool = true) { public init(fontStyle: Styler.Font, _ scale: Bool = true) {
super.init(frame: .zero) super.init(frame: .zero)
setupView() setFontStyle(fontStyle, scale)
font = fontStyle.getFont(false)
textColor = fontStyle.color()
setScale(scale)
} }
@objc convenience public init(standardFontSize size: CGFloat) { @objc convenience public init(standardFontSize size: CGFloat) {
@ -145,7 +91,6 @@ public typealias ActionBlock = () -> ()
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(frame: .zero) super.init(frame: .zero)
setupView()
styleB2(true) styleB2(true)
set(with: model, delegateObject, additionalData) set(with: model, delegateObject, additionalData)
} }
@ -236,12 +181,6 @@ public typealias ActionBlock = () -> ()
} }
} }
enum LabelAlignment: String {
case center
case right
case left
}
@objc public func resetAttributeStyle() { @objc public func resetAttributeStyle() {
/* /*
* This is to address a reuse issue with iOS 13 and up. * This is to address a reuse issue with iOS 13 and up.
@ -250,298 +189,121 @@ public typealias ActionBlock = () -> ()
* appropriately called. * appropriately called.
* Only other reference found of issue: https://www.thetopsites.net/article/58142205.shtml * Only other reference found of issue: https://www.thetopsites.net/article/58142205.shtml
*/ */
if let attributedText = attributedText, let text = text, !text.isEmpty { if let text = text, !text.isEmpty {
let attributedString = NSMutableAttributedString(string: text)
let range = NSRange(location: 0, length: text.count) //create the primary string
for attribute in attributedText.attributes(at: 0, effectiveRange: nil) { let mutableText = NSMutableAttributedString.mutableText(for: text,
if attribute.key == .underlineStyle { textStyle: textStyle,
attributedString.addAttribute(.underlineStyle, value: 0, range: range) useScaledFont: useScaledFont,
} textColor: textColorConfiguration.getColor(self),
if attribute.key == .strikethroughStyle { alignment: textAlignment,
attributedString.addAttribute(.strikethroughStyle, value: 0, range: range) lineBreakMode: lineBreakMode)
}
if let attributes = attributes {
mutableText.apply(attributes: attributes)
} }
self.attributedText = attributedString self.attributedText = mutableText
} }
} }
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject? = nil, _ additionalData: [AnyHashable: Any]? = nil) { public func viewModelDidUpdate() {
shouldMaskWhileRecording = viewModel.shouldMaskRecordedView ?? false
text = viewModel.text
hero = viewModel.hero
Label.setLabel(self, withHTML: viewModel.html)
textAlignment = viewModel.textAlignment ?? .left
surface = viewModel.surface
clauses = [] makeWholeViewClickable = viewModel.makeWholeViewClickable ?? false
text = nil if let backgroundColor = viewModel.backgroundColor {
attributedText = nil
originalAttributedString = nil
shouldMaskWhileRecording = model.shouldMaskRecordedView ?? false
guard let labelModel = model as? LabelModel else { return }
text = labelModel.text
if let accessibilityTraits = labelModel.accessibilityTraits {
self.accessibilityTraits = accessibilityTraits
}
resetAttributeStyle()
hero = labelModel.hero
Label.setLabel(self, withHTML: labelModel.html)
isAccessibilityElement = hasText
switch labelModel.textAlignment {
case .center:
textAlignment = .center
case .right:
textAlignment = .right
default:
textAlignment = .left
}
makeWholeViewClickable = labelModel.makeWholeViewClickable ?? false
if let backgroundColor = labelModel.backgroundColor {
self.backgroundColor = backgroundColor.uiColor self.backgroundColor = backgroundColor.uiColor
} }
if let accessibilityText = labelModel.accessibilityText { if let style = viewModel.fontStyle?.vdsTextStyle() {
accessibilityLabel = accessibilityText font = style.font
} textStyle = style
} else if let fontName = viewModel.fontName {
if let fontStyle = labelModel.fontStyle { // there is a TextStyle.defaultStyle
fontStyle.styleLabel(self, genericScaling: false) let fontSize = viewModel.fontSize
standardFontSize = font.pointSize if let fontSize {
} else {
let fontSize = labelModel.fontSize
if let fontSize = fontSize {
standardFontSize = fontSize standardFontSize = fontSize
} }
if let fontName = labelModel.fontName { if let customStyle = style(for: fontName, pointSize: fontSize ?? standardFontSize), customStyle != textStyle {
font = MFFonts.mfFont(withName: fontName, size: fontSize ?? standardFontSize) font = customStyle.font
} else if let fontSize = fontSize { textStyle = customStyle
font = font.updateSize(fontSize)
} }
} }
if let color = labelModel.textColor { if let color = viewModel.textColor {
textColor = color.uiColor textColorConfiguration = SurfaceColorConfiguration(color.uiColor, color.uiColor).eraseToAnyColorable()
} }
if let lines = labelModel.numberOfLines { if let lines = viewModel.numberOfLines {
numberOfLines = lines numberOfLines = lines
} }
if let attributes = labelModel.attributes, let labelText = text { if let attributeModels = viewModel.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) {
let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.updateSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor]) attributes = attributeModels
}
for attribute in attributes {
guard let range = validateAttribute(range: NSRange(location: attribute.location, length: attribute.length) , in: attributedString, type: attribute.type) }
else { continue }
/// See if the font that is currently set matches a VDS Font and if so grab the matching TextStyle or create custom TextStyle that
switch attribute { /// that the Label will use moving forward.
case let underlineAtt as LabelAttributeUnderlineModel: private func checkforFontChange() {
attributedString.addAttribute(.underlineStyle, value: underlineAtt.underlineValue.rawValue, range: range) guard let customStyle = style(for: font.fontName, pointSize: font.pointSize), customStyle != textStyle
if let underlineColor = underlineAtt.color?.uiColor { else { return }
attributedString.addAttribute(.underlineColor, value: underlineColor, range: range) textStyle = customStyle
} }
case _ as LabelAttributeStrikeThroughModel: private func style(for fontName: String, pointSize: CGFloat) -> TextStyle? {
attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) guard let vdsFont = Font.from(fontName: fontName),
attributedString.addAttribute(.baselineOffset, value: 0, range: range) let customStyle = TextStyle.style(from: vdsFont, pointSize: pointSize)
else { return nil }
case let colorAtt as LabelAttributeColorModel: return customStyle
if let colorHex = colorAtt.textColor { }
attributedString.removeAttribute(.foregroundColor, range: range)
attributedString.addAttribute(.foregroundColor, value: colorHex.uiColor, range: range) open override func updateView() {
} checkforFontChange()
super.updateView()
case let imageAtt as LabelAttributeImageModel: }
var fontSize = font.pointSize
if let attributeSize = imageAtt.size { open override func updateAccessibility() {
fontSize = attributeSize super.updateAccessibility()
} if let accessibilityTraits = viewModel?.accessibilityTraits {
let imageName = imageAtt.name ?? "externalLink" self.accessibilityTraits = accessibilityTraits
let imageAttachment: NSTextAttachment }
if let url = imageAtt.URL { if let accessibilityText = viewModel?.accessibilityText {
imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: self) accessibilityLabel = accessibilityText
} else { }
imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize) }
}
@objc open override func reset() {
if let tintColor = imageAtt.tintColor { super.reset()
imageAttachment.image = imageAttachment.image?.withTintColor(tintColor.uiColor, renderingMode: .alwaysTemplate) }
}
@objc open func updateView(_ size: CGFloat) { }
// Confirm that the intended image location is within range.
if 0...labelText.count ~= imageAtt.location { @objc open func setFont(_ font: UIFont, scale: Bool) {
let mutableString = NSMutableAttributedString() self.font = font
mutableString.append(NSAttributedString(attachment: imageAttachment)) setScale(scale)
attributedString.insert(mutableString, at: imageAtt.location) }
}
@objc open func setScale(_ scale: Bool) {
case let fontAtt as LabelAttributeFontModel: if scale {
if let fontStyle = fontAtt.style { standardFontSize = font.pointSize
attributedString.removeAttribute(.font, range: range) } else {
attributedString.removeAttribute(.foregroundColor, range: range) standardFontSize = 0
attributedString.addAttribute(.font, value: fontStyle.getFont(), range: range)
attributedString.addAttribute(.foregroundColor, value: fontStyle.color(), range: range)
} else {
let fontSize = fontAtt.size
var font: UIFont?
if let fontName = fontAtt.name {
font = MFFonts.mfFont(withName: fontName, size: fontSize ?? self.font.pointSize)
} else if let fontSize = fontSize {
font = self.font.updateSize(fontSize)
}
if let font = font {
attributedString.removeAttribute(.font, range: range)
attributedString.addAttribute(.font, value: font, range: range)
}
}
case let actionAtt as LabelAttributeActionModel:
addTappableLinkAttribute(range: NSRange(location: range.location, length: range.length)) {
MVMCoreUIActionHandler.performActionUnstructured(with: actionAtt.action, sourceModel: model, additionalData: additionalData, delegateObject: delegateObject)
}
addActionAttributes(range: range, string: attributedString)
default:
continue
}
}
attributedText = attributedString
originalAttributedString = attributedText
} }
self.model = labelModel
} }
@objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) { @objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) {
guard let label = label else { return } guard let label = label as? Label,
let json = json as? [String: Any],
// Some properties can only be set on Label. let labelModel = try? LabelModel.decode(jsonDict: json) else { return }
// Label fonts should not be scaled because it will be scaled in updateView. label.set(with: labelModel, delegate as? MVMCoreUIDelegateObject, additionalData)
let mvmLabel = label as? Label
label.text = json?.optionalStringForKey(KeyText)
setLabel(label, withHTML: json?.optionalStringForKey("html"))
if let alignment = json?.optionalStringForKey("textAlignment") {
switch alignment {
case "center":
label.textAlignment = .center
case "right":
label.textAlignment = .right
default:
label.textAlignment = .left
}
}
mvmLabel?.makeWholeViewClickable = json?.boolForKey("makeWholeViewClickable") ?? false
if let backgroundColorHex = json?.optionalStringForKey(KeyBackgroundColor), !backgroundColorHex.isEmpty {
label.backgroundColor = UIColor.mfGet(forHex: backgroundColorHex)
}
label.accessibilityLabel = json?.optionalStringForKey("accessibilityText")
if let fontStyle = json?.optionalStringForKey("fontStyle") {
MFStyler.style(label: label, styleString: fontStyle, genericScaling: mvmLabel == nil)
mvmLabel?.standardFontSize = label.font.pointSize
} else {
let fontSize = json?["fontSize"] as? CGFloat
if let fontSize = fontSize {
mvmLabel?.standardFontSize = fontSize
}
if let fontName = json?.optionalStringForKey("fontName") {
label.font = MFFonts.mfFont(withName: fontName, size: fontSize ?? mvmLabel?.standardFontSize ?? label.font.pointSize)
} else if let fontSize = fontSize {
label.font = label.font.updateSize(fontSize)
}
}
if let textColorHex = json?.optionalStringForKey(KeyTextColor), !textColorHex.isEmpty {
label.textColor = UIColor.mfGet(forHex: textColorHex)
}
if let attributes = json?.optionalArrayForKey("attributes"), let labelText = label.text {
let attributedString = NSMutableAttributedString(string: labelText,
attributes: [NSAttributedString.Key.font: mvmLabel?.font.updateSize(mvmLabel!.standardFontSize) ?? label.font as UIFont,
NSAttributedString.Key.foregroundColor: label.textColor as UIColor])
for case let attribute as [String: Any] in attributes {
guard let attributeType = attribute.optionalStringForKey(KeyType),
let location = attribute["location"] as? Int,
let length = attribute["length"] as? Int,
let range = validateAttribute(range: NSRange(location: location, length: length), in: attributedString, type: attributeType)
else { continue }
switch attributeType {
case "underline":
attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
case "strikethrough":
attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range)
attributedString.addAttribute(.baselineOffset, value: 0, range: range)
case "color":
if let colorHex = attribute.optionalStringForKey(KeyTextColor), !colorHex.isEmpty {
attributedString.removeAttribute(.foregroundColor, range: range)
attributedString.addAttribute(.foregroundColor, value: UIColor.mfGet(forHex: colorHex), range: range)
}
case "image":
let fontSize = attribute["size"] as? CGFloat ?? label.font.pointSize
let imageName = attribute["name"] as? String ?? "externalLink"
let imageURL = attribute["URL"] as? String
let imageAttachment: NSTextAttachment
if let url = imageURL, let label = label as? Label {
imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: label)
} else {
imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize)
}
let mutableString = NSMutableAttributedString()
mutableString.append(NSAttributedString(attachment: imageAttachment))
attributedString.insert(mutableString, at: location)
case "font":
if let fontStyle = attribute.optionalStringForKey("style") {
let styles = MFStyler.getAttributedString(for: "0", styleString: fontStyle, genericScaling: mvmLabel == nil)
attributedString.removeAttribute(.font, range: range)
attributedString.removeAttribute(.foregroundColor, range: range)
attributedString.addAttributes(styles.attributes(at: 0, effectiveRange: nil), range: range)
} else {
let fontSize = attribute["size"] as? CGFloat
var font: UIFont?
if let fontName = attribute.optionalStringForKey("name") {
font = MFFonts.mfFont(withName: fontName, size: fontSize ?? mvmLabel?.standardFontSize ?? label.font.pointSize)
} else if let fontSize = fontSize {
font = label.font.updateSize(fontSize)
}
if let font = font {
attributedString.removeAttribute(.font, range: range)
attributedString.addAttribute(.font, value: font, range: range)
}
}
case "action":
guard let actionLabel = label as? Label else { continue }
actionLabel.addActionAttributes(range: range, string: attributedString)
if let actionBlock = actionLabel.createActionBlockFor(actionMap: attribute, additionalData: additionalData, delegateObject: delegate) {
actionLabel.appendActionableClause(range: range, actionBlock: actionBlock)
}
default:
continue
}
}
label.attributedText = attributedString
mvmLabel?.originalAttributedString = attributedString
}
} }
//------------------------------------------------------ //------------------------------------------------------
@ -549,119 +311,50 @@ public typealias ActionBlock = () -> ()
//------------------------------------------------------ //------------------------------------------------------
public func setFontStyle(_ fontStyle: Styler.Font, _ scale: Bool = true) { public func setFontStyle(_ fontStyle: Styler.Font, _ scale: Bool = true) {
fontStyle.styleLabel(self, genericScaling: false) guard let style = fontStyle.vdsTextStyle() else { return }
textStyle = style
setScale(scale) setScale(scale)
} }
//------------------------------------------------------ //------------------------------------------------------
// MARK: - 2.0 Styling Methods // MARK: - 2.0 Styling Methods
//------------------------------------------------------ //------------------------------------------------------
@objc public func styleH1(_ scale: Bool) { @objc public func styleH1(_ scale: Bool) {
MFStyler.styleLabelH1(self, genericScaling: false) setFontStyle(.H1, scale)
setScale(scale)
} }
@objc public func styleH2(_ scale: Bool) { @objc public func styleH2(_ scale: Bool) {
MFStyler.styleLabelH2(self, genericScaling: false) setFontStyle(.H2, scale)
setScale(scale)
} }
@objc public func styleH3(_ scale: Bool) { @objc public func styleH3(_ scale: Bool) {
MFStyler.styleLabelH3(self, genericScaling: false) setFontStyle(.H3, scale)
setScale(scale)
} }
@objc public func styleH32(_ scale: Bool) { @objc public func styleH32(_ scale: Bool) {
MFStyler.styleLabelH32(self, genericScaling: false) setFontStyle(.H32, scale)
setScale(scale)
} }
@objc public func styleB1(_ scale: Bool) { @objc public func styleB1(_ scale: Bool) {
MFStyler.styleLabelB1(self, genericScaling: false) setFontStyle(.B1, scale)
setScale(scale)
} }
@objc public func styleB2(_ scale: Bool) { @objc public func styleB2(_ scale: Bool) {
MFStyler.styleLabelB2(self, genericScaling: false) setFontStyle(.B2, scale)
setScale(scale)
} }
@objc public func styleB3(_ scale: Bool) { @objc public func styleB3(_ scale: Bool) {
MFStyler.styleLabelB3(self, genericScaling: false) setFontStyle(.B3, scale)
setScale(scale)
} }
@objc public func styleB20(_ scale: Bool) { @objc public func styleB20(_ scale: Bool) {
MFStyler.styleLabelB20(self, genericScaling: false) setFontStyle(.B20, scale)
setScale(scale)
} }
/// Will remove the values contained in attributedText. }
func clearAttributes() {
guard let labelText = text, !labelText.isEmpty else { return }
guard let attributes = attributedText?.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: labelText.count))
else { return }
let attributedString = NSMutableAttributedString(string: labelText)
for attribute in attributes {
attributedString.removeAttribute(attribute.key, range: NSRange(location: 0, length: labelText.count))
}
attributedText = attributedString
}
// Mark: - Old Helpers
@objc public func updateView(_ size: CGFloat) { extension Label {
scaleSize = size as NSNumber
if let originalAttributedString = originalAttributedString {
let attributedString = NSMutableAttributedString(attributedString: originalAttributedString)
attributedString.removeAttribute(.font, range: NSRange(location: 0, length: attributedString.length))
// Loop the original attributed string, resize the fonts.
originalAttributedString.enumerateAttribute(.font, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in
if let fontObj = value as? UIFont, let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: fontObj.pointSize)?.getValueBased(onSize: size) {
attributedString.addAttribute(.font, value: fontObj.updateSize(stylerSize) as Any, range: range)
}
}
// Loop the original attributed string, resize the image attachments.
originalAttributedString.enumerateAttribute(.attachment, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in
if let attachment = value as? NSTextAttachment,
let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: attachment.bounds.width)?.getValueBased(onSize: size) {
let dimension = round(stylerSize)
attachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
}
}
attributedText = attributedString
} else if !MVMCoreGetterUtility.fequal(a: Float(standardFontSize), b: 0.0), let sizeObject = sizeObject ?? MFStyler.sizeObjectGeneric(forCurrentDevice: standardFontSize) {
font = font.updateSize(sizeObject.getValueBased(onSize: size))
}
}
@objc public func setFont(_ font: UIFont, scale: Bool) {
self.font = font
setScale(scale)
}
@objc public func setScale(_ scale: Bool) {
if scale {
standardFontSize = font.pointSize
if let floatScale = scaleSize?.floatValue {
updateView(CGFloat(floatScale))
} else {
updateView(MVMCoreUIUtility.getWidth())
}
} else {
standardFontSize = 0
}
}
/** /**
Appends an external link image to the end of the attributed string. Appends an external link image to the end of the attributed string.
Will provide one whitespace to the left of the icon; adds 2 chars to the end of the string. Will provide one whitespace to the left of the icon; adds 2 chars to the end of the string.
@ -693,7 +386,7 @@ public typealias ActionBlock = () -> ()
self.attributedText = mutableString self.attributedText = mutableString
} }
/* /*
Retrieves an NSTextAttachment for NSAttributedString that is prepped to be inserted with the text. Retrieves an NSTextAttachment for NSAttributedString that is prepped to be inserted with the text.
@ -709,47 +402,6 @@ public typealias ActionBlock = () -> ()
return imageAttachment return imageAttachment
} }
static func getTextAttachmentFrom(url: String, dimension: CGFloat, label: Label) -> NSTextAttachment {
let imageAttachment = NSTextAttachment()
imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
DispatchQueue.global(qos: .default).async {
MVMCoreCache.shared()?.getImage(url, useWidth: false, widthForS7: 0, useHeight: false, heightForS7: 0, localFallbackImageName: nil) { image, data, _ in
DispatchQueue.main.sync {
imageAttachment.image = image
label.setNeedsDisplay()
}
}
}
return imageAttachment
}
/// Call to detect in the attributedText contains an NSTextAttachment.
func containsTextAttachment() -> Bool {
guard let attributedText = attributedText else { return false }
var containsAttachment = false
attributedText.enumerateAttribute(.attachment, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, stop in
if value is NSTextAttachment {
containsAttachment = true
return
}
}
return containsAttachment
}
func appendActionableClause(range: NSRange, actionBlock: @escaping ActionBlock) {
accessibilityTraits = .button
let accessibleAction = customAccessibilityAction(range: range)
clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1))
}
public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect {
guard let abstractContainer = label.abstractTextContainer() else { return CGRect() } guard let abstractContainer = label.abstractTextContainer() else { return CGRect() }
@ -793,25 +445,11 @@ public typealias ActionBlock = () -> ()
return (textContainer, layoutManager, textStorage) return (textContainer, layoutManager, textStorage)
} }
} }
// MARK: - Atomization // MARK: - Atomization
extension Label { extension Label {
public func reset() {
text = nil
attributedText = nil
hero = nil
textAlignment = .left
originalAttributedString = nil
styleB2(true)
accessibilityCustomActions = []
clauses = []
accessibilityTraits = .staticText
numberOfLines = 0
}
public func needsToBeConstrained() -> Bool { true } public func needsToBeConstrained() -> Bool { true }
public func horizontalAlignment() -> UIStackView.Alignment { .leading } public func horizontalAlignment() -> UIStackView.Alignment { .leading }
@ -821,19 +459,25 @@ extension Label {
// MARK: - Multi-Link Functionality // MARK: - Multi-Link Functionality
extension Label { extension Label {
/// Applied to existing text. Removes underlines of tappable links and assoated actionable clauses. /// Underlines the tappable region and stores the tap logic for interation.
@objc public func clearActionableClauses() { private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
guard let attributedText = attributedText else { return } var textLink = ActionLabelAttribute(location: range.location, length: range.length)
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) textLink.subscriber = textLink
.action
clauses.forEach { clause in .sink { _ in
mutableAttributedString.removeAttribute(NSAttributedString.Key.underlineStyle, range: clause.range) actionBlock()
}
if var attributes {
attributes.append(textLink)
setNeedsUpdate()
} else {
attributes = [textLink]
} }
}
self.attributedText = mutableAttributedString
accessibilityElements = [] @objc public func clearActionableClauses() {
clauses = [] attributes = attributes?.filter { !($0 is (any ActionLabelAttributeModel)) }
} }
public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? { public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? {
@ -846,24 +490,6 @@ extension Label {
} }
} }
private func addActionAttributes(range: NSRange, string: NSMutableAttributedString?) {
guard let string = string,
let range = validateAttribute(range: range, in: string)
else { return }
string.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range)
}
fileprivate func setActionAttributes(range: NSRange) {
guard let attributedText = attributedText else { return }
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText)
addActionAttributes(range: range, string: mutableAttributedString)
self.attributedText = mutableAttributedString
}
/** /**
Provides an actionable range of text. Provides an actionable range of text.
@ -902,113 +528,6 @@ extension Label {
setTextLinkState(range: getRange, actionBlock: actionBlock) setTextLinkState(range: getRange, actionBlock: actionBlock)
} }
/// Underlines the tappable region and stores the tap logic for interation.
private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
setActionAttributes(range: range)
appendActionableClause(range: range, actionBlock: actionBlock)
}
@objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) {
for clause in clauses {
// This determines if we tapped on the desired range of text.
if gesture.didTapAttributedTextInLabel(self, inRange: clause.range) {
clause.performAction()
return
}
}
}
}
// MARK: -
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool {
// There would only ever be one clause to act on.
if label.makeWholeViewClickable {
return true
}
guard let abstractContainer = label.abstractTextContainer() else { return false }
let textContainer = abstractContainer.0
let layoutManager = abstractContainer.1
let tapLocation = location(in: label)
let indexOfGlyph = layoutManager.glyphIndex(for: tapLocation, in: textContainer)
let intrinsicWidth = label.intrinsicContentSize.width
// Assert that tapped occured within acceptable bounds based on alignment.
switch label.textAlignment {
case .right:
if tapLocation.x < label.bounds.width - intrinsicWidth {
return false
}
case .center:
let halfBounds = label.bounds.width / 2
let halfIntrinsicWidth = intrinsicWidth / 2
if tapLocation.x > halfBounds + halfIntrinsicWidth {
return false
} else if tapLocation.x < halfBounds - halfIntrinsicWidth {
return false
}
default: // Left align
if tapLocation.x > intrinsicWidth {
return false
}
}
// Affirms that the tap occured in the desired rect of provided by the target range.
return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(tapLocation) && NSLocationInRange(indexOfGlyph, targetRange)
}
}
// MARK: - Accessibility
extension Label {
func customAccessibilityAction(range: NSRange) -> UIAccessibilityCustomAction? {
guard let text = text else { return nil }
if accessibilityHint == nil {
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "swipe_to_select_with_action_hint")
}
let actionText = NSString(string: text).substring(with: range)
let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:)))
accessibilityCustomActions?.append(accessibleAction)
return accessibleAction
}
@objc public func accessibilityCustomAction(_ action: UIAccessibilityCustomAction) {
for clause in clauses {
if action.hash == clause.accessibilityID {
clause.performAction()
return
}
}
}
open override func accessibilityActivate() -> Bool {
guard let accessibleActions = accessibilityCustomActions else { return false }
for clause in clauses {
for action in accessibleActions {
if action.hash == clause.accessibilityID {
clause.performAction()
return true
}
}
}
return false
}
} }
//------------------------------------------------------ //------------------------------------------------------
@ -1029,3 +548,4 @@ func validateAttribute(range: NSRange, in string: NSAttributedString, type: Stri
return range return range
} }

View File

@ -5,7 +5,7 @@
// Created by Suresh, Kamlesh on 10/3/19. // Created by Suresh, Kamlesh on 10/3/19.
// Copyright © 2019 Suresh, Kamlesh. All rights reserved. // Copyright © 2019 Suresh, Kamlesh. All rights reserved.
// //
import VDS
@objcMembers open class LabelModel: MoleculeModelProtocol { @objcMembers open class LabelModel: MoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
@ -30,6 +30,7 @@
public var numberOfLines: Int? public var numberOfLines: Int?
public var shouldMaskRecordedView: Bool? = false public var shouldMaskRecordedView: Bool? = false
public var accessibilityTraits: UIAccessibilityTraits? public var accessibilityTraits: UIAccessibilityTraits?
public var inverted: Bool = false
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Keys // MARK: - Keys
@ -49,6 +50,7 @@
case attributes case attributes
case html case html
case hero case hero
case inverted
case makeWholeViewClickable case makeWholeViewClickable
case numberOfLines case numberOfLines
case shouldMaskRecordedView case shouldMaskRecordedView
@ -97,6 +99,7 @@
attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes) attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes)
html = try typeContainer.decodeIfPresent(String.self, forKey: .html) html = try typeContainer.decodeIfPresent(String.self, forKey: .html)
hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero) hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero)
inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable) makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable)
numberOfLines = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfLines) numberOfLines = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfLines)
shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? false shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? false
@ -123,9 +126,14 @@
try container.encodeModelsIfPresent(attributes, forKey: .attributes) try container.encodeModelsIfPresent(attributes, forKey: .attributes)
try container.encodeIfPresent(html, forKey: .html) try container.encodeIfPresent(html, forKey: .html)
try container.encodeIfPresent(hero, forKey: .hero) try container.encodeIfPresent(hero, forKey: .hero)
try container.encodeIfPresent(inverted, forKey: .inverted)
try container.encodeIfPresent(makeWholeViewClickable, forKey: .makeWholeViewClickable) try container.encodeIfPresent(makeWholeViewClickable, forKey: .makeWholeViewClickable)
try container.encodeIfPresent(numberOfLines, forKey: .numberOfLines) try container.encodeIfPresent(numberOfLines, forKey: .numberOfLines)
try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView)
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
} }
} }
extension LabelModel {
public var surface: Surface { inverted ? .dark : .light }
}

View File

@ -68,23 +68,35 @@ open class TileletModel: MoleculeModelProtocol {
public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? { public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? {
guard let title else { return nil } guard let title else { return nil }
let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
let style: TextStyle? = title.fontStyle?.vdsTextStyle()
if let style, let standardStyle = Tilelet.TitleModel.StandardStyle(rawValue: style.toStandardStyle().rawValue) { do {
return .init(text: title.text, textAttributes: attrs, standardStyle: standardStyle) if let style = title.fontStyle {
} else { return .init(text: title.text,
return .init(text: title.text, textAttributes: attrs) textAttributes: attrs,
} standardStyle: try style.vdsSubsetStyle())
} }
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: title.text, textAttributes: attrs)
}
public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? { public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? {
guard let subTitle else { return nil } guard let subTitle else { return nil }
let attrs = subTitle.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) let attrs = subTitle.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
let style: TextStyle? = subTitle.fontStyle?.vdsTextStyle() do {
if let style, let standardStyle = Tilelet.SubTitleModel.StandardStyle(rawValue: style.toStandardStyle().rawValue) { if let style = subTitle.fontStyle {
return .init(text: subTitle.text, textAttributes: attrs, standardStyle: standardStyle) return .init(text: subTitle.text,
} else { otherStandardStyle: try style.vdsSubsetStyle(),
return .init(text: subTitle.text, textAttributes: attrs) textAttributes: attrs)
} }
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: subTitle.text, textAttributes: attrs)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {

View File

@ -26,6 +26,7 @@ extension VDS.Tabs.Size: Codable {}
extension VDS.TextLink.Size: Codable {} extension VDS.TextLink.Size: Codable {}
extension VDS.TextLinkCaret.IconPosition: Codable {} extension VDS.TextLinkCaret.IconPosition: Codable {}
extension VDS.TileContainer.AspectRatio: Codable {} extension VDS.TileContainer.AspectRatio: Codable {}
extension VDS.TitleLockup.TextAlignment: Codable {}
extension VDS.Tooltip.FillColor: Codable {} extension VDS.Tooltip.FillColor: Codable {}
extension VDS.Tooltip.Size: Codable {} extension VDS.Tooltip.Size: Codable {}
extension VDS.Line.Style: Codable {} extension VDS.Line.Style: Codable {}

View File

@ -12,20 +12,54 @@ import VDS
extension Styler.Font { extension Styler.Font {
//Converts Legacy Font to a VDS.TextStyle //Converts Legacy Font to a VDS.TextStyle
public func vdsTextStyle() -> VDS.TextStyle? { public func vdsTextStyle() -> VDS.TextStyle? {
let updatedRaw = rawValue.replacingOccurrences(of: "Regular", with: "")
//ensure that the current Styler.Font isn't Legacy Version
//if it is, here is the conversion to the updated version
var actualFont: Styler.Font = self
switch self {
case .Title2XLarge: actualFont = .RegularTitle2XLarge
case .TitleXLarge: actualFont = .RegularTitleXLarge
case .H1: actualFont = .RegularTitle2XLarge
case .H32: actualFont = .RegularTitleXLarge
case .H2: actualFont = .RegularTitleLarge
case .B20: actualFont = .RegularBodyLarge
case .H3: actualFont = .BoldTitleMedium
case .B1: actualFont = .BoldBodySmall
case .B2: actualFont = .RegularBodySmall
case .B3: actualFont = .RegularMicro
default: break
}
let updatedRaw = actualFont.rawValue.replacingOccurrences(of: "Regular", with: "")
let newRaw = updatedRaw.prefix(1).lowercased() + updatedRaw.dropFirst() let newRaw = updatedRaw.prefix(1).lowercased() + updatedRaw.dropFirst()
guard let style = VDS.TextStyle(rawValue: newRaw) else { return nil } guard let style = VDS.TextStyle(rawValue: newRaw) else { return nil }
return style return style
} }
public func vdsSubsetStyle<T: EnumSubset>() -> T? { public func vdsSubsetStyle<T: EnumSubset>() throws -> T {
guard let style = vdsTextStyle() else { return nil } guard let style = vdsTextStyle(), let rawValue = style.toStandardStyle().rawValue as? T.RawValue, let standardStyle = T(rawValue: rawValue) else {
guard let rawValue = style.rawValue as? T.RawValue, let err = "\(rawValue) was not found in the \(T.self), only these cases exist:\r\(T.allCases)"
let found = T(rawValue: rawValue) else { throw MVMCoreError.errorObject(MVMCoreErrorObject(title: "\(T.self) conversion Issue",
print("Style: \(style.rawValue) is not in enum \(T.self)\ronly these cases exist:\r\(T.allCases)") messageToLog: err,
return nil code: 999,
domain: ErrorDomainNative,
location: #file)!)
} }
return found return standardStyle
} }
} }
extension VDS.Font {
internal static func from(fontName: String) -> Self? {
Self.allCases.filter({$0.fontName == fontName }).first
}
}
extension VDS.TextStyle {
internal static func style(from font: VDS.Font, pointSize: CGFloat) -> TextStyle? {
guard let first = allCases.filter({$0.fontFace == font && $0.pointSize == pointSize}).first else {
return TextStyle(rawValue: "Custom-TextStyle", fontFace: font, pointSize: pointSize)
}
return first
}
}

View File

@ -12,7 +12,7 @@ import Foundation
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Outlets // MARK: - Outlets
//-------------------------------------------------- //--------------------------------------------------
public let planLabel = Label() public let planLabel = Label(fontStyle: .BoldFeatureXLarge)
public let headline = Label(fontStyle: .BoldTitleLarge) public let headline = Label(fontStyle: .BoldTitleLarge)
public let subHeadline = Label(fontStyle: .RegularTitleLarge) public let subHeadline = Label(fontStyle: .RegularTitleLarge)
public let body = Label(fontStyle: .RegularBodySmall) public let body = Label(fontStyle: .RegularBodySmall)
@ -33,8 +33,6 @@ import Foundation
//------------------------------------------------------- //-------------------------------------------------------
open override func setupView() { open override func setupView() {
super.setupView() super.setupView()
planLabel.font = MFStyler.getMVA3FontSize(96, bold: true)
planLabel.standardFontSize = 96
addSubview(stack) addSubview(stack)
planLabel.setContentCompressionResistancePriority(.required, for: .horizontal) planLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
planLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) planLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
@ -94,8 +92,7 @@ import Foundation
open override func reset() { open override func reset() {
super.reset() super.reset()
stack.reset() stack.reset()
planLabel.font = MFStyler.getMVA3FontSize(96, bold: true) planLabel.setFontStyle(.BoldFeatureXLarge)
planLabel.standardFontSize = 96
headline.setFontStyle(.BoldTitleLarge) headline.setFontStyle(.BoldTitleLarge)
subHeadline.setFontStyle(.RegularTitleLarge) subHeadline.setFontStyle(.RegularTitleLarge)
body.setFontStyle(.RegularBodySmall) body.setFontStyle(.RegularBodySmall)

View File

@ -5,119 +5,43 @@
// Created by Nadigadda, Sumanth on 04/05/22. // Created by Nadigadda, Sumanth on 04/05/22.
// Copyright © 2022 Verizon Wireless. All rights reserved. // Copyright © 2022 Verizon Wireless. All rights reserved.
// //
import VDS
@objcMembers open class TitleLockup: VDS.TitleLockup, VDSMoleculeViewProtocol {
@objcMembers open class TitleLockup: View {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Outlets // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
open var viewModel: TitleLockupModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
public let eyebrow = Label(fontStyle: .RegularBodySmall) //--------------------------------------------------
public let title = Label(fontStyle: .RegularBodySmall) // MARK: - Public Functions
public let subTitle = Label(fontStyle: .RegularBodySmall) //--------------------------------------------------
public lazy var stack: UIStackView = { open func viewModelDidUpdate() {
let stack = UIStackView(arrangedSubviews: [eyebrow, title, subTitle]) surface = viewModel.surface
stack.translatesAutoresizingMaskIntoConstraints = false textAlignment = viewModel.textAlignment
stack.axis = .vertical eyebrowModel = viewModel.eyebrowModel(delegateObject: delegateObject, additionalData: additionalData)
return stack titleModel = viewModel.titleModel(delegateObject: delegateObject, additionalData: additionalData)
}() subTitleModel = viewModel.subTitleModel(delegateObject: delegateObject, additionalData: additionalData)
var castModel: TitleLockupModel? {
get { return model as? TitleLockupModel }
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initialization // MARK: - Initialization
//-------------------------------------------------- //--------------------------------------------------
public convenience init() { public convenience required init() {
self.init(frame: .zero) self.init(frame: .zero)
} }
//--------------------------------------------------
// MARK: - MFViewProtocol
//--------------------------------------------------
open override func setupView() {
super.setupView()
addSubview(stack)
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
stack.updateView(size)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - MoleculeViewProtocol // MARK: - MoleculeViewProtocol
//-------------------------------------------------- //--------------------------------------------------
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
open override func reset() {
super.reset()
stack.reset()
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? TitleLockupModel else { return }
stack.setCustomSpacing(model.defaultEyebrowTitleSpacing(), after: eyebrow)
stack.setCustomSpacing(model.defaultTitleSubTitleSpacing(), after: title)
stack.updateContainedMolecules(with: [model.eyebrow,
model.title,
model.subTitle],
delegateObject, additionalData)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 65 return 65
} }
//-------------------------------------------------- open func updateView(_ size: CGFloat) {}
// MARK: - Accessibility Helpers
//--------------------------------------------------
/// Returns the labels text in one message.
func getAccessibilityMessage() -> String? {
var message = ""
if let eyebrowLabel = eyebrow.text {
message += eyebrowLabel + ", "
}
if let headlineLabel = title.text {
message += headlineLabel + ", "
}
if let bodyLabel = subTitle.text {
message += bodyLabel
}
return message.count > 0 ? message : nil
}
/// Returns an array of the appropriate accessibility elements.
func getAccessibilityElements() -> [Any]? {
var elements: [UIView] = []
if eyebrow.hasText {
elements.append(eyebrow)
}
if title.hasText {
elements.append(title)
}
if subTitle.hasText {
elements.append(subTitle)
}
return elements.count > 0 ? elements : nil
}
} }

View File

@ -7,6 +7,7 @@
// //
import VDSColorTokens import VDSColorTokens
import VDS
public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
@ -18,47 +19,16 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
public var moleculeName: String = TitleLockupModel.identifier public var moleculeName: String = TitleLockupModel.identifier
public var id: String = UUID().uuidString public var id: String = UUID().uuidString
public var textAlignment: TitleLockup.TextAlignment = .left
public var eyebrow: LabelModel? public var eyebrow: LabelModel?
public var title: LabelModel public var title: LabelModel
public var subTitle: LabelModel? public var subTitle: LabelModel?
public var subTitleColor: Use = .primary
public var alignment: Alignment = .left { public var alignment: VDS.TitleLockup.TextAlignment = .left
didSet { public var inverted: Bool = false
///Updating the text alignment for all labels
if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) {
eyebrow?.textAlignment = textAlignment
title.textAlignment = textAlignment
subTitle?.textAlignment = textAlignment
}
}
}
public var inverted: Bool = false { public var backgroundColor: Color?
didSet {
///Updating the text color
eyebrow?.textColor = titleColor
title.textColor = titleColor
subTitle?.textColor = subTitleColor
}
}
private var _backgroundColor: Color?
public var backgroundColor: Color? {
get {
return inverted ? Color(uiColor: VDSColor.backgroundPrimaryDark) : Color(uiColor: VDSColor.backgroundPrimaryLight)
}
set {
_backgroundColor = newValue
}
}
public var titleColor: Color? {
return inverted ? Color(uiColor: VDSColor.elementsPrimaryOndark) : Color(uiColor: VDSColor.elementsPrimaryOnlight)
}
public var subTitleColor: Color? {
return inverted ? Color(uiColor: VDSColor.elementsSecondaryOndark) : Color(uiColor: VDSColor.elementsSecondaryOnlight)
}
public var children: [MoleculeModelProtocol] { public var children: [MoleculeModelProtocol] {
[eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } [eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule }
@ -78,58 +48,6 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
self.eyebrow = eyebrow self.eyebrow = eyebrow
self.title = title self.title = title
self.subTitle = subTitle self.subTitle = subTitle
updateLabelAttributes()
}
//--------------------------------------------------
// MARK: - Enum
//--------------------------------------------------
public enum Alignment: String, Codable {
case left
case center
}
//--------------------------------------------------
// MARK: - Styling
//--------------------------------------------------
/// Returns the default fontStyle for the subtitle, based on the title fontStyle.
func defaultSubtitleFontStyle() -> Styler.Font {
switch title.fontStyle {
case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall:
return .RegularBodyLarge
case .RegularFeatureSmall, .RegularFeatureMedium:
return .RegularTitleLarge
default:
return .RegularBodySmall
}
}
/// Returns the default spacing between the eyebrow and title, based on the title fontStyle.
func defaultEyebrowTitleSpacing() -> CGFloat {
switch title.fontStyle {
case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall:
return Padding.Three
case .RegularFeatureMedium:
return subTitle?.fontStyle == .RegularBodyLarge ? Padding.Three : Padding.Four
default:
return Padding.Two
}
}
/// Returns the default spacing between the title and subTitle, based on the title fontStyle.
func defaultTitleSubTitleSpacing() -> CGFloat {
switch title.fontStyle {
case .RegularTitleXLarge:
return Padding.Three
case .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall:
return Padding.Four
case .RegularFeatureMedium:
return Padding.Five
default:
return Padding.Two
}
} }
//-------------------------------------------------- //--------------------------------------------------
@ -139,10 +57,11 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case id case id
case moleculeName case moleculeName
case backgroundColor case textAlignment
case eyebrow case eyebrow
case title case title
case subTitle case subTitle
case subTitleColor
case inverted case inverted
case alignment case alignment
} }
@ -154,72 +73,122 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
textAlignment = try typeContainer.decodeIfPresent(TitleLockup.TextAlignment.self, forKey: .textAlignment) ?? .left
title = try typeContainer.decodeMolecule(codingKey: .title) title = try typeContainer.decodeMolecule(codingKey: .title)
eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle) subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle)
/// look for color hex code
if let color = try? typeContainer.decodeIfPresent(Color.self, forKey: .subTitleColor) {
self.subTitleColor = color.uiColor.isDark() ? .primary : .secondary
if let newAlignment = try typeContainer.decodeIfPresent(Alignment.self, forKey: .alignment) { } else if let subTitleColor = try? typeContainer.decodeIfPresent(Use.self, forKey: .subTitleColor) {
self.subTitleColor = subTitleColor
} else {
subTitleColor = .primary
}
if let newAlignment = try typeContainer.decodeIfPresent(VDS.TitleLockup.TextAlignment.self, forKey: .alignment) {
alignment = newAlignment alignment = newAlignment
} }
if let invertedStatus = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
inverted = invertedStatus self.inverted = inverted
} else {
try setInverted(deprecatedFrom: decoder)
} }
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
updateLabelAttributes()
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id) try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(textAlignment, forKey: .textAlignment)
try container.encodeIfPresent(eyebrow, forKey: .eyebrow) try container.encodeIfPresent(eyebrow, forKey: .eyebrow)
try container.encodeModel(title, forKey: .title) try container.encodeModel(title, forKey: .title)
try container.encodeIfPresent(subTitle, forKey: .subTitle) try container.encodeIfPresent(subTitle, forKey: .subTitle)
try container.encode(subTitleColor, forKey: .subTitleColor)
try container.encode(alignment, forKey: .alignment) try container.encode(alignment, forKey: .alignment)
try container.encode(inverted, forKey: .inverted) try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor)
} }
//-------------------------------------------------- public func eyebrowModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.EyebrowModel? {
// MARK: - Model updates guard let eyebrow else { return nil }
//-------------------------------------------------- let attrs = eyebrow.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
do {
if let style = eyebrow.fontStyle {
return .init(text: eyebrow.text,
isBold: style.isBold(),
standardStyle: try style.vdsSubsetStyle(),
textAttributes: attrs,
numberOfLines: eyebrow.numberOfLines ?? 0)
}
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: eyebrow.text, textAttributes: attrs, numberOfLines: eyebrow.numberOfLines ?? 0)
}
public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.TitleModel {
let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
do {
if let style = title.fontStyle {
return .init(text: title.text,
textAttributes: attrs,
isBold: style.isBold(),
standardStyle: try style.vdsSubsetStyle(),
numberOfLines: title.numberOfLines ?? 0)
}
private func updateLabelAttributes() { } catch MVMCoreError.errorObject(let object) {
// If subtitle style is not available, will set font style based on the component MVMCoreLoggingHandler.shared()?.addError(toLog: object)
if subTitle?.fontStyle == nil { } catch { }
subTitle?.fontStyle = defaultSubtitleFontStyle()
}
// If eyebrow style is not available, will set font style based on the component. Eyebrow and subtitle share the same font size return .init(text: title.text, textAttributes: attrs, numberOfLines: title.numberOfLines ?? 0)
if eyebrow?.fontStyle == nil { }
eyebrow?.fontStyle = subTitle?.fontStyle
} public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.SubTitleModel? {
guard let subTitle else { return nil }
let attrs = subTitle.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
// Updating the text color do {
if eyebrow?.textColor == nil { if let style = subTitle.fontStyle {
eyebrow?.textColor = subTitleColor return .init(text: subTitle.text,
} otherStandardStyle: try style.vdsSubsetStyle(),
if title.textColor == nil { textColor: subTitleColor,
title.textColor = titleColor textAttributes: attrs,
} numberOfLines: subTitle.numberOfLines ?? 0)
if subTitle?.textColor == nil {
subTitle?.textColor = subTitleColor
}
// Updating the text alignment for all labels
if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) {
if eyebrow?.textAlignment == nil {
eyebrow?.textAlignment = textAlignment
}
if title.textAlignment == nil {
title.textAlignment = textAlignment
}
if subTitle?.textAlignment == nil {
subTitle?.textAlignment = textAlignment
} }
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: subTitle.text, textColor: subTitleColor, textAttributes: attrs, numberOfLines: subTitle.numberOfLines ?? 0)
}
private enum DeprecatedCodingKeys: String, CodingKey {
case titleColor
case backgroundColor
}
private func setInverted(deprecatedFrom decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: DeprecatedCodingKeys.self)
if let titleColor = try typeContainer.decodeIfPresent(Color.self, forKey: .titleColor) {
inverted = !titleColor.uiColor.isDark()
}
if let backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) {
inverted = !backgroundColor.uiColor.isDark()
} }
} }
}
extension TitleLockupModel {
public var surface: Surface { inverted ? .dark : .light }
} }

View File

@ -76,8 +76,8 @@ import UIKit
private func setDefaultState() { private func setDefaultState() {
headlineBodyButton.headlineBody.headlineLabel.font = MFStyler.fontBoldTitleMedium() headlineBodyButton.headlineBody.headlineLabel.setFontStyle(.BoldTitleMedium)
headlineBodyButton.headlineBody.messageLabel.font = MFStyler.fontRegularMicro() headlineBodyButton.headlineBody.messageLabel.setFontStyle(.RegularMicro)
imageLoader.imageView.contentMode = .scaleAspectFit imageLoader.imageView.contentMode = .scaleAspectFit
imageLoader.addSizeConstraintsForAspectRatio = true imageLoader.addSizeConstraintsForAspectRatio = true
buttonHeaderPadding = PaddingTwo buttonHeaderPadding = PaddingTwo

View File

@ -14,7 +14,7 @@ import UIKit
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public let label = Label(fontStyle: .BoldBodySmall) public let label = Label().with { $0.font = Styler.Font.BoldFeatureXLarge.getFont() }
public let toggle = Toggle() public let toggle = Toggle()
//-------------------------------------------------- //--------------------------------------------------

View File

@ -62,8 +62,8 @@
private func defaultState() { private func defaultState() {
headlineBody.headlineLabel.font = Styler.Font.BoldTitleMedium.getFont() headlineBody.headlineLabel.setFontStyle(.BoldTitleMedium)
headlineBody.messageLabel.font = Styler.Font.RegularMicro.getFont() headlineBody.messageLabel.setFontStyle(.RegularMicro)
button.use = .secondary button.use = .secondary
button.isHidden = false button.isHidden = false
buttonHeadlinePadding = PaddingTwo buttonHeadlinePadding = PaddingTwo

View File

@ -57,59 +57,6 @@ open class Styler {
case B2 // Maps to RegularBodySmall case B2 // Maps to RegularBodySmall
case B3 // Maps to RegularMicro case B3 // Maps to RegularMicro
/// Returns the font size of the current enum case.
public func pointSize() -> CGFloat {
switch self {
case .RegularFeatureXLarge,
.BoldFeatureXLarge:
return 96
case .RegularFeatureLarge,
.BoldFeatureLarge:
return 80
case .RegularFeatureMedium,
.BoldFeatureMedium:
return 64
case .RegularFeatureSmall,
.BoldFeatureSmall:
return 48
case .RegularFeatureXSmall,
.BoldFeatureXSmall,
.RegularTitle2XLarge,
.BoldTitle2XLarge,
.Title2XLarge,
.H1:
return 40
case .RegularTitleXLarge,
.BoldTitleXLarge,
.TitleXLarge,
.H32:
return 32
case .BoldTitleLarge,
.RegularTitleLarge,
.H2:
return 24
case .BoldTitleMedium,
.RegularTitleMedium,
.H3:
return 20
case .RegularTitleSmall,
.BoldTitleSmall,
.BoldBodyLarge,
.RegularBodyLarge,
.B20:
return 16
case .RegularBodyMedium,
.BoldBodyMedium:
return 14
case .BoldBodySmall, .B1,
.RegularBodySmall, .B2:
return 12
case .BoldMicro,
.RegularMicro, .B3:
return 11
}
}
public func color() -> UIColor { public func color() -> UIColor {
switch self { switch self {
case .B3: case .B3:
@ -148,8 +95,8 @@ open class Styler {
/// Returns the font based on the declared enum case. /// Returns the font based on the declared enum case.
public func getFont(_ genericScaling: Bool = true) -> UIFont { public func getFont(_ genericScaling: Bool = true) -> UIFont {
let size = genericScaling ? sizeFontGeneric(forCurrentDevice: pointSize()) : pointSize() let vdsStyle = vdsTextStyle() ?? .defaultStyle
return MFStyler.getFontFor(size: size, isBold: isBold()) return vdsStyle.font
} }
/// Styles the provided label to the declared enum Font case. /// Styles the provided label to the declared enum Font case.
@ -237,7 +184,7 @@ open class Styler {
/// Creates the appropriate VZW font for a VDS style, scaling based on the scaleValue threshold passed in. /// Creates the appropriate VZW font for a VDS style, scaling based on the scaleValue threshold passed in.
@objc static func getFontFor(styleString: String, scaleValue: CGFloat) -> UIFont? { @objc static func getFontFor(styleString: String, scaleValue: CGFloat) -> UIFont? {
guard let font = Styler.Font(rawValue: styleString), guard let font = Styler.Font(rawValue: styleString),
let size = Styler.Font(rawValue: styleString)?.pointSize(), let size = font.vdsTextStyle()?.pointSize,
let newSize = Styler.sizeObjectGeneric(forCurrentDevice: size)?.getValueBased(onSize: scaleValue) else { return nil } let newSize = Styler.sizeObjectGeneric(forCurrentDevice: size)?.getValueBased(onSize: scaleValue) else { return nil }
return getFontFor(size: newSize, isBold: font.isBold()) return getFontFor(size: newSize, isBold: font.isBold())
} }