diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index a7cad899..b42741cc 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -570,6 +570,8 @@ DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391A224421A0001AB423 /* CaretLink.swift */; }; DBEFFA04225A829700230692 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB891E822253FA8500022516 /* Label.swift */; }; EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; }; + EA05EFA9278DDE2C00828819 /* ClearFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */; }; + EA05EFAB278DE53600828819 /* ClearableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */; }; EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; }; EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */; }; @@ -577,8 +579,8 @@ EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */; }; EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */; }; EAA0CFB3275E831E00D65EB0 /* DisableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */; }; - EABFC1412763BB8D00E78B40 /* StateLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC1402763BB8D00E78B40 /* StateLabel.swift */; }; - EABFC152276913E800E78B40 /* StateLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC151276913E800E78B40 /* StateLabelModel.swift */; }; + EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC1402763BB8D00E78B40 /* FormLabel.swift */; }; + EABFC152276913E800E78B40 /* FormLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC151276913E800E78B40 /* FormLabelModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -1148,6 +1150,8 @@ DBC4391722442197001AB423 /* DashLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashLine.swift; sourceTree = ""; }; DBC4391A224421A0001AB423 /* CaretLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretLink.swift; sourceTree = ""; }; EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = ""; }; + EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearFormFieldEffectModel.swift; sourceTree = ""; }; + EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearableModelProtocol.swift; sourceTree = ""; }; EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = ""; }; EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = ""; }; EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableFormFieldEffectModel.swift; sourceTree = ""; }; @@ -1155,8 +1159,8 @@ EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldEffectProtocol.swift; sourceTree = ""; }; EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideFormFieldEffectModel.swift; sourceTree = ""; }; EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableFormFieldEffectModel.swift; sourceTree = ""; }; - EABFC1402763BB8D00E78B40 /* StateLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateLabel.swift; sourceTree = ""; }; - EABFC151276913E800E78B40 /* StateLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateLabelModel.swift; sourceTree = ""; }; + EABFC1402763BB8D00E78B40 /* FormLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabel.swift; sourceTree = ""; }; + EABFC151276913E800E78B40 /* FormLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabelModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1181,6 +1185,7 @@ D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */, 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */, 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */, + EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */, 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */, 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */, D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, @@ -1446,8 +1451,8 @@ 94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */, 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */, DB891E822253FA8500022516 /* Label.swift */, - EABFC151276913E800E78B40 /* StateLabelModel.swift */, - EABFC1402763BB8D00E78B40 /* StateLabel.swift */, + EABFC151276913E800E78B40 /* FormLabelModel.swift */, + EABFC1402763BB8D00E78B40 /* FormLabel.swift */, ); path = Label; sourceTree = ""; @@ -2428,6 +2433,7 @@ EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */, EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */, EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */, + EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */, ); path = FormFieldEffect; sourceTree = ""; @@ -2570,7 +2576,7 @@ 324FB6AA249366F3002552C7 /* ListLeftVariableNumberedListBodyTextModel.swift in Sources */, 5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */, AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */, - EABFC152276913E800E78B40 /* StateLabelModel.swift in Sources */, + EABFC152276913E800E78B40 /* FormLabelModel.swift in Sources */, 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */, 0AF60F0926B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift in Sources */, 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */, @@ -2851,6 +2857,7 @@ D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, 011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */, D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */, + EA05EFAB278DE53600828819 /* ClearableModelProtocol.swift in Sources */, 8D448E5524050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift in Sources */, BBC0C4FD24811DBC0087C44F /* Tag.swift in Sources */, 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */, @@ -2995,7 +3002,7 @@ 013F801923FB4A8E00AD8013 /* UIContentMode+Extension.swift in Sources */, AA104AC724472DB0004D2810 /* HeadersH1Button.swift in Sources */, 525239C22407BD1000454969 /* ListTwoColumnPriceDetails.swift in Sources */, - EABFC1412763BB8D00E78B40 /* StateLabel.swift in Sources */, + EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */, AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */, @@ -3017,6 +3024,7 @@ 52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */, D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */, 0AA4D2E125CAEC72008DB32D /* AccessibilityModelProtocol.swift in Sources */, + EA05EFA9278DDE2C00828819 /* ClearFormFieldEffectModel.swift in Sources */, C003506123AA94CD00B6AC29 /* Button.swift in Sources */, DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */, D29C559025C095210082E7D6 /* Video.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift index d9b42a41..6e8be5f1 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift @@ -44,7 +44,7 @@ import UIKit super.isEnabled = enabled } } - + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift index 147f6f5f..4b84e8a1 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift @@ -17,8 +17,8 @@ import UIKit //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- - public private(set) var titleLabel: StateLabel = { - let label = StateLabel() + public private(set) var titleLabel: FormLabel = { + let label = FormLabel() label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() @@ -26,12 +26,33 @@ import UIKit public private(set) var entryFieldContainer = EntryFieldContainer() /// Provides contextual information on the TextField. - public private(set) var feedbackLabel: StateLabel = { - let label = StateLabel() + public private(set) var feedbackLabel: FormLabel = { + let label = FormLabel() label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() - + + public private(set) var errorLabel: Label = { + let label = Label() + label.setFontStyle(.RegularMicro) + label.textColor = .mvmBlack + label.setContentCompressionResistancePriority(.required, for: .vertical) + return label + }() + + public lazy var stack: UIStackView = { + errorLabel.isHidden = true + let stack = UIStackView(arrangedSubviews: [titleLabel, entryFieldContainer, errorLabel, feedbackLabel]) + stack.axis = .vertical + stack.alignment = .fill + stack.distribution = .fill + stack.setCustomSpacing(Padding.One, after: titleLabel) + stack.setCustomSpacing(Padding.Two, after: entryFieldContainer) + stack.setCustomSpacing(Padding.One, after: errorLabel) + stack.translatesAutoresizingMaskIntoConstraints = false + return stack + }() + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -59,21 +80,26 @@ import UIKit feedbackLabel.isEnabled = enabled entryFieldContainer.isEnabled = enabled entryFieldModel?.enabled = enabled - if !enabled { - self.text = defaultText - } } } } + /// Toggles enabled (original) or disabled UI. + public var isRequired: Bool = true { + didSet{ + titleLabel.isRequired = isRequired + } + } + /// Toggles error or original UI. public var showError: Bool { get { entryFieldContainer.showError } set (error) { if error { - feedbackLabel.showError(message: self.errorMessage ?? "") + errorLabel.text = self.errorMessage ?? "" + errorLabel.isHidden = false } else { - feedbackLabel.reset() + errorLabel.isHidden = true } entryFieldContainer.showError = error entryFieldModel?.showError = error @@ -130,25 +156,6 @@ import UIKit model as? EntryFieldModel } - ///This is the value of the entryFieldModel.text on set - ///This is used to update the text value on a reset or when enabled is set to false - private var defaultText: String = "" - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - public var entryFieldContainerLeading: NSLayoutConstraint? - public var entryFieldContainerTrailing: NSLayoutConstraint? - - public var feedbackLabelTrailing: NSLayoutConstraint? - public var feedbackLabelLeading: NSLayoutConstraint? - - public var titleLabelLeading: NSLayoutConstraint? - public var titleLabelTrailing: NSLayoutConstraint? - - public var titleContainerDistance: NSLayoutConstraint? - public var feedbackContainerDistance: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -188,37 +195,18 @@ import UIKit isAccessibilityElement = false setContentCompressionResistancePriority(.required, for: .vertical) - accessibilityElements = [titleLabel, feedbackLabel] + accessibilityElements = [titleLabel, errorLabel, feedbackLabel] backgroundColor = .mvmWhite - - addSubview(titleLabel) - - titleLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true - titleLabelLeading = titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - titleLabelLeading?.isActive = true - titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor) - titleLabelLeading?.isActive = true - - addSubview(entryFieldContainer) + + addSubview(stack) + entryFieldContainer.setContentCompressionResistancePriority(.required, for: .vertical) setupFieldContainerContent(entryFieldContainer) - - titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: Padding.One) - titleContainerDistance?.isActive = true - entryFieldContainerLeading = entryFieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - entryFieldContainerLeading?.isActive = true - entryFieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryFieldContainer.trailingAnchor) - entryFieldContainerTrailing?.isActive = true - - addSubview(feedbackLabel) - - feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryFieldContainer.bottomAnchor, constant: Padding.Two) - feedbackContainerDistance?.isActive = true - feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - feedbackLabelLeading?.isActive = true - feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) - feedbackLabelTrailing?.isActive = true - layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true + + stack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + stack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: stack.trailingAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true } @objc open override func layoutSubviews() { @@ -234,10 +222,7 @@ import UIKit @objc open override func updateView(_ size: CGFloat) { super.updateView(size) - - titleLabel.updateView(size) - feedbackLabel.updateView(size) - entryFieldContainer.updateView(size) + stack.updateView(size) } //-------------------------------------------------- @@ -295,6 +280,9 @@ import UIKit titleLabel.textColor = .mvmBlack feedbackLabel.font = Styler.Font.RegularMicro.getFont() feedbackLabel.textColor = .mvmBlack + errorLabel.font = Styler.Font.RegularMicro.getFont() + errorLabel.textColor = .mvmBlack + errorLabel.text = nil entryFieldContainer.disableAllBorders = false feedbackLabel.text = nil entryFieldContainer.reset() @@ -313,8 +301,8 @@ import UIKit titleLabel.setup(model: model.titleStateLabel, delegateObject, additionalData) feedbackLabel.setup(model: model.feedbackStateLabel, delegateObject, additionalData) - defaultText = model.text ?? "" - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly + isRequired = model.required model.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { guard let self = self else { return } @@ -326,6 +314,7 @@ import UIKit self.showError = false } self.isEnabled = model.enabled + self.text = model.text }) } @@ -376,8 +365,8 @@ extension EntryField { } extension LabelModel { - public convenience init(fontStyle: Styler.Font, textColor: Color) { - self.init(text: "") + public convenience init(text: String = "", fontStyle: Styler.Font, textColor: Color) { + self.init(text: text) self.fontStyle = fontStyle self.textColor = textColor } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift index 9520406f..3fed121a 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift @@ -9,7 +9,7 @@ import Foundation -@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol { +@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol, ClearableModelProtocol { //-------------------------------------------------- // MARK: - Properties @@ -29,6 +29,8 @@ import Foundation public var errorMessage: String? public var errorTextColor: Color? public var enabled: Bool = true + public var required: Bool = true + public var readOnly: Bool = false public var showError: Bool? public var hideBorders = false public var locked: Bool? @@ -38,20 +40,12 @@ import Foundation public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? public var wasInitiallySelected: Bool = false - - //text only - //Used for re-encoding what was decoded - private var title: String? - private var feedback: String? - - //label models - //Used for re-encoding what was decoded - private var titleLabel: LabelModel? - private var feedbackLabel: LabelModel? + public var title: String? + public var feedback: String? //used to drive the EntryFieldView UI - public var titleStateLabel: StateLabelModel - public var feedbackStateLabel: StateLabelModel + public var titleStateLabel: FormLabelModel + public var feedbackStateLabel: FormLabelModel public var isValid: Bool? = true { didSet { updateUI?() } @@ -73,6 +67,7 @@ import Foundation case accessibilityIdentifier case title case enabled + case readOnly case feedback case errorMessage case errorTextColor @@ -83,8 +78,7 @@ import Foundation case text case fieldKey case groupName - case titleLabel - case feedbackLabel + case required } //-------------------------------------------------- @@ -117,8 +111,15 @@ import Foundation public init(with text: String) { self.text = text baseValue = text - self.titleStateLabel = StateLabelModel(text: "") - self.feedbackStateLabel = StateLabelModel(text: "") + self.titleStateLabel = FormLabelModel(text: "") + self.feedbackStateLabel = FormLabelModel(text: "") + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public func clear() { + self.text = "" } //-------------------------------------------------- @@ -134,30 +135,21 @@ import Foundation errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor) enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false locked = try typeContainer.decodeIfPresent(Bool.self, forKey: .locked) selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) text = try typeContainer.decodeIfPresent(String.self, forKey: .text) hideBorders = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideBorders) ?? false baseValue = text fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - titleLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .titleLabel) - feedbackLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .feedbackLabel) if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } - - //Setup the stateLabelModels - if let titleLabel = titleLabel { - self.titleStateLabel = StateLabelModel(model: titleLabel) - } else { - self.titleStateLabel = StateLabelModel(text: title ?? "") - } - - if let feedBackLabel = feedbackLabel { - self.feedbackStateLabel = StateLabelModel(model: feedBackLabel) - } else { //feedback is the model for the error - self.feedbackStateLabel = StateLabelModel(text: feedback ?? "") - } + self.titleStateLabel = FormLabelModel(text: title ?? "") + self.feedbackStateLabel = FormLabelModel(model: LabelModel(text: feedback ?? "", + fontStyle: FormLabelModel.defaultFontStyle, + textColor: Color(uiColor: .mvmCoolGray6))) } public func encode(to encoder: Encoder) throws { @@ -166,9 +158,7 @@ import Foundation try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encodeIfPresent(title, forKey: .title) - try container.encodeIfPresent(titleLabel, forKey: .titleLabel) try container.encodeIfPresent(feedback, forKey: .feedback) - try container.encodeIfPresent(feedbackLabel, forKey: .feedbackLabel) try container.encodeIfPresent(text, forKey: .text) try container.encodeIfPresent(locked, forKey: .locked) try container.encodeIfPresent(showError, forKey: .showError) @@ -178,7 +168,9 @@ import Foundation try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encode(readOnly, forKey: .readOnly) try container.encode(enabled, forKey: .enabled) + try container.encode(required, forKey: .required) try container.encode(hideBorders, forKey: .hideBorders) } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 543469c1..3221f043 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -46,7 +46,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele if self.textView.isShowingPlaceholder { self.textView.textColor = self.textView.placeholderTextColor } else { - self.textView.textColor = (enabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor + self.textView.textColor = (self.textView.isEnabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor } } } @@ -282,8 +282,5 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele adjustMarginConstraints(constant: 0) } - if !model.enabled { - isEnabled = false - } } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift index 4481411e..4bd64c7c 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift @@ -95,7 +95,7 @@ import MVMCore } } } - + public var disabledBackgroundColor: UIColor = .clear public var disabledBorderColor: UIColor = .mvmCoolGray3 public var disabledCheckColor: UIColor = .mvmCoolGray3 @@ -430,7 +430,7 @@ import MVMCore }) } - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly if (model.action != nil || model.offAction != nil) { actionBlock = { [weak self] in diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift index 69f026ef..063c322a 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift @@ -23,7 +23,8 @@ public var backgroundColor: Color? public var accessibilityIdentifier: String? public var selected: Bool = false - public var enabled: Bool = true + public var enabled: Bool = true + public var readOnly: Bool = false public var animated: Bool = true public var inverted: Bool = false public var round: Bool = false @@ -54,6 +55,7 @@ case accessibilityIdentifier case checked case enabled + case readOnly case inverted case animated case round @@ -158,10 +160,8 @@ self.inverted = inverted } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false action = try typeContainer.decodeModelIfPresent(codingKey: .action) fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) @@ -191,7 +191,8 @@ try container.encodeIfPresent(disabledCheckColor, forKey: .disabledCheckColor) try container.encodeIfPresent(animated, forKey: .animated) try container.encodeIfPresent(round, forKey: .round) - try container.encodeIfPresent(enabled, forKey: .enabled) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) try container.encodeModelIfPresent(action, forKey: .action) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeModelIfPresent(offAction, forKey: .offAction) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift index e694e194..eb0b0b4c 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift @@ -62,7 +62,7 @@ import UIKit heartPath.close() heart.path = heartPath.cgPath heart.fillColor = isSelected ? heartModel?.activeColor.cgColor : heartModel?.inActiveColor.cgColor - heart.opacity = 1.0 + heart.opacity = isEnabled ? 1.0 : 0.5 heart.lineWidth = 1 heart.strokeColor = isSelected ? UIColor.clear.cgColor : UIColor.mvmBlack.cgColor return heart @@ -88,7 +88,7 @@ import UIKit self.additionalData = additionalData guard let model = model as? HeartModel else { return } isSelected = model.isActive - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly updateAccessibilityLabel() } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift index aea065ef..c03a7302 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift @@ -21,8 +21,8 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { public var inActiveColor: Color = Color(uiColor: .clear) public var action: ActionModelProtocol = ActionNoopModel() public var enabled: Bool = true - - //-------------------------------------------------- + public var readOnly: Bool = false + // MARK: - Keys //-------------------------------------------------- @@ -35,6 +35,7 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { case inActiveColor case action case enabled + case readOnly } //-------------------------------------------------- @@ -61,9 +62,8 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) { self.action = action } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false } public func encode(to encoder: Encoder) throws { @@ -76,5 +76,6 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { try container.encode(inActiveColor, forKey: .inActiveColor) try container.encodeModel(action, forKey: .action) try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift index 1d8c6960..27323048 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -39,7 +39,7 @@ open class RadioBox: Control, MFButtonProtocol { public override var isEnabled: Bool { didSet { updateAccessibility() } } - + //-------------------------------------------------- // MARK: - MVMCoreViewProtocol //-------------------------------------------------- @@ -89,7 +89,7 @@ open class RadioBox: Control, MFButtonProtocol { accentColor = color } isSelected = model.selected - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly } open override func reset() { diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift index f1b22dc3..fc299b50 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift @@ -19,6 +19,7 @@ public var selectedAccentColor: Color? public var selected: Bool = false public var enabled: Bool = true + public var readOnly: Bool = false public var strikethrough: Bool = false public var fieldValue: String? public var action: ActionModelProtocol? @@ -39,6 +40,7 @@ case strikethrough case fieldValue case action + case readOnly } //-------------------------------------------------- @@ -57,9 +59,8 @@ selected = isSelected } - if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - enabled = isEnabled - } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { strikethrough = isStrikeTrough @@ -79,6 +80,7 @@ try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encode(selected, forKey: .selected) try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeModelIfPresent(action, forKey: .action) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift index 1614f3ec..7e919553 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift @@ -21,7 +21,7 @@ public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? public var enabled: Bool = true - + public var readOnly: Bool = false //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -42,6 +42,7 @@ private enum CodingKeys: String, CodingKey { case moleculeName case enabled + case readOnly case selectedAccentColor case backgroundColor case accessibilityIdentifier @@ -66,9 +67,8 @@ if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false baseValue = formFieldValue() } @@ -81,5 +81,7 @@ try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encode(groupName, forKey: .groupName) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift index c6e1e049..1854f943 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift @@ -66,7 +66,7 @@ import UIKit open override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } - let color = isEnabled ? enabledColor.cgColor : disabledColor.cgColor + let color = isEnabled == false ? disabledColor.cgColor : enabledColor.cgColor layer.cornerRadius = bounds.width * 0.5 layer.borderColor = color layer.borderWidth = bounds.width * 0.0333 @@ -163,7 +163,7 @@ import UIKit guard let model = model as? RadioButtonModel else { return } isSelected = model.state - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift index 445fd2a3..79fcb9cb 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift @@ -19,6 +19,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { public var accessibilityIdentifier: String? public var state: Bool = false public var enabled: Bool = true + public var readOnly: Bool = false /// The specific value to send to server. TODO: update this to be more generic. public var fieldValue: String? @@ -42,6 +43,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { case fieldKey case groupName case action + case readOnly } //-------------------------------------------------- @@ -72,11 +74,9 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { self.state = state } - - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - + + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) @@ -96,6 +96,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { try container.encode(moleculeName, forKey: .moleculeName) try container.encode(state, forKey: .state) try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift index 41f709c0..da7ef0c2 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift @@ -18,7 +18,8 @@ private var selectedRadioButtonModel: RadioButtonModel? public var baseValue: AnyHashable? public var enabled: Bool = true - + public var readOnly: Bool = false + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -41,6 +42,7 @@ radioButton.isSelected = false } self.enabled = radioButtonModel.enabled + self.readOnly = radioButtonModel.readOnly } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift index e80f8813..eb569810 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift @@ -38,7 +38,7 @@ open class RadioSwatch: Control, MFButtonProtocol { updateAccessibility() } } - + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -64,7 +64,7 @@ open class RadioSwatch: Control, MFButtonProtocol { self.additionalData = additionalData bottomText.text = model.text isSelected = model.selected - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly } public override func reset() { diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift index 100e2311..059dd2c8 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift @@ -19,6 +19,7 @@ public var text: String? public var selected: Bool = false public var enabled: Bool = true + public var readOnly: Bool = false public var strikethrough: Bool = false public var fieldValue: String? public var action: ActionModelProtocol? @@ -38,6 +39,7 @@ case strikethrough case fieldValue case action + case readOnly } //-------------------------------------------------- @@ -58,17 +60,15 @@ if let selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { self.selected = selected } - - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - + if let strikethrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { self.strikethrough = strikethrough } fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) action = try typeContainer.decodeModelIfPresent(codingKey: .action) + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false } public func encode(to encoder: Encoder) throws { @@ -83,5 +83,6 @@ try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeModelIfPresent(action, forKey: .action) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift index 3fb6ec19..7e475081 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift @@ -20,7 +20,8 @@ public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? public var enabled: Bool = true - + public var readOnly: Bool = false + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -46,6 +47,7 @@ case fieldKey case groupName case enabled + case readOnly } //-------------------------------------------------- @@ -61,9 +63,8 @@ if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false baseValue = formFieldValue() } @@ -75,5 +76,7 @@ try container.encode(swatches, forKey: .swatches) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encode(groupName, forKey: .groupName) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index 3513a03b..04c34da2 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -66,7 +66,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: isEnabled ? "AccToggleHint" : "AccDisabled") } } - + /// Simple means to prevent user interaction with the toggle. public var isLocked: Bool = false { didSet { isUserInteractionEnabled = !isLocked } @@ -384,7 +384,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) isOn = model.selected changeStateNoAnimation(isOn) isAnimated = model.animated - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly if let accessibileString = model.accessibilityText { accessibilityLabel = accessibileString diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index 22961e3f..4c860342 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -18,6 +18,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { public var selected: Bool = false public var animated: Bool = true public var enabled: Bool = true + public var readOnly: Bool = false public var action: ActionModelProtocol? public var alternateAction: ActionModelProtocol? public var accessibilityText: String? @@ -39,6 +40,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { case state case animated case enabled + case readOnly case action case backgroundColor case accessibilityIdentifier @@ -81,10 +83,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { self.selected = state } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) { self.animated = animated } @@ -117,6 +115,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false } public func encode(to encoder: Encoder) throws { @@ -136,5 +136,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift index 975c7ebb..381b8112 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift @@ -79,7 +79,7 @@ open class BarsIndicatorView: CarouselIndicator { } } } - + /// Colors the currently selected index, unique from other indicators public var currentIndicatorColor: UIColor { get { barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift index eb3a8ebd..f07fe123 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift @@ -44,7 +44,7 @@ open class NumericIndicatorView: CarouselIndicator { open override var isEnabled: Bool { didSet { setViewColor(isEnabled ? indicatorColor : disabledIndicatorColor) } } - + /// Sets the color for pageCount text, left arrow and right arrow. public override var indicatorColor: UIColor { get { super.indicatorColor } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/StateLabel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift similarity index 61% rename from MVMCoreUI/Atomic/Atoms/Views/Label/StateLabel.swift rename to MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift index d20755c7..08b7267e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/StateLabel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift @@ -9,35 +9,31 @@ import Foundation /// Subclass of label that helps with different states -public class StateLabel: 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 - private var stateModel: StateLabelModel! + private var formModel: FormLabelModel! //public properties public override var isEnabled: Bool { didSet{ - self.state = isEnabled ? .enabled : .disabled + self.formModel.enabled = isEnabled + self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData) + } + } + + public var isRequired: Bool = true { + didSet{ + self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData) } } public override func reset(){ super.reset() - self.state = .enabled - } - - //current mode of label - public var state: StateLabelModel.State { - get { - return self.stateModel.state - } - set { - self.stateModel.state = newValue - self.set(with: stateModel.model, delegateObject, additionalData) - } + self.isEnabled = true } /// Used in setting up the Label for use @@ -46,25 +42,18 @@ public class StateLabel: Label { /// - model: Model takes priority over a text value. The model has its own text value that will be looked at to draw the screen, this model is used for both enabled/disabled models /// - delegateObject: passed in from the creator /// - additionalData: passed in from the creator - public func setup(model: StateLabelModel, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?){ + public func setup(model: FormLabelModel, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?){ self.additionalData = additionalData self.delegateObject = delegateObject - self.stateModel = model + self.formModel = model //default to enabled state self.reset() } - /// Use this to switch the label into a error state - /// - Parameter message: message to show in the errorModel - public func showError(message: String){ - self.stateModel.set(text: message, for: .error) - self.state = .error - } - /// Text change that will update both enabledModel and disabledModel text values /// - Parameter text: text you want to see public func set(text: String?){ - self.stateModel.set(text: text ?? "", for: .enabled) + self.formModel.set(text: text ?? "") } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/StateLabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift similarity index 55% rename from MVMCoreUI/Atomic/Atoms/Views/Label/StateLabelModel.swift rename to MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift index ae6bc317..c7552047 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/StateLabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift @@ -8,37 +8,23 @@ import Foundation -public class StateLabelModel { +public class FormLabelModel: EnableableModelProtocol { static let defaultFontStyle = Styler.Font.RegularMicro static let defaultEnabledTextColor = Color(uiColor: .mvmBlack) static let defaultDisabledTextColor = Color(uiColor: .mvmCoolGray3) - static let defaultErrorTextColor = Color(uiColor: .mvmBlack) + static let defaultRequiredTextColor = Color(uiColor: .mvmCoolGray6) private var enabledModel: LabelModel - private var disabledModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultDisabledTextColor) - private var errorLabelModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultErrorTextColor) + private var disabledModel = LabelModel(fontStyle: FormLabelModel.defaultFontStyle, textColor: FormLabelModel.defaultDisabledTextColor) - public enum State { - case enabled - case disabled - case error - } - //current state - public var state: State = .enabled + public var enabled: Bool = true //model is based on current state public var model: LabelModel { - switch state { - case .enabled: - return enabledModel - case .disabled: - return disabledModel - case .error: - return errorLabelModel - } + return enabled ? enabledModel: disabledModel } - + //init public init(model: LabelModel){ @@ -48,13 +34,13 @@ public class StateLabelModel { if let modelFontStyle = model.fontStyle { self.disabledModel.fontStyle = modelFontStyle } else { - model.fontStyle = StateLabelModel.defaultFontStyle + model.fontStyle = FormLabelModel.defaultFontStyle } //ensure the textColor is set //otherwise use the defaultEnabledTextColor if model.textColor == nil { - model.textColor = StateLabelModel.defaultEnabledTextColor + model.textColor = FormLabelModel.defaultEnabledTextColor } //set the enabledModel to the model passed in @@ -66,26 +52,30 @@ public class StateLabelModel { public init(text: String){ //create the enabled model - self.enabledModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultEnabledTextColor) + self.enabledModel = LabelModel(fontStyle: FormLabelModel.defaultFontStyle, textColor: FormLabelModel.defaultEnabledTextColor) self.enabledModel.text = text //make sure the enabled & disabled text match self.disabledModel.text = self.enabledModel.text } + public var requiredModel: LabelModel { + let required = LabelModel(fontStyle: model.fontStyle!, textColor: model.textColor!) + if enabled { + required.attributes = [LabelAttributeColorModel(FormLabelModel.defaultRequiredTextColor, model.text.count + 1, 8)] + } + required.text = "\(model.text) Optional" + return required + } + //methods public func reset(){ - self.state = .enabled + self.enabled = true } //set text for state - public func set(text:String, for state: State){ - switch state { - case .enabled, .disabled: - self.enabledModel.text = text - self.disabledModel.text = text - case .error: - self.errorLabelModel.text = text - } + public func set(text:String){ + self.enabledModel.text = text + self.disabledModel.text = text } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift index 94579d02..8fa1aa58 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift @@ -24,10 +24,14 @@ case textColor } + public init(_ textColor: Color?, _ location: Int, _ length: Int){ + self.textColor = textColor + super.init(location, length) + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- - required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index f2122901..9858190d 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -20,6 +20,7 @@ public var analyticsData: JSONValueDictionary? public var fieldValue: String? public var enabled: Bool = true + public var readOnly: Bool = false public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? @@ -40,6 +41,8 @@ case fieldValue case fieldKey case groupName + case enabled + case readOnly } //-------------------------------------------------- @@ -57,6 +60,12 @@ self.groupName = groupName } baseValue = fieldValue + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) { + self.readOnly = readOnly + } try super.init(from: decoder) } @@ -67,5 +76,7 @@ try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) try container.encodeIfPresent(analyticsData, forKey: .analyticsData) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index 35d29020..857984cf 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -38,7 +38,8 @@ import UIKit public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var enabled: Bool = true - + public var readOnly: Bool = false + public var selectable = false public var selectedIndex: Int? @@ -87,6 +88,8 @@ import UIKit case fieldKey case selectable case selectedIndex + case enabled + case readOnly } //-------------------------------------------------- @@ -120,10 +123,16 @@ import UIKit if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) { + self.readOnly = readOnly + } baseValue = formFieldValue() - } - - public func encode(to encoder: Encoder) throws { + } + + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) @@ -145,6 +154,8 @@ import UIKit try container.encode(index, forKey: .index) try container.encode(selectable, forKey: .selectable) try container.encode(selectedIndex, forKey: .selectedIndex) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ClearableModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ClearableModelProtocol.swift new file mode 100644 index 00000000..046e23d5 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ClearableModelProtocol.swift @@ -0,0 +1,13 @@ +// +// ClearableModelProtocol.swift +// MVMCoreUI +// +// Created by Matt Bruce on 1/11/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ClearableModelProtocol { + func clear() +} diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index a8fde2e5..865e3a8e 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -13,7 +13,6 @@ import UIKit //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var model: MoleculeModelProtocol? private var initialSetupPerformed = false diff --git a/MVMCoreUI/FormUIHelpers/FormFieldEffect/ClearFormFieldEffectModel.swift b/MVMCoreUI/FormUIHelpers/FormFieldEffect/ClearFormFieldEffectModel.swift new file mode 100644 index 00000000..173c0e33 --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/FormFieldEffect/ClearFormFieldEffectModel.swift @@ -0,0 +1,64 @@ +// +// ClearFormFieldEffectModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 1/11/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ClearFormFieldEffectModel: FormFieldEffectProtocol { + + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "clearFormFieldEffect" + public var fieldKey: String = "" + public var activatedRuleIds: [String]? + public var rules: [RulesProtocol] + + init(_ fieldKey: String, activatedRuleIds: [String], rules: [RulesProtocol]) { + self.fieldKey = fieldKey + self.activatedRuleIds = activatedRuleIds + self.rules = rules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case fieldKey + case activatedRuleIds + case rules + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey) + self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds) + self.rules = try typeContainer.decodeModels(codingKey: .rules) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(activatedRuleIds, forKey: .activatedRuleIds) + try container.encodeModels(rules, forKey: .rules) + } + + public func setEffect(validity: Bool, field: FormFieldProtocol,form: FormValidator, group: FormGroupRule) { + guard let field = field as? ClearableModelProtocol, validity else { return } + field.clear() + if let updateField = field as? UIUpdatableModelProtocol { + updateField.updateUI?() + } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift index afdde887..0a501440 100644 --- a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift @@ -16,11 +16,18 @@ public protocol FormFieldProtocol: FormItemProtocol { /// A place to store the initial value of the field for checking if the value has changed. var baseValue: AnyHashable? { get set } + ///Bool to determine a state that is different from disabled. Readonly values will be sent + ///to the server where disabled fields are not + var readOnly: Bool { get set } + /// Returns the value of the field. Used for validations and possibly for sending to server. func formFieldValue() -> AnyHashable? + } extension FormFieldProtocol { var baseValue: AnyHashable? { nil } + + var readOnly: Bool { false } } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index f991926b..4a1d36c7 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -111,11 +111,19 @@ import MVMCore /// - counter: keeps track of how many times causes another group validation /// - Returns: validity for the FormGroupRule.rules public func validateGroup(_ group: FormGroupRule, counter: Int = 0) throws -> Bool { - let valid = group.validate(fields) + let tuple = group.validate(fields) + + group.rules.forEach { rule in + for formKey in rule.fields { + guard let formField = fields[formKey] as? FormRuleWatcherFieldProtocol, + let fieldValidity = tuple.fieldValidity[formKey] else { continue } + formField.setValidity(fieldValidity, rule: rule) + } + } // Notify the group watchers of validity. for watcher in groupWatchers.filter({$0.groupName == group.groupName}) { - watcher.setValidity(valid) + watcher.setValidity(tuple.valid) } var ruleChange = false @@ -125,14 +133,14 @@ import MVMCore //get the fieldKey for the effect if let effected = fields[effect.fieldKey] { //get the validity - let validity = effect.validate(fields) + let effectTuple = effect.validate(fields) //set the effect with the validation - effect.setEffect(validity: validity, field: effected, form: self, group: group) + effect.setEffect(validity: effectTuple.valid, field: effected, form: self, group: group) //update the group form rules if let ruleIds = effect.activatedRuleIds { - let didChange = self.updateRules(for: group, with: validity, for: effect.fieldKey, and: ruleIds) + let didChange = self.updateRules(for: group, with: effectTuple.valid, for: effect.fieldKey, and: ruleIds) if(didChange) { ruleChange = didChange } @@ -147,7 +155,7 @@ import MVMCore return try self.validateGroup(group, counter: counter + 1) } } else { - return valid + return tuple.valid } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index dd02070b..25e21d4f 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -51,15 +51,10 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { fieldValidity = false } - for formKey in fields { - guard let formField = fieldMolecules[formKey] else { continue } - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(true, rule: self) - } break } previousValidity[formKey] = valid - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) } return (valid: valid, fieldValidity: previousValidity) } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift index fae81358..70b787bf 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift @@ -43,8 +43,6 @@ public class RuleEqualsModel: RulesProtocol { if compareValue != formField.formFieldValue() { valid = false - previousValidity[formKey] = valid - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) break } else { var fieldValidity = valid @@ -52,8 +50,9 @@ public class RuleEqualsModel: RulesProtocol { if let validity = previousFieldValidity[formKey], !validity, fieldValidity { fieldValidity = false } - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self) } + previousValidity[formKey] = valid + } return (valid: valid, fieldValidity: previousValidity) } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift index 37cd1828..8e158676 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift @@ -53,7 +53,6 @@ public extension RulesProtocol { if let validity = previousFieldValidity[formKey], !validity, fieldValidity { fieldValidity = false } - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self) valid = valid && fieldValidity previousValidity[formKey] = fieldValidity } @@ -66,12 +65,19 @@ public protocol RulesContainerProtocol{ } public extension RulesContainerProtocol { - func validate(_ fields: [String: FormFieldProtocol]) -> Bool { + /// This validation for Rules for the Validation or for Effects. + /// - Parameters: + /// - fields: Fields for the group + /// - setValidity: Since this function is for validation, this bool determines if you should set the FormFields.setValidity for a rule. + /// this method can be called for Form Validation and Effect Validation (this doesn't affect the submital of the form) + /// - Returns: Tuple(valid, fieldValidity) + /// - valid: bool for all rules + /// - fieldValidity: accumulation of all fieldKey: valid + func validate(_ fields: [String: FormFieldProtocol]) -> (valid: Bool, fieldValidity: [String:Bool] ) { // Validate each rule. var valid = true var previousValidity: [String: Bool] = [:] for rule in self.rules { - //validate the rule against the fields let tuple = rule.validate(fields, previousValidity) @@ -79,6 +85,6 @@ public extension RulesContainerProtocol { previousValidity = previousValidity.merging(tuple.fieldValidity) { (_, new) in new } valid = valid && tuple.valid } - return valid + return (valid: valid, fieldValidity: previousValidity) } } diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 38a89cd7..2a7c9d90 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -247,5 +247,6 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(DynamicRuleFormFieldEffectModel.self) ModelRegistry.register(DisableFormFieldEffectModel.self) ModelRegistry.register(HideFormFieldEffectModel.self) + ModelRegistry.register(ClearFormFieldEffectModel.self) } }