This commit is contained in:
Scott Pfeil 2022-03-14 17:05:56 -04:00
commit 392f723d29
20 changed files with 287 additions and 256 deletions

View File

@ -272,6 +272,7 @@
AAE7270E24AC8B9300A3ED0E /* HeadersH2CaretLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7270D24AC8B9300A3ED0E /* HeadersH2CaretLink.swift */; };
AAE96FA225341F6A0037A989 /* ListStoreLocatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */; };
AAE96FA525341F7D0037A989 /* ListStoreLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */; };
AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; };
BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; };
BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; };
BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; };
@ -583,6 +584,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 */; };
EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */; };
EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.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 */
@ -855,6 +858,7 @@
AAE7270D24AC8B9300A3ED0E /* HeadersH2CaretLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2CaretLink.swift; sourceTree = "<group>"; };
AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocatorModel.swift; sourceTree = "<group>"; };
AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocator.swift; sourceTree = "<group>"; };
AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = "<group>"; };
BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = "<group>"; };
BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = "<group>"; };
BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = "<group>"; };
@ -1167,6 +1171,8 @@
EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldEffectProtocol.swift; sourceTree = "<group>"; };
EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideFormFieldEffectModel.swift; sourceTree = "<group>"; };
EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableFormFieldEffectModel.swift; sourceTree = "<group>"; };
EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleCompareModelProtocol.swift; sourceTree = "<group>"; };
EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyModelProtocol.swift; sourceTree = "<group>"; };
EABFC1402763BB8D00E78B40 /* FormLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabel.swift; sourceTree = "<group>"; };
EABFC151276913E800E78B40 /* FormLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabelModel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -1212,12 +1218,14 @@
children = (
011D95A4240455DC000E3791 /* FormGroupRule.swift */,
011D958424042432000E3791 /* RulesProtocol.swift */,
011D959A240451E3000E3791 /* RuleRequiredModel.swift */,
011D959C2404536F000E3791 /* RuleAnyValueChangedModel.swift */,
EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */,
EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */,
011D959E240453A1000E3791 /* RuleAllValueChangedModel.swift */,
011D95A0240453D0000E3791 /* RuleEqualsModel.swift */,
011D959A240451E3000E3791 /* RuleRequiredModel.swift */,
011D95A2240453F8000E3791 /* RuleRegexModel.swift */,
011D959C2404536F000E3791 /* RuleAnyValueChangedModel.swift */,
0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */,
011D95A0240453D0000E3791 /* RuleEqualsModel.swift */,
0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */,
);
name = Rules;
@ -1529,6 +1537,15 @@
path = Miscellaneous;
sourceTree = "<group>";
};
AFE4A1D427DFBB2700C458D0 /* NavigationController */ = {
isa = PBXGroup;
children = (
D2B18B93236214AD00A9AEDC /* NavigationController.swift */,
AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */,
);
path = NavigationController;
sourceTree = "<group>";
};
D202AFE2242A5F1400E5BEDF /* Extensions */ = {
isa = PBXGroup;
children = (
@ -2066,9 +2083,9 @@
D29DF11921E68467003B2FB9 /* Containers */ = {
isa = PBXGroup;
children = (
AFE4A1D427DFBB2700C458D0 /* NavigationController */,
0ABD1369237B18EE0081388D /* Views */,
D29DF2B621E7BE66003B2FB9 /* SplitViewController */,
D2B18B93236214AD00A9AEDC /* NavigationController.swift */,
);
path = Containers;
sourceTree = "<group>";
@ -2631,6 +2648,7 @@
DBC4391922442197001AB423 /* DashLine.swift in Sources */,
D2ED27FC254B0E0300A1C293 /* MVMCoreAlertObject+Swift.swift in Sources */,
D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */,
AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */,
AAC23FAD24D92A0D009208DF /* ListThreeColumnSpeedTestModel.swift in Sources */,
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
01F2C20527C81F9700DC3D36 /* SubNavSwipeAnimator.swift in Sources */,
@ -2669,6 +2687,7 @@
D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */,
D2B18B7F2360913400A9AEDC /* Control.swift in Sources */,
D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */,
EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */,
011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */,
BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */,
D20C700B250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift in Sources */,
@ -2872,6 +2891,7 @@
BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */,
017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */,
323AC96C24C837FF00F8E4C4 /* ListThreeColumnBillChanges.swift in Sources */,
EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */,
0A0FEC7825D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift in Sources */,
D28A837923C7D5BC00DFE4FC /* PageModelProtocol.swift in Sources */,
D2351C7C24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift in Sources */,

View File

@ -38,29 +38,6 @@ import UIKit
// Default dimensions of the DigitBox
static let size: CGSize = CGSize(width: 39, height: 44)
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
public override var showError: Bool {
get { super.showError }
set (error) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.borderStrokeColor = error ? .mvmOrange : .mvmCoolGray3
let barHeight: CGFloat = self.showError ? 4 : 1
self.bottomBar?.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight)
self.bottomBar?.backgroundColor = self.showError ? UIColor.mvmOrange.cgColor : UIColor.mvmBlack.cgColor
self.setNeedsDisplay()
self.layoutIfNeeded()
}
super.showError = error
}
}
//--------------------------------------------------
// MARK: - Delegate
//--------------------------------------------------

