diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 3433e17b..39bd239c 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -581,6 +581,7 @@ EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; }; EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; }; EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; + EA5DBDAB2C35B6C500290DF8 /* FormFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */; }; EA6642912BCDA97300D81DC4 /* TileContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642902BCDA97300D81DC4 /* TileContainer.swift */; }; EA6642932BCDA97D00D81DC4 /* TileContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */; }; EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B942B504A43000139B4 /* ButtonGroup.swift */; }; @@ -1201,6 +1202,7 @@ EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.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 = ""; }; + EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormFieldModel.swift; sourceTree = ""; }; EA6642902BCDA97300D81DC4 /* TileContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainer.swift; sourceTree = ""; }; EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainerModel.swift; sourceTree = ""; }; EA6E8B942B504A43000139B4 /* ButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroup.swift; sourceTree = ""; }; @@ -2487,6 +2489,7 @@ D2BEFEF5248A954C00FAB3A9 /* FormFields */ = { isa = PBXGroup; children = ( + EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */, D2BEFEF6248A957A00FAB3A9 /* Tags */, D29DF22B21E6A0FA003B2FB9 /* TextFields */, ); @@ -3292,6 +3295,7 @@ D28BA7432480284E00B75CB8 /* TabBar.swift in Sources */, AA3561AE24C96B9000452EB1 /* ListRightVariableRightCaretAllTextAndLinks.swift in Sources */, AA26850C244840AE00CE34CC /* HeadersH2TinyButton.swift in Sources */, + EA5DBDAB2C35B6C500290DF8 /* FormFieldModel.swift in Sources */, 011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */, D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */, 0A25209824645B76000FA9F6 /* TextViewEntryFieldModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift new file mode 100644 index 00000000..b15c23d6 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift @@ -0,0 +1,110 @@ +// +// FormFieldModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 7/3/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class FormFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public class var identifier: String { "" } + public var id: String = UUID().uuidString + + public var backgroundColor: Color? + public var accessibilityIdentifier: String? + + public var enabled: Bool = true + public var required: Bool = true + public var readOnly: Bool = false + public var showError: Bool? + public var errorMessage: String? + + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? + + public var isValid: Bool? = true { + didSet { updateUI?() } + } + + /// Temporary binding mechanism for the view to update on enable changes. + public var updateUI: ActionBlock? + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init() {} + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case id + case moleculeName + case accessibilityIdentifier + case errorMessage + case enabled + case readOnly + case required + case fieldKey + case groupName + } + + //-------------------------------------------------- + // MARK: - Validation Methods + //-------------------------------------------------- + + open func formFieldValue() -> AnyHashable? { + fatalError("developer must implement") + } + + open func formFieldServerValue() -> AnyHashable? { + return formFieldValue() + } + + public func setValidity(_ valid: Bool, errorMessage: String?) { + + if let ruleErrorMessage = errorMessage, fieldKey != nil { + self.errorMessage = ruleErrorMessage + } + + self.isValid = valid + updateUI?() + } + + //-------------------------------------------------- + // MARK: - Codable + //-------------------------------------------------- + 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 + accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) + errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) + 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 + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) ?? FormValidator.defaultGroupName + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encodeIfPresent(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) + try container.encodeIfPresent(errorMessage, forKey: .errorMessage) + 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) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileContainer.swift b/MVMCoreUI/Atomic/Atoms/Views/TileContainer.swift index 6296f640..eb5020c1 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/TileContainer.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/TileContainer.swift @@ -135,8 +135,6 @@ open class TileContainer: VDS.TileContainer, VDSMoleculeViewProtocol{ } extension TileContainer: MVMCoreUIViewConstrainingProtocol { - public func horizontalAlignment() -> UIStackView.Alignment { .leading } - public func isClippable() -> Bool { return false } diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift index b8b64f3d..0e7c6032 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift @@ -67,7 +67,7 @@ open class TileContainerBaseModel UIStackView.Alignment { .leading } - public func isClippable() -> Bool { return false } diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift b/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift index cf5d382b..e53066d0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/TileletModel.swift @@ -45,6 +45,7 @@ open class TileletModel: TileContainerBaseModel, Molec case textWidth case textPercentage } + required public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString @@ -86,7 +87,7 @@ open class TileletModel: TileContainerBaseModel, Molec } else { subTitleColor = .primary } - + try super.init(from: decoder) } diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift index d5e0182e..7ac663d6 100644 --- a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -137,7 +137,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, open override func pageShown() { // Currently not calling super until we can decouple page shown logics for managers. - hideNavigationBarLine(true) + hideNavigationBarLine(!tabs.isHidden) } open override func viewWillDisappear(_ animated: Bool) { @@ -148,7 +148,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - hideNavigationBarLine(true) + hideNavigationBarLine(!tabs.isHidden) } /// ensures margin for tabs are correct private func updateTabsMargin() { @@ -263,7 +263,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, } tabs.selectIndex(index, animated: true) self.index = nil - hideNavigationBarLine(true) + hideNavigationBarLine(!tabs.isHidden) } public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { @@ -354,12 +354,12 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, open func newDataReceived(in viewController: UIViewController) { manager?.newDataReceived?(in: viewController) - hideNavigationBarLine(true) + hideNavigationBarLine(!tabs.isHidden) } public func willDisplay(_ viewController: UIViewController) { manager?.willDisplay?(viewController) - hideNavigationBarLine(true) + hideNavigationBarLine(!tabs.isHidden) } public func displayedViewController(_ viewController: UIViewController) {