diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index ae49c2b5..56e3492c 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -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 = ""; }; AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocatorModel.swift; sourceTree = ""; }; AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocator.swift; sourceTree = ""; }; + AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = ""; }; BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = ""; }; @@ -1167,6 +1171,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 = ""; }; + EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleCompareModelProtocol.swift; sourceTree = ""; }; + EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyModelProtocol.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 */ @@ -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 = ""; }; + AFE4A1D427DFBB2700C458D0 /* NavigationController */ = { + isa = PBXGroup; + children = ( + D2B18B93236214AD00A9AEDC /* NavigationController.swift */, + AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */, + ); + path = NavigationController; + sourceTree = ""; + }; 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 = ""; @@ -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 */, diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift index 912b0891..399cedac 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift @@ -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 //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift index c70d458e..a0de9d1f 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift @@ -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) } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift index 3fed121a..962580a6 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift @@ -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 } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 4a154d3a..d7e947db 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -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) diff --git a/MVMCoreUI/Categories/UIColor+Extension.swift b/MVMCoreUI/Categories/UIColor+Extension.swift index ca763ac5..78363630 100644 --- a/MVMCoreUI/Categories/UIColor+Extension.swift +++ b/MVMCoreUI/Categories/UIColor+Extension.swift @@ -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") diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController/NavigationController.swift similarity index 50% rename from MVMCoreUI/Containers/NavigationController.swift rename to MVMCoreUI/Containers/NavigationController/NavigationController.swift index 7da6e0f6..03c10595 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController/NavigationController.swift @@ -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,79 +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.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 @@ -146,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) } @@ -155,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) } @@ -163,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) } @@ -174,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) { @@ -190,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) { diff --git a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift new file mode 100644 index 00000000..32de0bdf --- /dev/null +++ b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift @@ -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) + } +} diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift index 58bb1497..76afeca8 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift @@ -13,18 +13,13 @@ import UIKit 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) @@ -122,7 +117,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) guard !(topAlertView?.overridingStatusBar() ?? false) else { return } setStatusBarForCurrentViewController() } diff --git a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift index 2c95f942..01835ed9 100644 --- a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift @@ -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) + } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift index 3fc47d25..289bcb39 100644 --- a/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift @@ -10,5 +10,5 @@ import Foundation public protocol FormRuleWatcherFieldProtocol { - func setValidity(_ valid: Bool, rule: RulesProtocol) + func setValidity(_ valid: Bool, errorMessage: String?) } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 42c385e8..bf2f8c2f 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -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) } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyModelProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyModelProtocol.swift new file mode 100644 index 00000000..bdfd937c --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyModelProtocol.swift @@ -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) + } + +} diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift index 2b958d3b..38dd6669 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift @@ -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) - } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift index 09e91ceb..1d2c8faf 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift @@ -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) - } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleCompareModelProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleCompareModelProtocol.swift new file mode 100644 index 00000000..42022636 --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleCompareModelProtocol.swift @@ -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) + + } +} diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 95149551..4149737e 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -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 } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift index aa07cb29..c8a8aa56 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift @@ -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 } + } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift index 8e158676..d1326ba9 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift @@ -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) diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift index 8fbf8d49..c3be1868 100644 --- a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -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) }