View File

@ -105,7 +105,7 @@ import UIKit
super.isLocked = locked
}
}
public override var placeholder: String? {
get {
var string = ""
@ -311,7 +311,6 @@ import UIKit
//--------------------------------------------------
@objc override func startEditing() {
selectedDigitBox?.isSelected = true
selectedDigitBox?.digitField.becomeFirstResponder()
}
@ -328,7 +327,6 @@ import UIKit
}
@objc public override func dismissFieldInput(_ sender: Any?) {
digitBoxes.forEach {
if $0.isSelected {
$0.digitField.resignFirstResponder()
@ -398,7 +396,6 @@ extension DigitEntryField {
digitEntryModel?.text = text
return false
}
return true
}
@ -411,9 +408,9 @@ extension DigitEntryField {
}
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
digitBoxes.forEach {
if $0.digitField === textField {
startEditing()
selectedDigitBox = $0
$0.isSelected = true
return
@ -429,15 +426,15 @@ extension DigitEntryField {
}
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
// There should only be one digitBox to deselect.
selectedDigitBox?.isSelected = false
selectedDigitBox = nil
if !switchFieldsAutomatically && validateWhenDoneEditing {
validateText()
endInputing()
}
proprietorTextDelegate?.textFieldDidEndEditing?(textField)
}

View File

@ -94,9 +94,9 @@ import Foundation
return text
}
public func setValidity(_ valid: Bool, rule: RulesProtocol) {
public func setValidity(_ valid: Bool, errorMessage: String?) {
if let fieldKey = fieldKey, let ruleErrorMessage = rule.errorMessage?[fieldKey] {
if let ruleErrorMessage = errorMessage, fieldKey != nil {
self.errorMessage = ruleErrorMessage
}

View File

@ -349,7 +349,6 @@ import UIKit
}
open func pageShown() {
guard self as? MVMCoreViewManagerProtocol == nil else { return }
// Update split view properties if this is the current detail controller.
if self == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() {
MVMCoreUISplitViewController.main()?.setBottomProgressBarProgress(bottomProgress() ?? 0)

View File

@ -186,7 +186,7 @@ extension UIColor {
//--------------------------------------------------
/// HEX: #F6F6F6
public static let mvmCoolGray1 = UIColor.assetColor(named: "coolGray1")
@objc public static let mvmCoolGray1 = UIColor.assetColor(named: "coolGray1")
/// HEX: #D8DADA
public static let mvmCoolGray3 = UIColor.assetColor(named: "coolGray3")

View File

@ -23,7 +23,7 @@ import UIKit
MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController
MVMCoreNavigationHandler.shared()?.navigationController = navigationController
MVMCoreNavigationHandler.shared()?.addDelegate(navigationController)
NavigationController.setNavigationBarUI(navigationController: navigationController, navigationItemModel: NavigationItemModel())
navigationController.setNavigationBarUI(with: NavigationItemModel())
return navigationController
}
@ -34,80 +34,6 @@ import UIKit
return navigationController
}
/// Convenience function for setting the navigation item.
public static func setNavigationItem(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) {
viewController.navigationItem.title = navigationItemModel.title
viewController.navigationItem.accessibilityLabel = navigationItemModel.title
viewController.navigationItem.hidesBackButton = navigationItemModel.hidesSystemBackButton
viewController.navigationItem.leftItemsSupplementBackButton = !navigationItemModel.hidesSystemBackButton
setNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController)
setNavigationTitleView(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController)
}
/// Convenience function for setting the navigation buttons.
public static func setNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) {
let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
var leftItems: [UIBarButtonItem] = []
if navigationItemModel.hidesSystemBackButton,
navigationItemModel.alwaysShowBackButton != false {
if let backButtonModel = navigationItemModel.backButton,
MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 0 > 1 || navigationItemModel.alwaysShowBackButton ?? false {
leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
}
if let leftItemModels = navigationItemModel.additionalLeftButtons {
for item in leftItemModels {
leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
}
}
}
viewController.navigationItem.leftBarButtonItems = leftItems.count > 0 ? leftItems : nil
var rightItems: [UIBarButtonItem] = []
if let rightItemModels = navigationItemModel.additionalRightButtons {
for item in rightItemModels {
rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
}
}
viewController.navigationItem.rightBarButtonItems = rightItems.count > 0 ? rightItems : nil
}
static func getNavigationBarShadowImage(for navigationItemModel: NavigationItemModelProtocol) -> UIImage? {
guard let thickness = navigationItemModel.line?.thickness,
let backgroundColor = navigationItemModel.line?.backgroundColor else { return nil }
return backgroundColor.uiColor.image(CGSize(width: thickness, height: thickness))
}
/// Convenience function for setting the navigation bar ui, except for the buttons.
public static func setNavigationBarUI(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) {
let navigationBar = navigationController.navigationBar
let font = MFStyler.fontBoldBodySmall(false)
let backgroundColor = navigationItemModel.backgroundColor?.uiColor
let tint = navigationItemModel.tintColor.uiColor
navigationBar.tintColor = tint
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.titleTextAttributes = [NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: tint];
appearance.backgroundColor = backgroundColor
appearance.titleTextAttributes.updateValue(tint, forKey: .foregroundColor)
appearance.titlePositionAdjustment = navigationItemModel.titleOffset ?? .zero
appearance.shadowColor = navigationItemModel.line?.backgroundColor?.uiColor ?? .clear
appearance.shadowImage = getNavigationBarShadowImage(for: navigationItemModel)?.withRenderingMode(.alwaysTemplate)
navigationBar.standardAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
navigationController.setNavigationBarHidden(navigationItemModel.hidden, animated: true)
}
/// Convenience function for setting the navigation titleView.
public static func setNavigationTitleView(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) {
let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
if let titleViewModel = navigationItemModel?.titleView, let molecule = ModelRegistry.createMolecule(titleViewModel, delegateObject: delegate, additionalData: nil) {
viewController.navigationItem.titleView = molecule
}
}
/// Convenience function to return the navigation model of the lowest controller traversing managers if applicable.
public func getNavigationModel(from viewController: UIViewController) -> NavigationItemModelProtocol? {
return (viewController as? PageProtocol)?.pageModel?.navigationBar
@ -147,8 +73,8 @@ extension NavigationController: MVMCoreViewManagerProtocol {
if isDisplayed(viewController: viewController),
let topViewController = topViewController,
let model = getNavigationModel(from: viewController) {
Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController)
Self.setNavigationBarUI(navigationController: self, navigationItemModel: model)
setNavigationItem(with: model, for: topViewController)
setNavigationBarUI(with: model)
}
manager?.newDataReceived?(in: viewController)
}
@ -156,7 +82,7 @@ extension NavigationController: MVMCoreViewManagerProtocol {
public func willDisplay(_ viewController: UIViewController) {
if let topViewController = topViewController,
let model = getNavigationModel(from: viewController) {
Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController)
setNavigationItem(with: model, for: topViewController)
}
manager?.willDisplay?(viewController)
}
@ -164,7 +90,7 @@ extension NavigationController: MVMCoreViewManagerProtocol {
public func displayedViewController(_ viewController: UIViewController) {
if isDisplayed(viewController: viewController),
let model = getNavigationModel(from: viewController) {
Self.setNavigationBarUI(navigationController: self, navigationItemModel: model)
setNavigationBarUI(with: model)
}
manager?.displayedViewController?(viewController)
}
@ -175,7 +101,7 @@ extension NavigationController: MVMCorePresentationDelegateProtocol {
guard self == navigationController,
let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController),
let model = getNavigationModel(from: newViewController) else { return }
Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: viewController)
setNavigationItem(with: model, for: viewController)
}
public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) {
@ -191,7 +117,7 @@ extension NavigationController: MVMCorePresentationDelegateProtocol {
guard self == navigationController,
let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return }
if let model = getNavigationModel(from: newViewController) {
Self.setNavigationBarUI(navigationController: self, navigationItemModel: model)
setNavigationBarUI(with: model)
}
manager?.displayedViewController?(newViewController)
if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {

View File

@ -0,0 +1,87 @@
//
// UINavigationController+Extension.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 3/14/22.
// Copyright © 2022 Verizon Wireless. All rights reserved.
//
import Foundation
public extension UINavigationController {
/// Convenience function for setting the navigation item.
func setNavigationItem(with model: NavigationItemModelProtocol, for viewController: UIViewController) {
viewController.navigationItem.title = model.title
viewController.navigationItem.accessibilityLabel = model.title
viewController.navigationItem.hidesBackButton = model.hidesSystemBackButton
viewController.navigationItem.leftItemsSupplementBackButton = !model.hidesSystemBackButton
setNavigationButtons(with: model, for: viewController)
setNavigationTitleView(with: model, for: viewController)
}
/// Convenience function for setting the navigation buttons.
func setNavigationButtons(with model: NavigationItemModelProtocol, for viewController: UIViewController) {
let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
var leftItems: [UIBarButtonItem] = []
if model.hidesSystemBackButton,
model.alwaysShowBackButton != false {
if let backButtonModel = model.backButton,
MVMCoreNavigationHandler.shared()?.getViewControllers(for: self)?.count ?? 0 > 1 || model.alwaysShowBackButton ?? false {
leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
}
if let leftItemModels = model.additionalLeftButtons {
for item in leftItemModels {
leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
}
}
}
viewController.navigationItem.leftBarButtonItems = leftItems.count > 0 ? leftItems : nil
var rightItems: [UIBarButtonItem] = []
if let rightItemModels = model.additionalRightButtons {
for item in rightItemModels {
rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
}
}
viewController.navigationItem.rightBarButtonItems = rightItems.count > 0 ? rightItems : nil
}
/// Convenience function for setting the navigation titleView.
func setNavigationTitleView(with model: NavigationItemModelProtocol, for viewController: UIViewController) {
let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
if let titleViewModel = model.titleView,
let molecule = ModelRegistry.createMolecule(titleViewModel, delegateObject: delegate, additionalData: nil) {
viewController.navigationItem.titleView = molecule
}
}
/// Returns a ShadowImage based on the line property of NavigationItemModelProtocol
func getNavigationBarShadowImage(for navigationItemModel: NavigationItemModelProtocol) -> UIImage? {
guard let thickness = navigationItemModel.line?.thickness,
let backgroundColor = navigationItemModel.line?.backgroundColor else { return nil }
return backgroundColor.uiColor.image(CGSize(width: thickness, height: thickness))
}
/// Convenience function for setting the navigation bar ui
func setNavigationBarUI(with model: NavigationItemModelProtocol) {
let navigationBar = navigationBar
let font = MFStyler.fontBoldBodySmall(false)
let backgroundColor = model.backgroundColor?.uiColor
let tint = model.tintColor.uiColor
navigationBar.tintColor = tint
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.titleTextAttributes = [NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: tint];
appearance.backgroundColor = backgroundColor
appearance.titleTextAttributes.updateValue(tint, forKey: .foregroundColor)
appearance.shadowColor = model.line?.backgroundColor?.uiColor ?? .clear
appearance.shadowImage = getNavigationBarShadowImage(for: model)?.withRenderingMode(.alwaysTemplate)
navigationBar.standardAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
setNavigationBarHidden(model.hidden, animated: true)
}
}

View File

@ -12,18 +12,13 @@ import Foundation
public extension MVMCoreUISplitViewController {
/// Convenience function. Sets the navigation and split view properties for the view controller. Panel access is determined if view controller is a detail view protocol.
static func setNavigationBarUI(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) {
guard let splitView = MVMCoreUISplitViewController.main(),
navigationController == splitView.navigationController,
viewController == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() else {
func setNavigationBar(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) {
guard navigationController == self.navigationController,
viewController == getCurrentDetailViewController() else {
/// Not the split view navigation controller, skip split functions.
return
}
splitView.set(for: viewController, navigationController: navigationController, navigationItemModel: navigationItemModel)
}
/// Sets the navigation item for the view controller based on the model and splitview controller
private func set(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) {
setLeftPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isLeftPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false)
setRightPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isRightPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false)
@ -121,7 +116,7 @@ public extension MVMCoreUISplitViewController {
guard let navigationController = navigationController,
navigationController.isDisplayed(viewController: viewController),
let model = navigationController.getNavigationModel(from: viewController) else { return }
set(for: viewController, navigationController: navigationController, navigationItemModel: model)
setNavigationBar(for: viewController, navigationController: navigationController, navigationItemModel: model)
}
}

View File

@ -29,3 +29,19 @@ public extension FormFieldProtocol {
var baseValue: AnyHashable? { nil }
}
public class FormFieldValidity{
public var fieldKey: String
public var valid: Bool = true
public var errorMessages: [String] = []
public init(_ fieldKey: String){
self.fieldKey = fieldKey
}
public func addError(message: String?){
if let message = message {
self.errorMessages.append(message)
}
}
}

View File

@ -10,5 +10,5 @@
import Foundation
public protocol FormRuleWatcherFieldProtocol {
func setValidity(_ valid: Bool, rule: RulesProtocol)
func setValidity(_ valid: Bool, errorMessage: String?)
}

View File

@ -133,11 +133,10 @@ import MVMCore
let tuple = group.validate(fields)
//set the validity for the 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)
fields.forEach { (key: String, value: FormFieldProtocol) in
if let formField = value as? FormRuleWatcherFieldProtocol,
let fieldValidity = tuple.fieldValidity[key] {
formField.setValidity(fieldValidity.valid, errorMessage: fieldValidity.errorMessages.last)
}
}

View File

@ -0,0 +1,45 @@
//
// RuleAnyModelProtocol.swift
// MVMCoreUI
//
// Created by Matt Bruce on 3/9/22.
// Copyright © 2022 Verizon Wireless. All rights reserved.
//
import Foundation
/// RuleAnyModelProtocol was abstracted to 1 place since this code was currently
/// duplicated in 2 classes.
/// This protocol should be used for the rules that need to Loop through all of the fields
/// and if ANY formField's isValid == TRUE, the Rule is then TRUE
public protocol RuleAnyModelProtocol: RulesProtocol{}
extension RuleAnyModelProtocol {
/// Overriding the RulesProtocol default implementation
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity]) {
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
var fieldValidity = isValid(formField)
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity.valid, fieldValidity {
fieldValidity = false
}
// If TRUE the RULE is TRUE, even if there are many fields to check.
if fieldValidity {
return (true, previousFieldValidity)
}
}
// if the rule breaks all fields should be set to false
fields.forEach { (formKey) in
previousFieldValidity[formKey]?.valid = false
previousFieldValidity[formKey]?.addError(message: errorMessage?[formKey])
}
return (valid: false, fieldValidity: previousFieldValidity)
}
}

View File

@ -8,8 +8,7 @@
import UIKit
public class RuleAnyRequiredModel: RulesProtocol {
public class RuleAnyRequiredModel: RuleAnyModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -37,27 +36,4 @@ public class RuleAnyRequiredModel: RulesProtocol {
return false
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
var fieldValidity = isValid(formField)
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
if fieldValidity {
return (fieldValidity, previousValidity)
}
}
// if the rule breaks all fields should be set to false
fields.forEach { (formKey) in
previousValidity[formKey] = false
}
return (valid: false, fieldValidity: previousValidity)
}
}

View File

@ -8,8 +8,7 @@
import Foundation
public class RuleAnyValueChangedModel: RulesProtocol {
public class RuleAnyValueChangedModel: RuleAnyModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -27,26 +26,4 @@ public class RuleAnyValueChangedModel: RulesProtocol {
public func isValid(_ formField: FormFieldProtocol) -> Bool {
return formField.baseValue != formField.formFieldValue()
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
var fieldValidity = isValid(formField)
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
if fieldValidity {
return (true, previousValidity)
}
}
// if the rule breaks all fields should be set to false
fields.forEach { (formKey) in
previousValidity[formKey] = false
}
return (valid: false, fieldValidity: previousValidity)
}
}

View File

@ -0,0 +1,55 @@
//
// RuleCompareModelProtocol.swift
// MVMCoreUI
//
// Created by Matt Bruce on 3/9/22.
// Copyright © 2022 Verizon Wireless. All rights reserved.
//
import Foundation
/// RuleCompareModelProtocol is meant to be used for rules that compare
/// 2 FormField's values. It is up to the Implementing class to determine what
/// occurs within the "compare" method that is required. This can be anything
/// that returns a Bool. More than likley it will be a <,>, <=, =>, == comparison.
public protocol RuleCompareModelProtocol: RulesProtocol {
associatedtype CompareType
func compare(lhs: CompareType?, rhs: CompareType?) -> Bool
}
extension RuleCompareModelProtocol {
/// This overrides the RulesProtocol default implementation to then use your class implementation's "compare" method.
/// A requirement of this rule is that the fields array contains at least 2 fieldKeys and it will pull out these formFields with ONLY
/// the first 2 keys to send to the compare method your class will be implementing.
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity]) {
var valid = false
guard fields.count > 1, let firstFormField = fieldMolecules[fields[0]],
let secondFormField = fieldMolecules[fields[1]] else {
return (valid: true, previousFieldValidity)
}
let result = compare(lhs: firstFormField.formFieldValue() as? CompareType, rhs: secondFormField.formFieldValue() as? CompareType)
let formKey = fields[1]
//only check the 2nd value
if result {
valid = true
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity.valid, valid {
valid = false
}
previousFieldValidity[formKey]?.valid = valid
} else { //false
previousFieldValidity[formKey]?.valid = valid
previousFieldValidity[formKey]?.addError(message: errorMessage?[formKey])
}
return (valid: valid, fieldValidity: previousFieldValidity)
}
}

