diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift index c583638c..bc3e9617 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift @@ -19,6 +19,7 @@ import UIKit //-------------------------------------------------- public private(set) var titleLabel: FormLabel = { let label = FormLabel() + label.setFontStyle(.RegularMicro) label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() @@ -28,6 +29,7 @@ import UIKit /// Provides contextual information on the TextField. public private(set) var feedbackLabel: FormLabel = { let label = FormLabel() + label.setFontStyle(.RegularMicro) label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() @@ -276,15 +278,13 @@ import UIKit backgroundColor = .clear isAccessibilityElement = false - titleLabel.font = Styler.Font.RegularMicro.getFont() - titleLabel.textColor = .mvmBlack - feedbackLabel.font = Styler.Font.RegularMicro.getFont() - feedbackLabel.textColor = .mvmBlack - errorLabel.font = Styler.Font.RegularMicro.getFont() - errorLabel.textColor = .mvmBlack + titleLabel.setFontStyle(.RegularMicro) + feedbackLabel.setFontStyle(.RegularMicro) + errorLabel.setFontStyle(.RegularMicro) + titleLabel.text = nil + feedbackLabel.text = nil errorLabel.text = nil entryFieldContainer.disableAllBorders = false - feedbackLabel.text = nil entryFieldContainer.reset() entryFieldModel?.updateUI = nil } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift index 08b7267e..a7755928 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift @@ -9,11 +9,7 @@ import Foundation /// Subclass of label that helps with different states -public class FormLabel: Label { - //properties used in setting label - private var delegateObject: MVMCoreUIDelegateObject? - private var additionalData: [AnyHashable: Any]? - +public class FormLabel: Label { //models that drive the label UI private var formModel: FormLabelModel! diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index d69c501d..37eff177 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -8,15 +8,18 @@ // import MVMCore +import VDS 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 //------------------------------------------------------ + open var viewModel: LabelModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? public var makeWholeViewClickable = false @@ -24,58 +27,23 @@ public typealias ActionBlock = () -> () public var standardFontSize: CGFloat = 0.0 /// 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? + @available(*, deprecated, message: "VDS is maintaining scaleSize") public var scaleSize: NSNumber? /// A specific text index to use as a unique marker. 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 { NSRange(location: 0, length: text?.count ?? 0) } public var shouldMaskWhileRecording: Bool = false - public var model: MoleculeModelProtocol? - //------------------------------------------------------ - // MARK: - Multi-Action Text - //------------------------------------------------------ - - /// 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 - } + public var hasText: Bool { + guard let text = text, let attributedText = attributedText else { return false } + return !text.isEmpty || !attributedText.string.isEmpty } //------------------------------------------------------ @@ -84,49 +52,27 @@ public typealias ActionBlock = () -> () /// Sets the clauses array to empty. @objc public func setEmptyClauses() { - clauses = [] } //------------------------------------------------------ // MARK: - Initialization //------------------------------------------------------ - @objc public func setupView() { - backgroundColor = .clear - 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 public required init() { + super.init() } @objc required public init?(coder: NSCoder) { super.init(coder: coder) - setupView() } @objc override public init(frame: CGRect) { super.init(frame: frame) - setupView() } public init(fontStyle: Styler.Font, _ scale: Bool = true) { super.init(frame: .zero) - setupView() - - font = fontStyle.getFont(false) - textColor = fontStyle.color() - setScale(scale) + setFontStyle(fontStyle, scale) } @objc convenience public init(standardFontSize size: CGFloat) { @@ -145,7 +91,6 @@ public typealias ActionBlock = () -> () required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.init(frame: .zero) - setupView() styleB2(true) 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() { /* * This is to address a reuse issue with iOS 13 and up. @@ -250,298 +189,121 @@ public typealias ActionBlock = () -> () * appropriately called. * Only other reference found of issue: https://www.thetopsites.net/article/58142205.shtml */ - if let attributedText = attributedText, let text = text, !text.isEmpty { - let attributedString = NSMutableAttributedString(string: text) - let range = NSRange(location: 0, length: text.count) - for attribute in attributedText.attributes(at: 0, effectiveRange: nil) { - if attribute.key == .underlineStyle { - attributedString.addAttribute(.underlineStyle, value: 0, range: range) - } - if attribute.key == .strikethroughStyle { - attributedString.addAttribute(.strikethroughStyle, value: 0, range: range) - } + if let text = text, !text.isEmpty { + + //create the primary string + let mutableText = NSMutableAttributedString.mutableText(for: text, + textStyle: textStyle, + useScaledFont: useScaledFont, + textColor: textColorConfiguration.getColor(self), + alignment: textAlignment, + 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 = [] - text = nil - 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 { + makeWholeViewClickable = viewModel.makeWholeViewClickable ?? false + if let backgroundColor = viewModel.backgroundColor { self.backgroundColor = backgroundColor.uiColor } - if let accessibilityText = labelModel.accessibilityText { - accessibilityLabel = accessibilityText - } - - if let fontStyle = labelModel.fontStyle { - fontStyle.styleLabel(self, genericScaling: false) - standardFontSize = font.pointSize - } else { - let fontSize = labelModel.fontSize - if let fontSize = fontSize { + if let style = viewModel.fontStyle?.vdsTextStyle() { + font = style.font + textStyle = style + } else if let fontName = viewModel.fontName { + // there is a TextStyle.defaultStyle + let fontSize = viewModel.fontSize + if let fontSize { standardFontSize = fontSize } - if let fontName = labelModel.fontName { - font = MFFonts.mfFont(withName: fontName, size: fontSize ?? standardFontSize) - } else if let fontSize = fontSize { - font = font.updateSize(fontSize) + if let customStyle = style(for: fontName, pointSize: fontSize ?? standardFontSize), customStyle != textStyle { + font = customStyle.font + textStyle = customStyle } } - if let color = labelModel.textColor { - textColor = color.uiColor + if let color = viewModel.textColor { + textColorConfiguration = SurfaceColorConfiguration(color.uiColor, color.uiColor).eraseToAnyColorable() } - if let lines = labelModel.numberOfLines { - numberOfLines = lines + if let lines = viewModel.numberOfLines { + numberOfLines = lines } - - if let attributes = labelModel.attributes, let labelText = text { - let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.updateSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor]) - - for attribute in attributes { - guard let range = validateAttribute(range: NSRange(location: attribute.location, length: attribute.length) , in: attributedString, type: attribute.type) - else { continue } - - switch attribute { - case let underlineAtt as LabelAttributeUnderlineModel: - attributedString.addAttribute(.underlineStyle, value: underlineAtt.underlineValue.rawValue, range: range) - if let underlineColor = underlineAtt.color?.uiColor { - attributedString.addAttribute(.underlineColor, value: underlineColor, range: range) - } - - case _ as LabelAttributeStrikeThroughModel: - attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) - attributedString.addAttribute(.baselineOffset, value: 0, range: range) - - case let colorAtt as LabelAttributeColorModel: - if let colorHex = colorAtt.textColor { - attributedString.removeAttribute(.foregroundColor, range: range) - attributedString.addAttribute(.foregroundColor, value: colorHex.uiColor, range: range) - } - - case let imageAtt as LabelAttributeImageModel: - var fontSize = font.pointSize - if let attributeSize = imageAtt.size { - fontSize = attributeSize - } - let imageName = imageAtt.name ?? "externalLink" - let imageAttachment: NSTextAttachment - - if let url = imageAtt.URL { - imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: self) - } else { - imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize) - } - - if let tintColor = imageAtt.tintColor { - imageAttachment.image = imageAttachment.image?.withTintColor(tintColor.uiColor, renderingMode: .alwaysTemplate) - } - - // Confirm that the intended image location is within range. - if 0...labelText.count ~= imageAtt.location { - let mutableString = NSMutableAttributedString() - mutableString.append(NSAttributedString(attachment: imageAttachment)) - attributedString.insert(mutableString, at: imageAtt.location) - } - - case let fontAtt as LabelAttributeFontModel: - if let fontStyle = fontAtt.style { - attributedString.removeAttribute(.font, range: range) - attributedString.removeAttribute(.foregroundColor, range: range) - 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 + + if let attributeModels = viewModel.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) { + attributes = attributeModels + } + + } + + /// See if the font that is currently set matches a VDS Font and if so grab the matching TextStyle or create custom TextStyle that + /// that the Label will use moving forward. + private func checkforFontChange() { + guard let customStyle = style(for: font.fontName, pointSize: font.pointSize), customStyle != textStyle + else { return } + textStyle = customStyle + } + + private func style(for fontName: String, pointSize: CGFloat) -> TextStyle? { + guard let vdsFont = Font.from(fontName: fontName), + let customStyle = TextStyle.style(from: vdsFont, pointSize: pointSize) + else { return nil } + return customStyle + } + + open override func updateView() { + checkforFontChange() + super.updateView() + } + + open override func updateAccessibility() { + super.updateAccessibility() + if let accessibilityTraits = viewModel?.accessibilityTraits { + self.accessibilityTraits = accessibilityTraits + } + + if let accessibilityText = viewModel?.accessibilityText { + accessibilityLabel = accessibilityText + } + } + + @objc open override func reset() { + super.reset() + } + + @objc open func updateView(_ size: CGFloat) { } + + @objc open func setFont(_ font: UIFont, scale: Bool) { + self.font = font + setScale(scale) + } + + @objc open func setScale(_ scale: Bool) { + if scale { + standardFontSize = font.pointSize + } else { + standardFontSize = 0 } - self.model = labelModel } @objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) { - guard let label = label else { return } - - // Some properties can only be set on Label. - // Label fonts should not be scaled because it will be scaled in updateView. - 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 - } + guard let label = label as? Label, + let json = json as? [String: Any], + let labelModel = try? LabelModel.decode(jsonDict: json) else { return } + label.set(with: labelModel, delegate as? MVMCoreUIDelegateObject, additionalData) } //------------------------------------------------------ @@ -549,119 +311,50 @@ public typealias ActionBlock = () -> () //------------------------------------------------------ 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) } //------------------------------------------------------ // MARK: - 2.0 Styling Methods //------------------------------------------------------ - @objc public func styleH1(_ scale: Bool) { - MFStyler.styleLabelH1(self, genericScaling: false) - setScale(scale) + setFontStyle(.H1, scale) } @objc public func styleH2(_ scale: Bool) { - MFStyler.styleLabelH2(self, genericScaling: false) - setScale(scale) + setFontStyle(.H2, scale) } @objc public func styleH3(_ scale: Bool) { - MFStyler.styleLabelH3(self, genericScaling: false) - setScale(scale) + setFontStyle(.H3, scale) } @objc public func styleH32(_ scale: Bool) { - MFStyler.styleLabelH32(self, genericScaling: false) - setScale(scale) + setFontStyle(.H32, scale) } @objc public func styleB1(_ scale: Bool) { - MFStyler.styleLabelB1(self, genericScaling: false) - setScale(scale) + setFontStyle(.B1, scale) } @objc public func styleB2(_ scale: Bool) { - MFStyler.styleLabelB2(self, genericScaling: false) - setScale(scale) + setFontStyle(.B2, scale) } @objc public func styleB3(_ scale: Bool) { - MFStyler.styleLabelB3(self, genericScaling: false) - setScale(scale) + setFontStyle(.B3, scale) } @objc public func styleB20(_ scale: Bool) { - MFStyler.styleLabelB20(self, genericScaling: false) - setScale(scale) + setFontStyle(.B20, 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 - } +} - - @objc public func updateView(_ size: CGFloat) { - 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 - } - } - +// Mark: - Old Helpers +extension Label { /** 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. @@ -693,7 +386,7 @@ public typealias ActionBlock = () -> () self.attributedText = mutableString } - + /* Retrieves an NSTextAttachment for NSAttributedString that is prepped to be inserted with the text. @@ -709,47 +402,6 @@ public typealias ActionBlock = () -> () 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 { guard let abstractContainer = label.abstractTextContainer() else { return CGRect() } @@ -793,25 +445,11 @@ public typealias ActionBlock = () -> () return (textContainer, layoutManager, textStorage) } - } // MARK: - Atomization 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 horizontalAlignment() -> UIStackView.Alignment { .leading } @@ -821,19 +459,25 @@ extension Label { // MARK: - Multi-Link Functionality extension Label { - - /// Applied to existing text. Removes underlines of tappable links and assoated actionable clauses. - @objc public func clearActionableClauses() { - guard let attributedText = attributedText else { return } - let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) - - clauses.forEach { clause in - mutableAttributedString.removeAttribute(NSAttributedString.Key.underlineStyle, range: clause.range) + + /// Underlines the tappable region and stores the tap logic for interation. + private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) { + var textLink = ActionLabelAttribute(location: range.location, length: range.length) + textLink.subscriber = textLink + .action + .sink { _ in + actionBlock() + } + if var attributes { + attributes.append(textLink) + setNeedsUpdate() + } else { + attributes = [textLink] } - - self.attributedText = mutableAttributedString - accessibilityElements = [] - clauses = [] + } + + @objc public func clearActionableClauses() { + attributes = attributes?.filter { !($0 is (any ActionLabelAttributeModel)) } } 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. @@ -902,113 +528,6 @@ extension Label { 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 } + diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index 19284756..fe37a2c0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -5,7 +5,7 @@ // Created by Suresh, Kamlesh on 10/3/19. // Copyright © 2019 Suresh, Kamlesh. All rights reserved. // - +import VDS @objcMembers open class LabelModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -30,6 +30,7 @@ public var numberOfLines: Int? public var shouldMaskRecordedView: Bool? = false public var accessibilityTraits: UIAccessibilityTraits? + public var inverted: Bool = false //-------------------------------------------------- // MARK: - Keys @@ -49,6 +50,7 @@ case attributes case html case hero + case inverted case makeWholeViewClickable case numberOfLines case shouldMaskRecordedView @@ -97,6 +99,7 @@ attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes) html = try typeContainer.decodeIfPresent(String.self, forKey: .html) 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) numberOfLines = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfLines) shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? false @@ -123,9 +126,14 @@ try container.encodeModelsIfPresent(attributes, forKey: .attributes) try container.encodeIfPresent(html, forKey: .html) try container.encodeIfPresent(hero, forKey: .hero) + try container.encodeIfPresent(inverted, forKey: .inverted) try container.encodeIfPresent(makeWholeViewClickable, forKey: .makeWholeViewClickable) try container.encodeIfPresent(numberOfLines, forKey: .numberOfLines) try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) } } + +extension LabelModel { + public var surface: Surface { inverted ? .dark : .light } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift b/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift index b41d9eef..7cc9747e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift @@ -68,23 +68,35 @@ open class TileletModel: MoleculeModelProtocol { public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? { guard let title else { return nil } 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) { - return .init(text: title.text, textAttributes: attrs, standardStyle: standardStyle) - } else { - return .init(text: title.text, textAttributes: attrs) - } - } + + do { + if let style = title.fontStyle { + return .init(text: title.text, + 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? { guard let subTitle else { return nil } let attrs = subTitle.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) - let style: TextStyle? = subTitle.fontStyle?.vdsTextStyle() - if let style, let standardStyle = Tilelet.SubTitleModel.StandardStyle(rawValue: style.toStandardStyle().rawValue) { - return .init(text: subTitle.text, textAttributes: attrs, standardStyle: standardStyle) - } else { - return .init(text: subTitle.text, textAttributes: attrs) - } + do { + if let style = subTitle.fontStyle { + return .init(text: subTitle.text, + otherStandardStyle: try style.vdsSubsetStyle(), + 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 { diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 8f184d5a..fa55423f 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -26,6 +26,7 @@ extension VDS.Tabs.Size: Codable {} extension VDS.TextLink.Size: Codable {} extension VDS.TextLinkCaret.IconPosition: Codable {} extension VDS.TileContainer.AspectRatio: Codable {} +extension VDS.TitleLockup.TextAlignment: Codable {} extension VDS.Tooltip.FillColor: Codable {} extension VDS.Tooltip.Size: Codable {} extension VDS.Line.Style: Codable {} diff --git a/MVMCoreUI/Atomic/Extensions/VDS-TextStyle.swift b/MVMCoreUI/Atomic/Extensions/VDS-TextStyle.swift index 09fc5dcd..f7e5917e 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-TextStyle.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-TextStyle.swift @@ -12,20 +12,54 @@ import VDS extension Styler.Font { //Converts Legacy Font to a 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() guard let style = VDS.TextStyle(rawValue: newRaw) else { return nil } return style } - public func vdsSubsetStyle() -> T? { - guard let style = vdsTextStyle() else { return nil } - guard let rawValue = style.rawValue as? T.RawValue, - let found = T(rawValue: rawValue) else { - print("Style: \(style.rawValue) is not in enum \(T.self)\ronly these cases exist:\r\(T.allCases)") - return nil + public func vdsSubsetStyle() throws -> T { + guard let style = vdsTextStyle(), let rawValue = style.toStandardStyle().rawValue as? T.RawValue, let standardStyle = T(rawValue: rawValue) else { + let err = "\(rawValue) was not found in the \(T.self), only these cases exist:\r\(T.allCases)" + throw MVMCoreError.errorObject(MVMCoreErrorObject(title: "\(T.self) conversion Issue", + messageToLog: err, + 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 + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/LockupsPlanSMLXL.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/LockupsPlanSMLXL.swift index c0d4c44f..5e990303 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/LockupsPlanSMLXL.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/LockupsPlanSMLXL.swift @@ -12,7 +12,7 @@ import Foundation //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- - public let planLabel = Label() + public let planLabel = Label(fontStyle: .BoldFeatureXLarge) public let headline = Label(fontStyle: .BoldTitleLarge) public let subHeadline = Label(fontStyle: .RegularTitleLarge) public let body = Label(fontStyle: .RegularBodySmall) @@ -33,8 +33,6 @@ import Foundation //------------------------------------------------------- open override func setupView() { super.setupView() - planLabel.font = MFStyler.getMVA3FontSize(96, bold: true) - planLabel.standardFontSize = 96 addSubview(stack) planLabel.setContentCompressionResistancePriority(.required, for: .horizontal) planLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) @@ -94,8 +92,7 @@ import Foundation open override func reset() { super.reset() stack.reset() - planLabel.font = MFStyler.getMVA3FontSize(96, bold: true) - planLabel.standardFontSize = 96 + planLabel.setFontStyle(.BoldFeatureXLarge) headline.setFontStyle(.BoldTitleLarge) subHeadline.setFontStyle(.RegularTitleLarge) body.setFontStyle(.RegularBodySmall) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift index b48e4ddb..6339b8e2 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift @@ -5,119 +5,43 @@ // Created by Nadigadda, Sumanth on 04/05/22. // 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) - public let subTitle = Label(fontStyle: .RegularBodySmall) - public lazy var stack: UIStackView = { - let stack = UIStackView(arrangedSubviews: [eyebrow, title, subTitle]) - stack.translatesAutoresizingMaskIntoConstraints = false - stack.axis = .vertical - return stack - }() - - var castModel: TitleLockupModel? { - get { return model as? TitleLockupModel } + //-------------------------------------------------- + // MARK: - Public Functions + //-------------------------------------------------- + open func viewModelDidUpdate() { + surface = viewModel.surface + textAlignment = viewModel.textAlignment + eyebrowModel = viewModel.eyebrowModel(delegateObject: delegateObject, additionalData: additionalData) + titleModel = viewModel.titleModel(delegateObject: delegateObject, additionalData: additionalData) + subTitleModel = viewModel.subTitleModel(delegateObject: delegateObject, additionalData: additionalData) } - + //-------------------------------------------------- // MARK: - Initialization //-------------------------------------------------- - public convenience init() { + public convenience required init() { 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 //-------------------------------------------------- - - 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? { + open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 65 } - //-------------------------------------------------- - // 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 - } + open func updateView(_ size: CGFloat) {} } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift index bb77c26a..3c9f25fd 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift @@ -7,6 +7,7 @@ // import VDSColorTokens +import VDS public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { @@ -18,47 +19,16 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco public var moleculeName: String = TitleLockupModel.identifier public var id: String = UUID().uuidString + public var textAlignment: TitleLockup.TextAlignment = .left public var eyebrow: LabelModel? public var title: LabelModel public var subTitle: LabelModel? + public var subTitleColor: Use = .primary - public var alignment: Alignment = .left { - didSet { - ///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 alignment: VDS.TitleLockup.TextAlignment = .left + public var inverted: Bool = false - public var inverted: Bool = false { - 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 backgroundColor: Color? public var children: [MoleculeModelProtocol] { [eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } @@ -78,58 +48,6 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco self.eyebrow = eyebrow self.title = title 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 { case id case moleculeName - case backgroundColor + case textAlignment case eyebrow case title case subTitle + case subTitleColor case inverted case alignment } @@ -154,72 +73,122 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) 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) eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) 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 } - if let invertedStatus = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { - inverted = invertedStatus + if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { + self.inverted = inverted + } else { + try setInverted(deprecatedFrom: decoder) } - - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - - updateLabelAttributes() + } - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(textAlignment, forKey: .textAlignment) try container.encodeIfPresent(eyebrow, forKey: .eyebrow) try container.encodeModel(title, forKey: .title) try container.encodeIfPresent(subTitle, forKey: .subTitle) + try container.encode(subTitleColor, forKey: .subTitleColor) try container.encode(alignment, forKey: .alignment) try container.encode(inverted, forKey: .inverted) - try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) } - //-------------------------------------------------- - // MARK: - Model updates - //-------------------------------------------------- + public func eyebrowModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.EyebrowModel? { + 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() { - // If subtitle style is not available, will set font style based on the component - if subTitle?.fontStyle == nil { - subTitle?.fontStyle = defaultSubtitleFontStyle() - } + } catch MVMCoreError.errorObject(let object) { + MVMCoreLoggingHandler.shared()?.addError(toLog: object) + } catch { } - // If eyebrow style is not available, will set font style based on the component. Eyebrow and subtitle share the same font size - if eyebrow?.fontStyle == nil { - eyebrow?.fontStyle = subTitle?.fontStyle - } + return .init(text: title.text, textAttributes: attrs, numberOfLines: title.numberOfLines ?? 0) + } + + 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 - if eyebrow?.textColor == nil { - eyebrow?.textColor = subTitleColor - } - if title.textColor == nil { - title.textColor = titleColor - } - 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 + do { + if let style = subTitle.fontStyle { + return .init(text: subTitle.text, + otherStandardStyle: try style.vdsSubsetStyle(), + textColor: subTitleColor, + textAttributes: attrs, + numberOfLines: subTitle.numberOfLines ?? 0) } + } 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 } } diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImage.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImage.swift index 74543d21..d3a346fc 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImage.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImage.swift @@ -76,8 +76,8 @@ import UIKit private func setDefaultState() { - headlineBodyButton.headlineBody.headlineLabel.font = MFStyler.fontBoldTitleMedium() - headlineBodyButton.headlineBody.messageLabel.font = MFStyler.fontRegularMicro() + headlineBodyButton.headlineBody.headlineLabel.setFontStyle(.BoldTitleMedium) + headlineBodyButton.headlineBody.messageLabel.setFontStyle(.RegularMicro) imageLoader.imageView.contentMode = .scaleAspectFit imageLoader.addSizeConstraintsForAspectRatio = true buttonHeaderPadding = PaddingTwo diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift index 5611984f..98fbb779 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift @@ -14,7 +14,7 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - public let label = Label(fontStyle: .BoldBodySmall) + public let label = Label().with { $0.font = Styler.Font.BoldFeatureXLarge.getFont() } public let toggle = Toggle() //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift index d7fced36..893b73d8 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift @@ -62,8 +62,8 @@ private func defaultState() { - headlineBody.headlineLabel.font = Styler.Font.BoldTitleMedium.getFont() - headlineBody.messageLabel.font = Styler.Font.RegularMicro.getFont() + headlineBody.headlineLabel.setFontStyle(.BoldTitleMedium) + headlineBody.messageLabel.setFontStyle(.RegularMicro) button.use = .secondary button.isHidden = false buttonHeadlinePadding = PaddingTwo diff --git a/MVMCoreUI/Styles/Styler.swift b/MVMCoreUI/Styles/Styler.swift index 2daac86a..1f147241 100644 --- a/MVMCoreUI/Styles/Styler.swift +++ b/MVMCoreUI/Styles/Styler.swift @@ -57,59 +57,6 @@ open class Styler { case B2 // Maps to RegularBodySmall 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 { switch self { case .B3: @@ -148,8 +95,8 @@ open class Styler { /// Returns the font based on the declared enum case. public func getFont(_ genericScaling: Bool = true) -> UIFont { - let size = genericScaling ? sizeFontGeneric(forCurrentDevice: pointSize()) : pointSize() - return MFStyler.getFontFor(size: size, isBold: isBold()) + let vdsStyle = vdsTextStyle() ?? .defaultStyle + return vdsStyle.font } /// 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. @objc static func getFontFor(styleString: String, scaleValue: CGFloat) -> UIFont? { 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 } return getFontFor(size: newSize, isBold: font.isBold()) }