View File

@ -9,7 +9,7 @@
import Foundation
public class RuleEqualsIgnoreCaseModel: RulesProtocol {
public class RuleEqualsIgnoreCaseModel: RuleCompareModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -27,35 +27,12 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
public func isValid(_ formField: FormFieldProtocol) -> Bool {
return false
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var valid = false
var compareText: String?
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
guard let compareString = compareText else {
compareText = formField.formFieldValue() as? String
continue
}
if let fieldValue = formField.formFieldValue() as? String,
compareString.caseInsensitiveCompare(fieldValue) == .orderedSame {
valid = true
var fieldValidity = valid
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
previousValidity[formKey] = valid
break
}
previousValidity[formKey] = valid
///RuleCompareModelProtocol Method
public func compare(lhs: String?, rhs: String?) -> Bool {
guard let rhs = rhs else {
return false
}
return (valid: valid, fieldValidity: previousValidity)
return lhs?.caseInsensitiveCompare(rhs) == .orderedSame
}
}

View File

@ -8,8 +8,7 @@
import Foundation
public class RuleEqualsModel: RulesProtocol {
public class RuleEqualsModel: RuleCompareModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -27,33 +26,10 @@ public class RuleEqualsModel: RulesProtocol {
public func isValid(_ formField: FormFieldProtocol) -> Bool {
return false
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
var valid = true
var compareValue: AnyHashable?
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
if compareValue == nil {
compareValue = formField.formFieldValue()
continue
}
if compareValue != formField.formFieldValue() {
valid = false
previousValidity[formKey] = valid
break
} else {
var fieldValidity = valid
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
previousValidity[formKey] = valid
}
}
return (valid: valid, fieldValidity: previousValidity)
///RuleCompareModelProtocol Method
public func compare(lhs: AnyHashable?, rhs: AnyHashable?) -> Bool {
return lhs == rhs
}
}

View File

@ -30,7 +30,7 @@ public protocol RulesProtocol: ModelProtocol {
func isValid(_ formField: FormFieldProtocol) -> Bool
// Validates the rule and returns the result.
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool])
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity])
}
public extension RulesProtocol {
@ -42,21 +42,30 @@ public extension RulesProtocol {
static var categoryName: String { "\(RulesProtocol.self)" }
// Individual rule can override the function to validate based on the rule type.
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) {
func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity]) {
var valid = true
var previousValidity: [String: Bool] = [:]
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
//check the field isValid
var fieldValidity = isValid(formField)
//add the error message if it exists
if fieldValidity == false {
previousFieldValidity[formKey]?.addError(message: errorMessage?[formKey])
}
// If past rule is invalid for a field, the current rule should not flip the validity of a field
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
if let validity = previousFieldValidity[formKey], !validity.valid, fieldValidity {
fieldValidity = false
}
//set the valid for the field
previousFieldValidity[formKey]?.valid = fieldValidity
//set the full validity
valid = valid && fieldValidity
previousValidity[formKey] = fieldValidity
}
return (valid: valid, fieldValidity: previousValidity)
return (valid: valid, fieldValidity: previousFieldValidity)
}
}
@ -73,16 +82,16 @@ public extension RulesContainerProtocol {
/// - 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] ) {
func validate(_ fields: [String: FormFieldProtocol]) -> (valid: Bool, fieldValidity: [String:FormFieldValidity]) {
// Validate each rule.
var valid = true
var previousValidity: [String: Bool] = [:]
var previousValidity: [String: FormFieldValidity] = [:]
fields.keys.forEach { key in
previousValidity[key] = FormFieldValidity(key)
}
for rule in self.rules {
//validate the rule against the fields
let tuple = rule.validate(fields, previousValidity)
//merge the fieldValidity
previousValidity = previousValidity.merging(tuple.fieldValidity) { (_, new) in new }
valid = valid && tuple.valid
}
return (valid: valid, fieldValidity: previousValidity)

View File

@ -111,7 +111,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
}
open override func pageShown() {
super.pageShown()
// Currently not calling super until we can decouple page shown logics for managers.
hideNavigationBarLine(true)
}