Merge branch 'develop' into feature/atomic_vds_titleLockup

# Conflicts:
#	MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2024-02-15 16:14:07 -06:00
commit 5c8907b4a9
87 changed files with 1139 additions and 1239 deletions

View File

@ -168,6 +168,7 @@
526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; };
52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; };
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; };
608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; };
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; };
71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; };
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
@ -298,8 +299,8 @@
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; };
AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; };
AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; };
B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBE29DF34730005D28B /* BadgeModel.swift */; };
B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBC29DF34680005D28B /* Badge.swift */; };
B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBE29DF34730005D28B /* BadgeModel.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 */; };
@ -473,8 +474,6 @@
D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26921E6AA0B003B2FB9 /* FLAnimatedImageView.m */; };
D29DF26E21E6AA0B003B2FB9 /* FLAnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF26A21E6AA0B003B2FB9 /* FLAnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF26B21E6AA0B003B2FB9 /* FLAnimatedImageView.h */; settings = {ATTRIBUTES = (Public, ); }; };
D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */; };
D29DF27921E7A533003B2FB9 /* MVMCoreUISession.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */; settings = {ATTRIBUTES = (Public, ); }; };
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */; };
D29DF28021E7AA51003B2FB9 /* MVMCoreUIDetailViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF27F21E7AA50003B2FB9 /* MVMCoreUIDetailViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -574,10 +573,16 @@
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 */; };
EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B942B504A43000139B4 /* ButtonGroup.swift */; };
EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */; };
EA7D81602B2B6E6800D29F9E /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D815F2B2B6E6800D29F9E /* Icon.swift */; };
EA7D81622B2B6E7F00D29F9E /* IconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81612B2B6E7F00D29F9E /* IconModel.swift */; };
EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */; };
EA7D81662B2BABD200D29F9E /* Tooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81652B2BABD200D29F9E /* Tooltip.swift */; };
EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */; };
EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */; };
EA985C402970939A00F2FF2E /* TileletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3F2970939A00F2FF2E /* TileletModel.swift */; };
EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3D2970938F00F2FF2E /* Tilelet.swift */; };
EA985C402970939A00F2FF2E /* TileletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3F2970939A00F2FF2E /* TileletModel.swift */; };
EA985C602970A3F000F2FF2E /* VDS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C5F2970A3F000F2FF2E /* VDS.framework */; };
EA985C642970A40E00F2FF2E /* VDSTypographyTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C632970A40E00F2FF2E /* VDSTypographyTokens.xcframework */; };
EA985C852981AA9C00F2FF2E /* VDS-Enums+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C842981AA9C00F2FF2E /* VDS-Enums+Codable.swift */; };
@ -587,6 +592,7 @@
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 */; };
EAA482CE2B45F2F300978105 /* MFLoadingSpinner+VDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA482CD2B45F2F300978105 /* MFLoadingSpinner+VDS.swift */; };
EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA7801F290081320057DFDF /* VDSMoleculeViewProtocol.swift */; };
EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */; };
EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */; };
@ -760,6 +766,7 @@
526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = "<group>"; };
52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = "<group>"; };
52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = "<group>"; };
608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = "<group>"; };
7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; };
71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = "<group>"; };
8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; };
@ -890,8 +897,8 @@
AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = "<group>"; };
AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; };
AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = "<group>"; };
B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = "<group>"; };
B4CC8FBC29DF34680005D28B /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = "<group>"; };
B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.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>"; };
@ -1073,8 +1080,6 @@
D29DF26921E6AA0B003B2FB9 /* FLAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLAnimatedImageView.m; sourceTree = "<group>"; };
D29DF26A21E6AA0B003B2FB9 /* FLAnimatedImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLAnimatedImage.h; sourceTree = "<group>"; };
D29DF26B21E6AA0B003B2FB9 /* FLAnimatedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLAnimatedImageView.h; sourceTree = "<group>"; };
D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUILoggingHandler.h; sourceTree = "<group>"; };
D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUILoggingHandler.m; sourceTree = "<group>"; };
D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUISession.h; sourceTree = "<group>"; };
D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUISession.m; sourceTree = "<group>"; };
D29DF27F21E7AA50003B2FB9 /* MVMCoreUIDetailViewProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIDetailViewProtocol.h; sourceTree = "<group>"; };
@ -1167,6 +1172,12 @@
EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = "<group>"; };
EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = "<group>"; };
EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = "<group>"; };
EA6E8B942B504A43000139B4 /* ButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroup.swift; sourceTree = "<group>"; };
EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupModel.swift; sourceTree = "<group>"; };
EA7D815F2B2B6E6800D29F9E /* Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = "<group>"; };
EA7D81612B2B6E7F00D29F9E /* IconModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconModel.swift; sourceTree = "<group>"; };
EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipModel.swift; sourceTree = "<group>"; };
EA7D81652B2BABD200D29F9E /* Tooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tooltip.swift; sourceTree = "<group>"; };
EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableFormFieldEffectModel.swift; sourceTree = "<group>"; };
EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIUpdatableModelProtocol.swift; sourceTree = "<group>"; };
EA985C3D2970938F00F2FF2E /* Tilelet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tilelet.swift; sourceTree = "<group>"; };
@ -1180,6 +1191,7 @@
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>"; };
EAA482CD2B45F2F300978105 /* MFLoadingSpinner+VDS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFLoadingSpinner+VDS.swift"; sourceTree = "<group>"; };
EAA7801F290081320057DFDF /* VDSMoleculeViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VDSMoleculeViewProtocol.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>"; };
@ -1635,6 +1647,7 @@
D20492A324329A2800A5EED6 /* MVMCoreUIPagingProtocol.h */,
D29DF2B121E7B76C003B2FB9 /* MFLoadingSpinner.h */,
D29DF2B221E7B76D003B2FB9 /* MFLoadingSpinner.m */,
EAA482CD2B45F2F300978105 /* MFLoadingSpinner+VDS.swift */,
D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */,
D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */,
);
@ -2198,6 +2211,8 @@
DBC4391A224421A0001AB423 /* CaretLink.swift */,
D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */,
D2E2A99E23E07F8A000B42E6 /* PillButton.swift */,
EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */,
EA6E8B942B504A43000139B4 /* ButtonGroup.swift */,
);
path = Buttons;
sourceTree = "<group>";
@ -2242,6 +2257,10 @@
B4CC8FBC29DF34680005D28B /* Badge.swift */,
EA985C3F2970939A00F2FF2E /* TileletModel.swift */,
EA985C3D2970938F00F2FF2E /* Tilelet.swift */,
EA7D81612B2B6E7F00D29F9E /* IconModel.swift */,
EA7D815F2B2B6E6800D29F9E /* Icon.swift */,
EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */,
EA7D81652B2BABD200D29F9E /* Tooltip.swift */,
);
path = Views;
sourceTree = "<group>";
@ -2291,8 +2310,7 @@
D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */,
D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */,
D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */,
D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */,
D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */,
608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */,
AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */,
D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */,
D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */,
@ -2530,7 +2548,6 @@
D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */,
D29DF28421E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.h in Headers */,
D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */,
D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */,
D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */,
D20492A424329A2800A5EED6 /* MVMCoreUIPagingProtocol.h in Headers */,
D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */,
@ -2665,6 +2682,7 @@
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
01F2C20527C81F9700DC3D36 /* SubNavSwipeAnimator.swift in Sources */,
0AE277EC25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift in Sources */,
EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */,
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */,
3265B30424BCA749000D154B /* HeadersH1NoButtonsBodyText.swift in Sources */,
AAA7CD69250641F90045B959 /* HeartModel.swift in Sources */,
@ -2689,6 +2707,7 @@
0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */,
AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */,
AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */,
EAA482CE2B45F2F300978105 /* MFLoadingSpinner+VDS.swift in Sources */,
D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */,
D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */,
0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */,
@ -2755,6 +2774,7 @@
D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */,
D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */,
D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */,
608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */,
014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */,
0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */,
BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */,
@ -2762,7 +2782,6 @@
D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */,
AAA905E124D1759A00D1EFAB /* ListThreeColumnBillHistory.swift in Sources */,
C7F8012323E846C300396FBD /* ListRVWheelModel.swift in Sources */,
D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */,
D2ED27EC254B0CE700A1C293 /* UIAlertControllerStyle+Extension.swift in Sources */,
C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */,
014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */,
@ -2936,12 +2955,15 @@
011D958524042432000E3791 /* RulesProtocol.swift in Sources */,
4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */,
D23118B325124E18001C8440 /* NotificationMoleculeView.swift in Sources */,
EA7D81662B2BABD200D29F9E /* Tooltip.swift in Sources */,
AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */,
EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */,
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */,
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */,
27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */,
EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */,
D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */,
526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */,
B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */,
@ -2963,6 +2985,7 @@
C7F8012123E8303200396FBD /* ListRVWheel.swift in Sources */,
BB2C968F24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift in Sources */,
D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */,
EA7D81622B2B6E7F00D29F9E /* IconModel.swift in Sources */,
279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */,
BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */,
8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */,
@ -3134,6 +3157,7 @@
D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */,
BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */,
C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */,
EA7D81602B2B6E6800D29F9E /* Icon.swift in Sources */,
D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */,
D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */,
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */,

View File

@ -0,0 +1,111 @@
//
// ButtonGroup.swift
// MVMCoreUI
//
// Created by Matt Bruce on 1/11/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import UIKit
import VDS
@objcMembers open class ButtonGroup: VDS.ButtonGroup, VDSMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var viewModel: ButtonGroupModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
open var previousModel: ButtonGroupModel?
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
guard let model = model as? ButtonGroupModel,
let buttonModel = model.buttons.first
else { return 0 }
return PillButton.estimatedHeight(with: buttonModel, delegateObject)
}
public func viewModelDidUpdate() {
surface = viewModel.surface
isEnabled = viewModel.enabled
alignment = viewModel.alignment
rowQuantityPhone = viewModel.rowQuantityPhone
rowQuantityTablet = viewModel.rowQuantityTablet
if let childWidthValue = viewModel.childWidthValue {
childWidth = .value(childWidthValue)
} else if let childWidthPercentage = viewModel.childWidthPercentage {
childWidth = .percentage(childWidthPercentage)
} else {
childWidth = nil
}
// create / set the moleculeViews
if (previousModel == nil) || Self.nameForReuse(with: previousModel!, delegateObject) != Self.nameForReuse(with: viewModel, delegateObject) {
previousModel = viewModel
var buttonBases = [ButtonBase]()
viewModel.buttons.forEach { buttonModel in
do {
let buttonBaseType = try ModelRegistry.getHandler(buttonModel) as! MoleculeViewProtocol.Type
let button = buttonBaseType.init(model: buttonModel, delegateObject, additionalData) as! VDS.ButtonBase
buttonBases.append(button)
} catch {
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) {
MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject)
}
}
}
buttons = buttonBases
} else {
// only set the molecule views
for (index, buttonBase) in buttons.enumerated() {
if let button = buttonBase as? MoleculeViewProtocol, index < viewModel.buttons.count {
button.set(with: viewModel.buttons[index], delegateObject, additionalData)
}
}
// force redraw
setNeedsUpdate()
}
}
//--------------------------------------------------
// MARK: - MVMCoreUIViewConstrainingProtocol
//--------------------------------------------------
open func horizontalAlignment() -> UIStackView.Alignment {
return .center
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
public func updateView(_ size: CGFloat) {
setNeedsUpdate()
}
public static func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
// This will aggregate names of molecules to make an id.
guard let model = model as? ButtonGroupModel else {
return "buttonGroup<>"
}
var name = "buttonGroup<"
for case let item in model.buttons {
if let moleculeClass = ModelRegistry.getMoleculeClass(item),
let nameForReuse = moleculeClass.nameForReuse(with: item, delegateObject) {
name.append(nameForReuse + ",")
} else {
name.append(item.moleculeName + ",")
}
}
name.append(">")
return name
}
}

View File

@ -0,0 +1,82 @@
//
// ButtonGroupModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 1/11/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
public class ButtonGroupModel: ParentMoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "buttonGroup"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var children: [MoleculeModelProtocol] { buttons }
//--------------------------------------------------
// MARK: - VDS Properties
//--------------------------------------------------
public var buttons: [ButtonModelProtocol & MoleculeModelProtocol]
public var alignment: VDS.ButtonGroup.Alignment = .center
public var rowQuantityPhone: Int = 0
public var rowQuantityTablet: Int = 0
public var childWidthValue: CGFloat?
public var childWidthPercentage: CGFloat?
public var surface: VDS.Surface = .light
public var enabled: Bool = true
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case backgroundColor
case buttons
case alignment
case rowQuantityPhone
case rowQuantityTablet
case childWidthValue
case childWidthPercentage
case surface
case enabled
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
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
surface = try typeContainer.decodeIfPresent(Surface.self, forKey: .surface) ?? .light
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
buttons = try typeContainer.decodeModels(codingKey: .buttons)
alignment = try typeContainer.decodeIfPresent(VDS.ButtonGroup.Alignment.self, forKey: .alignment) ?? .center
rowQuantityPhone = try typeContainer.decodeIfPresent(Int.self, forKey: .rowQuantityPhone) ?? 0
rowQuantityTablet = try typeContainer.decodeIfPresent(Int.self, forKey: .rowQuantityTablet) ?? 0
childWidthValue = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .childWidthValue)
childWidthPercentage = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .childWidthPercentage)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(surface, forKey: .surface)
try container.encode(enabled, forKey: .enabled)
try container.encodeModels(buttons, forKey: .buttons)
try container.encode(alignment, forKey: .alignment)
try container.encode(rowQuantityPhone, forKey: .rowQuantityPhone)
try container.encode(rowQuantityTablet, forKey: .rowQuantityTablet)
try container.encodeIfPresent(childWidthValue, forKey: .childWidthValue)
try container.encodeIfPresent(childWidthPercentage, forKey: .childWidthPercentage)
}
}

View File

@ -31,6 +31,8 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
public var size: VDS.Button.Size = .large
public var groupName: String = ""
public var inverted: Bool = false
public var accessibilityTraits: UIAccessibilityTraits?
public var disabledAccessibilityTraits: UIAccessibilityTraits?
public var backgroundColor: Color?
//--------------------------------------------------
@ -77,6 +79,8 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
case size
case groupName
case width
case accessibilityTraits
case disabledAccessibilityTraits
}
//--------------------------------------------------
@ -118,6 +122,10 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
} else {
try setInverted(deprecatedFrom: decoder)
}
accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits)
disabledAccessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .disabledAccessibilityTraits)
}
private enum DeprecatedCodingKeys: String, CodingKey {
@ -158,6 +166,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
self.inverted = !backgroundColor.uiColor.isDark()
}
}
}
open func encode(to encoder: Encoder) throws {
@ -174,5 +183,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
try container.encode(size, forKey: .size)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encodeIfPresent(width, forKey: .width)
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
try container.encodeIfPresent(disabledAccessibilityTraits, forKey: .disabledAccessibilityTraits)
}
}

View File

@ -6,152 +6,61 @@
// Created by Christiano, Kevin on 3/18/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import VDS
open class CaretLink: VDS.TextLinkCaret, VDSMoleculeViewProtocol {
open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
// MARK: - Constants
//------------------------------------------------------
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var viewModel: CaretLinkModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
private let CARET_VIEW_HEIGHT: Float = 10.5
private let CARET_VIEW_WIDTH: Float = 6.5
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
@objc public var rightView: UIView?
@objc public var rightViewHeight: NSNumber?
@objc public var rightViewWidth: NSNumber?
@objc public var enabledColor: UIColor = .mvmBlack {
didSet { changeCaretColor() }
}
@objc public var disabledColor: UIColor = .mvmCoolGray3 {
didSet { changeCaretColor() }
}
private var caretSpacingConstraint: NSLayoutConstraint?
//------------------------------------------------------
// MARK: - Inits
//------------------------------------------------------
// Default values for view.
public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(frame: .zero)
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
setTitleColor(enabledColor, for: .normal)
setTitleColor(disabledColor, for: .disabled)
}
public override init(frame: CGRect) {
super.init(frame: frame)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//------------------------------------------------------
// MARK: - Lifecycle
//------------------------------------------------------
override public var isEnabled: Bool {
didSet { changeCaretColor() }
}
public override func updateView(_ size: CGFloat) {
titleLabel?.font = MFStyler.fontBoldBodySmall()
}
//------------------------------------------------------
// MARK: - Methods
//------------------------------------------------------
private func changeCaretColor() {
setTitleColor(enabledColor, for: .normal)
setTitleColor(disabledColor, for: .disabled)
if let rightCaretView = rightView as? CaretView {
rightCaretView.enabledColor = enabledColor
rightCaretView.disabledColor = disabledColor
rightCaretView.isEnabled = isEnabled
//--------------------------------------------------
// MARK: - Public Functions
//--------------------------------------------------
open func viewModelDidUpdate() {
isEnabled = viewModel.enabled
iconPosition = viewModel.iconPosition
text = viewModel.title
surface = viewModel.surface
onClick = { [weak self] control in
guard let self else { return }
MVMCoreUIActionHandler.performActionUnstructured(with: self.viewModel.action,
sourceModel: self.viewModel,
additionalData: self.additionalData,
delegateObject: self.delegateObject)
}
}
private func createCaretView() -> CaretView {
let caret = CaretView()
caret.lineWidth = 1.5
return caret
}
private func addCaretImageView() {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func updateAccessibility() {
super.updateAccessibility()
rightView?.removeFromSuperview()
let width = CGFloat(rightViewWidth?.floatValue ?? CARET_VIEW_WIDTH)
let height = CGFloat(rightViewHeight?.floatValue ?? CARET_VIEW_HEIGHT)
contentEdgeInsets.right = 4 + width
let caretView: UIView = rightView ?? createCaretView()
rightView = caretView
rightView?.translatesAutoresizingMaskIntoConstraints = false
addSubview(caretView)
caretView.widthAnchor.constraint(equalToConstant: width).isActive = true
caretView.heightAnchor.constraint(equalToConstant: height).isActive = true
let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 7)
caretLabelSpacing.isActive = true
caretSpacingConstraint = caretLabelSpacing
caretView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
trailingAnchor.constraint(greaterThanOrEqualTo: caretView.trailingAnchor).isActive = true
caretView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true
bottomAnchor.constraint(greaterThanOrEqualTo: caretView.bottomAnchor).isActive = true
contentHorizontalAlignment = .left
// Set correct color after layout
changeCaretColor()
}
public func updateCaretSpacing(_ spacing: CGFloat) {
caretSpacingConstraint?.constant = spacing
}
//------------------------------------------------------
// MARK: - Atomization
//------------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
addCaretImageView()
guard let model = model as? CaretLinkModel else { return }
if let color = model.backgroundColor {
backgroundColor = color.uiColor
guard let viewModel = viewModel else { return }
if let accessibilityText = viewModel.accessibilityText {
self.accessibilityLabel = accessibilityText
}
enabledColor = (model.inverted ? model.enabledColor_inverted : model.enabledColor).uiColor
disabledColor = (model.inverted ? model.disabledColor_inverted : model.disabledColor).uiColor
isEnabled = model.enabled
set(with: model.action, delegateObject: delegateObject, additionalData: additionalData)
setTitle(model.title, for: .normal)
}
open override func reset() {
super.reset()
rightView?.removeFromSuperview()
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
public func updateView(_ size: CGFloat) {}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 10.5 }
}
// MARK: - MVMCoreUIViewConstrainingProtocol
extension CaretLink: MVMCoreUIViewConstrainingProtocol {
public func needsToBeConstrained() -> Bool { true }
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 10.5 }
}

View File

@ -8,7 +8,7 @@
import Foundation
import MVMCore
import VDS
public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableModelProtocol {
//--------------------------------------------------
@ -20,13 +20,11 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var title: String
public var accessibilityText: String?
public var action: ActionModelProtocol
public var enabledColor: Color = Color(uiColor: .mvmBlack)
public var disabledColor: Color = Color(uiColor: .mvmCoolGray6)
public var enabledColor_inverted: Color = Color(uiColor: .mvmWhite)
public var disabledColor_inverted: Color = Color(uiColor: .mvmCoolGray10)
public var enabled = true
public var inverted = false
public var iconPosition: TextLinkCaret.IconPosition = .right
//--------------------------------------------------
// MARK: - Initializer
@ -45,12 +43,10 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
case id
case backgroundColor
case accessibilityIdentifier
case accessibilityText
case iconPosition
case title
case action
case enabledColor_inverted
case disabledColor_inverted
case enabledColor
case disabledColor
case enabled
case inverted
case moleculeName
@ -66,22 +62,11 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
title = try typeContainer.decode(String.self, forKey: .title)
if let enabledColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor_inverted) {
self.enabledColor_inverted = enabledColor_inverted
}
if let disabledColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor_inverted) {
self.disabledColor_inverted = disabledColor_inverted
}
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor) {
enabledColor = color
}
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor) {
disabledColor = color
if let iconPosition = try typeContainer.decodeIfPresent(TextLinkCaret.IconPosition.self, forKey: .iconPosition) {
self.iconPosition = iconPosition
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
@ -99,15 +84,17 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(iconPosition, forKey: .iconPosition)
try container.encode(title, forKey: .title)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeModel(action, forKey: .action)
try container.encode(enabledColor, forKey: .enabledColor)
try container.encodeIfPresent(disabledColor, forKey: .disabledColor)
try container.encode(enabled, forKey: .enabled)
try container.encode(enabledColor_inverted, forKey: .enabledColor_inverted)
try container.encode(disabledColor_inverted, forKey: .disabledColor_inverted)
try container.encode(inverted, forKey: .inverted)
}
}
extension CaretLinkModel {
public var surface: Surface { inverted ? .dark : .light }
}

View File

@ -33,7 +33,6 @@ import Foundation
super.set(with: model, delegateObject, additionalData)
FormValidator.setupValidation(for: castModel, delegate: delegateObject?.formHolderDelegate)
}
public func setState() {
@ -47,6 +46,9 @@ import Foundation
} else if let disabledTintColor = castModel.disabledTintColor {
image.imageView.tintColor = disabledTintColor.uiColor
}
if let traits = model?.accessibilityTraits {
accessibilityTraits = traits
}
}

View File

@ -24,7 +24,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
public var enabled: Bool = true
public var enabledTintColor: Color?
public var disabledTintColor: Color?
public var accessibilityTraits: UIAccessibilityTraits?
public var groupName: String = ""
public var updateUI: ActionBlock?
@ -45,6 +45,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
case groupName
case enabledTintColor
case disabledTintColor
case accessibilityTraits
}
//--------------------------------------------------
@ -59,7 +60,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
image = try typeContainer.decodeIfPresent(ImageViewModel.self, forKey: .image)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
action = try typeContainer.decodeModel(codingKey: .action)
accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits) ?? .button
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
@ -90,5 +91,6 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encodeIfPresent(enabledTintColor, forKey: .enabledTintColor)
try container.encodeIfPresent(disabledTintColor, forKey: .disabledTintColor)
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
}
}

View File

@ -11,7 +11,7 @@ import VDSColorTokens
import VDS
open class Link: VDS.TextLink, VDSMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@ -36,7 +36,7 @@ open class Link: VDS.TextLink, VDSMoleculeViewProtocol {
delegateObject: self.delegateObject)
}
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
@ -57,10 +57,9 @@ open class Link: VDS.TextLink, VDSMoleculeViewProtocol {
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 31 }
open func updateView(_ size: CGFloat) { }
open func setupView() {}
}
// MARK: - MVMCoreUIViewConstrainingProtocol

View File

@ -92,6 +92,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeModel(action, forKey: .action)
try container.encode(inverted, forKey: .inverted)
try container.encode(enabled, forKey: .enabled)

View File

@ -41,7 +41,9 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MFButtonPr
if let accessibilityText = viewModel.accessibilityText {
accessibilityLabel = accessibilityText
}
accessibilityTraits = isEnabled ? (viewModel?.accessibilityTraits ?? .button) : (viewModel?.disabledAccessibilityTraits ?? .none)
set(with: viewModel.action, delegateObject: delegateObject, additionalData: additionalData)
viewModel.updateUI = { [weak self] in
@ -58,8 +60,10 @@ open class PillButton: VDS.Button, MVMCoreUIViewConstrainingProtocol, MFButtonPr
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return (model as? ButtonModel)?.size.height
}
open func updateView(_ size: CGFloat) {}

View File

@ -187,7 +187,7 @@ import UIKit
let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing))
entryFieldContainer.addGestureRecognizer(tap)
accessibilityElements = [titleLabel, textField, feedbackLabel]
accessibilityElements = [textField, feedbackLabel]
}
@objc open override func updateView(_ size: CGFloat) {

View File

@ -161,7 +161,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
bottomConstraint?.isActive = true
heightConstraint = textView.heightAnchor.constraint(equalToConstant: 0)
accessibilityElements = [titleLabel, textView, feedbackLabel]
accessibilityElements = [textView]
}
open override func updateView(_ size: CGFloat) {
@ -281,6 +281,25 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
if model.hideBorders {
adjustMarginConstraints(constant: 0)
}
updateAccessibility(model: model)
}
func updateAccessibility(model: TextViewEntryFieldModel) {
var message = ""
if let titleText = model.accessibilityText ?? model.title {
message += "\(titleText) \( model.enabled ? String(format: (MVMCoreUIUtility.hardcodedString(withKey: "textfield_optional")) ?? "") : "" ) \(self.textView.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
}
if let feedback = model.feedback {
message += ", " + feedback
}
if let errorMessage = errorLabel.text {
message += ", " + errorMessage
}
textView.accessibilityLabel = message
}
}

View File

@ -0,0 +1,60 @@
//
// Icon.swift
// MVMCoreUI
//
// Created by Matt Bruce on 12/14/23.
// Copyright © 2023 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
/**
This class expects its height and width to be equal.
*/
open class Icon: VDS.Icon, VDSMoleculeViewProtocol{
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var viewModel: IconModel!
public var delegateObject: MVMCoreUIDelegateObject?
public var additionalData: [AnyHashable: Any]?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public convenience required init() {
self.init(frame: .zero)
}
//--------------------------------------------------
// MARK: - Public
//--------------------------------------------------
public func viewModelDidUpdate() {
surface = viewModel.surface
color = viewModel.color.uiColor
size = viewModel.size
customSize = viewModel.customSize
name = viewModel.name
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open func updateView(_ size: CGFloat) {}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
//since this is a class func, we can't reference it directly
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
VDS.Icon.Size.medium.dimensions.height
}
}
extension Icon: MVMCoreUIViewConstrainingProtocol {
public func needsToBeConstrained() -> Bool { true }
public func horizontalAlignment() -> UIStackView.Alignment { .leading }
}

View File

@ -0,0 +1,38 @@
//
// IconModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 12/14/23.
// Copyright © 2023 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
import VDSColorTokens
open class IconModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "icon"
@DecodableDefault.UUIDString public var id: String
public var backgroundColor: Color?
/// A representation that will be used to render the icon with corresponding name.
public var name: Icon.Name
/// Color of the icon.
@DecodableDefault.BlackColor() public var color: Color
/// Enum for a preset height and width for the icon.
@DecodableDefault.IconSize public var size: VDS.Icon.Size
/// surface of the object
@DecodableDefault.Surface public var surface: VDS.Surface
/// A custom size of the icon.
public var customSize: Int?
}

View File

@ -64,7 +64,7 @@ public class FormLabelModel: EnableableModelProtocol {
if enabled {
required.attributes = [LabelAttributeColorModel(FormLabelModel.defaultRequiredTextColor, model.text.count + 1, 8)]
}
required.text = "\(model.text) Optional"
required.text = "\(model.text) \(MVMCoreUIUtility.hardcodedString(withKey: "textfield_optional") ?? "")"
return required
}

View File

@ -231,7 +231,7 @@ public typealias ActionBlock = () -> ()
documentAttributes: nil)
} catch {
if let coreErrorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "LabelHTMLParse") {
MVMCoreUILoggingHandler.addError(toLog: coreErrorObject)
MVMCoreUILoggingHandler.shared()?.addError(toLog: coreErrorObject)
}
}
}
@ -1013,17 +1013,13 @@ extension Label {
func validateAttribute(range: NSRange, in string: NSAttributedString, type: String = "") -> NSRange? {
guard range.location >= 0 && range.location <= string.length else {
if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) {
loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(range.lowerBound) is out of bounds for '\(string.string)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!)
}
MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(range.lowerBound) is out of bounds for '\(string.string)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!)
return nil
}
if type != "image" && range.upperBound > string.length {
let newRange = NSRange(location: range.location, length: string.length - range.location)
if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) {
loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute ending location \(range.upperBound) is out of bounds for '\(string)'. Adjusting to \(newRange.upperBound).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!)
}
MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute ending location \(range.upperBound) is out of bounds for '\(string)'. Adjusting to \(newRange.upperBound).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!)
return newRange
}

View File

@ -7,207 +7,33 @@
//
import UIKit
import VDS
open class LoadingSpinner: VDS.Loader, VDSMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var viewModel: LoadingSpinnerModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
//--------------------------------------------------
// MARK: - Public Functions
//--------------------------------------------------
open func viewModelDidUpdate() {
size = Int(viewModel.diameter)
surface = viewModel.surface
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
guard let model = model as? LoadingSpinnerModel else { return 0 }
return model.diameter
}
open class LoadingSpinner: View {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var strokeColor: UIColor = .mvmBlack
public var lineWidth: CGFloat = 4.0
public var speed: Float = 1.5
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var heightConstraint: NSLayoutConstraint?
public var widthConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
override open var layer: CAShapeLayer {
get { return super.layer as! CAShapeLayer }
}
override open class var layerClass: AnyClass {
return CAShapeLayer.self
}
open override func setupView() {
super.setupView()
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
widthConstraint = widthAnchor.constraint(equalToConstant: 0)
}
override open func layoutSubviews() {
super.layoutSubviews()
open func updateView(_ size: CGFloat) { }
layer.fillColor = nil
layer.strokeColor = strokeColor.cgColor
layer.lineWidth = lineWidth
layer.lineCap = .butt
layer.speed = speed
let halfWidth = lineWidth / 2
let radius = (bounds.width - lineWidth) / 2
layer.path = UIBezierPath(arcCenter: CGPoint(x: radius + halfWidth,
y: radius + halfWidth),
radius: radius,
startAngle: -CGFloat.pi / 2,
endAngle: 2 * CGFloat.pi,
clockwise: true).cgPath
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
layer.removeAllAnimations()
animate()
}
public override func reset() {
super.reset()
layer.removeAllAnimations()
heightConstraint?.isActive = false
widthConstraint?.isActive = false
}
//--------------------------------------------------
// MARK: - Animation
//--------------------------------------------------
override open func didMoveToWindow() {
animate()
}
struct Pose {
/// Delayed time (in seconds) to execute after the previous Pose.
let delay: CFTimeInterval
/// The time into the animation to begin drawing.
let startTime: CGFloat
/// The length of the drawn line.
let length: CGFloat
}
// TODO: This needs more attention to improve frame smoothness.
class var poses: [Pose] {
get {
return [
Pose(delay: 0.0, startTime: 0.000, length: 0.7),
Pose(delay: 0.7, startTime: 0.500, length: 0.5),
Pose(delay: 0.6, startTime: 1.000, length: 0.3),
Pose(delay: 0.5, startTime: 1.500, length: 0.2),
Pose(delay: 0.5, startTime: 1.875, length: 0.2),
Pose(delay: 0.3, startTime: 2.250, length: 0.3),
Pose(delay: 0.2, startTime: 2.600, length: 0.5),
Pose(delay: 0.2, startTime: 3.000, length: 0.7)
]
}
}
private func animate() {
var time: CFTimeInterval = 0
var times = [CFTimeInterval]()
var start: CGFloat = 0
var rotations = [CGFloat]()
var strokeEnds = [CGFloat]()
let poses = Self.poses
var totalSeconds: CFTimeInterval = poses.reduce(0) { $0 + $1.delay }
for pose in poses {
time += pose.delay
times.append(time / totalSeconds)
start = pose.startTime
rotations.append(start * 2 * CGFloat.pi)
strokeEnds.append(pose.length)
}
totalSeconds += 0.3
animateKeyPath(keyPath: "strokeEnd", duration: totalSeconds, times: times, values: strokeEnds)
animateKeyPath(keyPath: "transform.rotation", duration: totalSeconds, times: times, values: rotations)
}
private func animateKeyPath(keyPath: String, duration: CFTimeInterval, times: [CFTimeInterval], values: [CGFloat]) {
let animation = CAKeyframeAnimation(keyPath: keyPath)
animation.keyTimes = times as [NSNumber]?
animation.values = values
animation.calculationMode = .linear
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.duration = duration
animation.rotationMode = .rotateAuto
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.repeatCount = .infinity
layer.add(animation, forKey: animation.keyPath)
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
func resumeSpinnerAfterDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
self?.resumeAnimations()
}
}
func pauseAnimations() {
let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
layer.speed = 0
isHidden = true
layer.timeOffset = pausedTime
}
func resumeAnimations() {
let pausedTime = layer.timeOffset
isHidden = false
layer.speed = speed
layer.timeOffset = 0
let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
layer.beginTime = timeSincePause
}
func stopAllAnimations() {
layer.removeAllAnimations()
}
func pinWidthAndHeight(diameter: CGFloat) {
let dimension = diameter + lineWidth
heightConstraint?.constant = dimension
widthConstraint?.constant = dimension
heightConstraint?.isActive = true
widthConstraint?.isActive = true
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? LoadingSpinnerModel else { return }
strokeColor = model.strokeColor.uiColor
lineWidth = model.lineWidth
pinWidthAndHeight(diameter: model.diameter)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 40.0
}
}

View File

@ -8,6 +8,7 @@
import Foundation
import MVMCore
import VDS
open class LoadingSpinnerModel: MoleculeModelProtocol {
//--------------------------------------------------
@ -17,8 +18,7 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var strokeColor = Color(uiColor: .mvmBlack)
public var lineWidth: CGFloat = 4
public var inverted: Bool = false
public var diameter: CGFloat = 40
//--------------------------------------------------
@ -28,10 +28,9 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case backgroundColor
case strokeColor
case lineWidth
case diameter
case inverted
}
//--------------------------------------------------
@ -48,18 +47,17 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
if let diameter = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .diameter) {
self.diameter = diameter
}
if let strokeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .strokeColor) {
self.strokeColor = strokeColor
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
self.inverted = inverted
}
if let lineWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .lineWidth) {
self.lineWidth = lineWidth
if let strokeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .strokeColor) {
self.inverted = !strokeColor.uiColor.isDark()
}
}
@ -67,9 +65,11 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(diameter, forKey: .diameter)
try container.encode(strokeColor, forKey: .strokeColor)
try container.encode(lineWidth, forKey: .lineWidth)
try container.encodeIfPresent(inverted, forKey: .inverted)
}
}
extension LoadingSpinnerModel {
public var surface: Surface { inverted ? .dark : .light }
}

View File

@ -54,7 +54,7 @@ import Foundation
// MARK: - Lifecycle
//--------------------------------------------------
public func setupView() {
open func setupView() {
clipsToBounds = true
translatesAutoresizingMaskIntoConstraints = false
@ -70,7 +70,7 @@ import Foundation
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let progressBarModel = model as? ProgressBarModel else { return }

View File

@ -8,8 +8,8 @@
import Foundation
@objcMembers public class ProgressBarModel: MoleculeModelProtocol {
public static var identifier: String = "progressBar"
@objcMembers open class ProgressBarModel: MoleculeModelProtocol {
open class var identifier: String { "progressBar" }
public var id: String = UUID().uuidString
@Percent public var percent: CGFloat
@ -46,7 +46,7 @@ import Foundation
thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness)
}
public func encode(to encoder: Encoder) throws {
open func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)

View File

@ -0,0 +1,66 @@
//
// Tooltip.swift
// MVMCoreUI
//
// Created by Matt Bruce on 12/14/23.
// Copyright © 2023 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class Tooltip: VDS.Tooltip, VDSMoleculeViewProtocol{
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var viewModel: MVMCoreUI.TooltipModel!
public var delegateObject: MVMCoreUIDelegateObject?
public var additionalData: [AnyHashable: Any]?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public convenience required init() {
self.init(frame: .zero)
}
//--------------------------------------------------
// MARK: - Public
//--------------------------------------------------
public func viewModelDidUpdate() {
surface = viewModel.surface
fillColor = viewModel.fillColor
size = viewModel.size
closeButtonText = viewModel.closeButtonText
title = viewModel.title
content = viewModel.content
if let moleculeModel = viewModel.molecule {
if contentView != nil {
(contentView as? MoleculeViewProtocol)?.set(with: moleculeModel, delegateObject, additionalData)
} else if let molecule = ModelRegistry.createMolecule(moleculeModel, delegateObject: delegateObject, additionalData: additionalData) {
contentView = molecule
}
}
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open func updateView(_ size: CGFloat) {}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
//since this is a class func, we can't reference it directly
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
VDS.Icon.Size.medium.dimensions.height
}
}
extension Tooltip: MVMCoreUIViewConstrainingProtocol {
public func needsToBeConstrained() -> Bool { true }
public func horizontalAlignment() -> UIStackView.Alignment { .leading }
}

View File

@ -0,0 +1,98 @@
//
// TooltipModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 12/14/23.
// Copyright © 2023 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
import VDSColorTokens
import MVMCore
open class TooltipModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "tooltip"
@DecodableDefault.UUIDString public var id: String
public var backgroundColor: Color?
public var size: VDS.Tooltip.Size = .medium
public var fillColor: VDS.Tooltip.FillColor = .primary
public var closeButtonText: String = { MVMCoreUIUtility.hardcodedString(withKey: "AccCloseButton") ?? "Close" }()
public var title: String?
public var content: String?
public var molecule: MoleculeModelProtocol?
/// surface of the object
public var surface: VDS.Surface = .light
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case backgroundColor
case closeButtonText
case title
case content
case contentView
case size
case fillColor
case surface
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
self.backgroundColor = try container.decodeIfPresent(Color.self, forKey: .backgroundColor)
self.title = try container.decodeIfPresent(String.self, forKey: .title)
self.content = try container.decodeIfPresent(String.self, forKey: .content)
self.molecule = try container.decodeModelIfPresent(codingKey: .contentView)
if let closeButtonText = try container.decodeIfPresent(String.self, forKey: .closeButtonText) {
self.closeButtonText = closeButtonText
}
if let surface = try container.decodeIfPresent(VDS.Surface.self, forKey: .surface) {
self.surface = surface
}
if let size = try container.decodeIfPresent(VDS.Tooltip.Size.self, forKey: .size) {
self.size = size
}
if let fillColor = try container.decodeIfPresent(VDS.Tooltip.FillColor.self, forKey: .fillColor) {
self.fillColor = fillColor
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(surface, forKey: .surface)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(size, forKey: .size)
try container.encodeIfPresent(fillColor, forKey: .fillColor)
try container.encodeIfPresent(closeButtonText, forKey: .closeButtonText)
try container.encodeIfPresent(title, forKey: .title)
try container.encodeIfPresent(content, forKey: .content)
try container.encodeModelIfPresent(molecule, forKey: .contentView)
}
}

View File

@ -89,7 +89,7 @@ open class Video: View {
})
case .failed:
if let errorObject = item.loadFailedError {
MVMCoreLoggingHandler.addError(toLog: errorObject)
MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject)
}
default:
break

View File

@ -8,20 +8,57 @@
import Foundation
import VDS
import VDSColorTokens
extension Surface: Codable {}
extension Badge.FillColor: Codable {}
extension Icon.Name: Codable {}
extension Icon.Size: Codable {}
extension TileContainer.BackgroundColor: Codable {}
extension TileContainer.Padding: Codable {}
extension TileContainer.AspectRatio: Codable {}
extension TextLink.Size: Codable {}
//--------------------------------------------------
// MARK: - Codable Extensions
//--------------------------------------------------
extension VDS.Surface: Codable {}
extension VDS.Badge.FillColor: Codable {}
extension VDS.ButtonGroup.Alignment: Codable {}
extension VDS.Icon.Name: Codable {}
extension VDS.Icon.Size: Codable {}
extension VDS.Tabs.Orientation: Codable {}
extension VDS.Tabs.IndicatorPosition: Codable {}
extension VDS.Tabs.Overflow: Codable {}
extension VDS.Tabs.Size: Codable {}
extension VDS.TextLink.Size: Codable {}
extension VDS.TextLinkCaret.IconPosition: Codable {}
extension VDS.TileContainer.BackgroundColor: Codable {}
extension VDS.TileContainer.Padding: Codable {}
extension VDS.TileContainer.AspectRatio: Codable {}
extension VDS.TitleLockup.TextAlignment: Codable {}
extension VDS.Tooltip.FillColor: Codable {}
extension VDS.Tooltip.Size: Codable {}
extension VDS.Line.Style: Codable {}
extension VDS.Line.Orientation: Codable {}
extension Use: Codable {}
extension VDS.Use: Codable {}
extension VDS.Button.Size: RawRepresentableCodable {
public static var mapping: [String : VDS.Button.Size] { ["standard": .large, "tiny": .small] }
public static var defaultValue: VDS.Button.Size? { nil }
}
//--------------------------------------------------
// MARK: - Decodable Defaults
//--------------------------------------------------
extension DecodableDefault.Sources {
public struct Surface: DecodableDefaultSource {
public static var defaultValue: VDS.Surface { .light }
}
public struct IconSize: DecodableDefaultSource {
public static var defaultValue: VDS.Icon.Size { .medium }
}
public struct BlackColor: DecodableDefaultSource {
public static var defaultValue: Color { .init(uiColor: VDSColor.paletteBlack) }
}
}
extension DecodableDefault {
public typealias IconSize = Wrapper<Sources.IconSize>
public typealias BlackColor = Wrapper<Sources.BlackColor>
public typealias Surface = Wrapper<Sources.Surface>
}

View File

@ -75,6 +75,7 @@ import Foundation
delegateObject, additionalData)
rightImageView.set(with: model.image, delegateObject, additionalData)
updateAccessibilityLabel()
accessibilityTraits.update(with: model.accessibilityTraits ?? button.accessibilityTraits)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {

View File

@ -73,6 +73,7 @@
delegateObject, additionalData)
rightImageView.set(with: model.image, delegateObject, additionalData)
updateAccessibilityLabel()
accessibilityTraits.update(with: model.accessibilityTraits ?? button.accessibilityTraits)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {

View File

@ -79,7 +79,7 @@
let linkShowing = (model as? ListLeftVariableIconAllTextLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil
isAccessibilityElement = !linkShowing
accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((isAccessibilityElement && accessoryView != nil) ? .button : .none)
if !linkShowing {
// Make whole cell focusable if no link.

View File

@ -91,6 +91,6 @@
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -100,7 +100,7 @@
func updateAccessibilityLabel() {
let linkShowing = (model as? ListLeftVariableIconWithRightCaretAllTextLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil
isAccessibilityElement = !linkShowing
accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((isAccessibilityElement && accessoryView != nil) ? .button : .none)
if !linkShowing {
// Make whole cell focusable if no link.

View File

@ -109,6 +109,6 @@
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -89,7 +89,7 @@
let linkShowing = (model as? ListLeftVariableNumberedListAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil
isAccessibilityElement = !linkShowing
accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((isAccessibilityElement && accessoryView != nil) ? .button : .none)
if !linkShowing {
// Make whole cell focusable if no link.

View File

@ -89,7 +89,7 @@
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -134,6 +134,6 @@ open class ListProgressBarThin: TableViewCell {
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -70,11 +70,7 @@
override public var accessibilityTraits: UIAccessibilityTraits {
get {
if (accessoryView != nil) {
return .button
} else {
return .none
}
return (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
set { }
}

View File

@ -97,7 +97,8 @@ import Foundation
// Ensures voice over does not read "selected" after user triggers action on cell.
override public var accessibilityTraits: UIAccessibilityTraits {
get {
return (accessoryView != nil) ? .button : .none
return (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
set {}
}

View File

@ -46,7 +46,7 @@ import Foundation
/// Ensures voice over does not read "selected" after user triggers action on cell.
override public var accessibilityTraits: UIAccessibilityTraits {
get {
return (accessoryView != nil) ? .button : .none
return (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
set {}
}

View File

@ -91,6 +91,6 @@
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -47,7 +47,7 @@
stack.restack()
accessibilityValue = button.accessibilityValue
accessibilityHint = button.accessibilityHint
accessibilityTraits = .button
accessibilityTraits = (listItemModel?.accessibilityTraits ?? .button)
}
//-----------------------------------------------------

View File

@ -83,6 +83,6 @@
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -102,7 +102,7 @@
let linkShowing = (model as? ListRightVariablePriceChangeAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil
isAccessibilityElement = !linkShowing
accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((isAccessibilityElement && accessoryView != nil) ? .button : .none)
if !linkShowing {
// Make whole cell focusable if no link.

View File

@ -98,6 +98,6 @@
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -99,8 +99,8 @@
let linkShowing = (model as? ListRightVariableRightCaretAllTextAndLinksModel)?.eyebrowHeadlineBodyLink.link?.title != nil
isAccessibilityElement = !linkShowing
accessibilityTraits = (isAccessibilityElement && accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((isAccessibilityElement && accessoryView != nil) ? .button : .none)
if !linkShowing {
// Make whole cell focusable if no link.
accessibilityLabel = getAccessibilityMessage()

View File

@ -89,7 +89,7 @@
func updateAccessibilityLabel() {
let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0
accessibilityTraits = .button
accessibilityTraits = listItemModel?.accessibilityTraits ?? .button
if !linkShowing && accessoryView == nil {
// Make whole cell focusable if one action

View File

@ -103,6 +103,6 @@ open class ListRightVariableTotalData: TableViewCell {
}
accessibilityLabel = message
accessibilityTraits = (accessoryView != nil) ? .button : .none
accessibilityTraits = (listItemModel?.accessibilityTraits) ?? ((accessoryView != nil) ? .button : .none)
}
}

View File

@ -29,8 +29,8 @@
if let _ = molecule as? ButtonModel {
horizontalAlignment = .fill
} else if let model = molecule as? TwoButtonViewModel,
model.primaryButton == nil || model.secondaryButton == nil {
} else if let model = molecule as? TwoButtonViewModel {
model.fillContainer = true
horizontalAlignment = .fill
}
}

View File

@ -84,16 +84,22 @@ import VDSColorTokens
// MARK: - TabBarProtocol
@MainActor
public func highlightTab(at index: Int) {
guard let newSelectedItem = items?[index] else { return }
guard let items = items, index >= 0, index < items.count else {
MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Invalid tab index \(index). \(items?.count ?? 0) tabs available .", code: 0, domain: ErrorDomainSystem, location: #function)!)
return
}
tabModel.selectedTab = index
selectedItem = newSelectedItem
selectedItem = items[index]
}
@MainActor
public func selectTab(at index: Int) {
guard let newSelectedItem = items?[index] else { return }
selectedItem = newSelectedItem
tabBar(self, didSelect: newSelectedItem)
guard let items = items, index >= 0, index < items.count else {
MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Invalid tab index \(index). \(items?.count ?? 0) tabs available.", code: 0, domain: ErrorDomainSystem, location: #function)!)
return
}
selectedItem = items[index]
tabBar(self, didSelect: items[index])
}
public func currentTabIndex() -> Int { tabModel.selectedTab }

View File

@ -8,379 +8,77 @@
import UIKit
import VDSColorTokens
import VDS
@objc public protocol TabsDelegate {
func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool
func didSelectItem(_ indexPath: IndexPath, tabs: Tabs)
}
@objcMembers open class Tabs: View, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol {
@objcMembers open class Tabs: VDS.Tabs, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol {
public var tabsModel: TabsModel? {
get { return model as? TabsModel }
}
var delegateObject: MVMCoreUIDelegateObject?
var additionalData: [AnyHashable: Any]?
let layout = UICollectionViewFlowLayout()
public var collectionView: CollectionView?
let bottomScrollView = UIScrollView(frame: .zero)
let bottomContentView = View()
let bottomLine = Line()
let selectionLine = View()
var selectionLineLeftConstraint: NSLayoutConstraint?
var selectionLineWidthConstraint: NSLayoutConstraint?
private var widthLabel = Label()
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
open var viewModel: TabsModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
//delegate
weak public var delegate: TabsDelegate?
//control var
public var selectedIndex: Int = 0
public var paddingBeforeFirstTab: Bool = true
//constant
let TabCellId = "TabCell"
public let itemSpacing: CGFloat = 20.0
public let cellHeight: CGFloat = 28.0
public let selectionLineHeight: CGFloat = 4.0
public let minimumItemWidth: CGFloat = 32.0
public let selectionLineMovingTime: TimeInterval = 0.2
weak public var delegate: TabsDelegate? {
didSet {
if let delegate {
onTabDidSelect = { [weak self] index in
guard let self else { return }
delegate.didSelectItem(.init(row: index, section: 0), tabs: self)
}
onTabShouldSelect = { [weak self] index in
guard let self else { return true }
return delegate.shouldSelectItem(.init(row: index, section: 0), tabs: self)
}
}
}
}
//--------------------------------------------------
// MARK: - Public Properties Overrides
//--------------------------------------------------
open override var selectedIndex: Int {
didSet {
guard let viewModel else { return }
viewModel.selectedIndex = selectedIndex
}
}
//-------------------------------------------------
// MARK: - Layout Views
//-------------------------------------------------
open override func reset() {
super.reset()
selectedIndex = 0
paddingBeforeFirstTab = true
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
collectionView?.updateView(size)
}
open override func setupView() {
super.setupView()
backgroundColor = VDSColor.backgroundPrimaryLight
addSubview(bottomLine)
setupCollectionView()
setupSelectionLine()
setupConstraints()
}
func setupCollectionView () {
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
open func updateView(_ size: CGFloat) {}
let collectionView = CollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(TabItemCell.self, forCellWithReuseIdentifier: TabCellId)
collectionView.backgroundColor = .clear
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.dataSource = self
collectionView.delegate = self
addSubview(collectionView)
self.collectionView = collectionView
}
func setupSelectionLine() {
bottomScrollView.translatesAutoresizingMaskIntoConstraints = false
bottomScrollView.delegate = self
addSubview(bottomScrollView)
bottomScrollView.addSubview(bottomContentView)
selectionLine.backgroundColor = VDSColor.paletteRed
bottomContentView.addSubview(selectionLine)
bringSubviewToFront(bottomScrollView)
}
func setupConstraints() {
//collection view
NSLayoutConstraint.constraintPinSubview(collectionView, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true)
collectionView?.heightAnchor.constraint(equalToConstant: cellHeight).isActive = true
//selection line
bottomScrollView.topAnchor.constraint(equalTo: collectionView!.bottomAnchor).isActive = true;
NSLayoutConstraint.constraintPinSubview(bottomScrollView, pinTop: false, pinBottom: false, pinLeft: true, pinRight: true)
bottomScrollView.heightAnchor.constraint(equalToConstant: selectionLineHeight).isActive = true
NSLayoutConstraint.constraintPinSubview(selectionLine, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false)
selectionLine.heightAnchor.constraint(equalToConstant: selectionLineHeight).isActive = true
selectionLineLeftConstraint = selectionLine.leftAnchor.constraint(equalTo: bottomContentView.leftAnchor)
selectionLineLeftConstraint?.isActive = true
selectionLineWidthConstraint = selectionLine.widthAnchor.constraint(equalToConstant: minimumItemWidth)
selectionLineWidthConstraint?.isActive = true
NSLayoutConstraint.constraintPinSubview(toSuperview: bottomContentView)
//bottom line
bottomLine.topAnchor.constraint(equalTo: bottomScrollView.bottomAnchor).isActive = true;
NSLayoutConstraint.constraintPinSubview(bottomLine, pinTop: false, pinBottom: true, pinLeft: true, pinRight: true)
}
open override func layoutSubviews() {
super.layoutSubviews()
// Accounts for any collection size changes
DispatchQueue.main.async {
self.layoutCollection()
}
}
/// Invalidates the layout and ensures we are paged to the correct cell.
open func layoutCollection() {
collectionView?.collectionViewLayout.invalidateLayout()
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
DispatchQueue.main.async {
self.collectionView?.scrollToItem(at: IndexPath(row: self.selectedIndex, section: 0), at: .left, animated: false)
self.collectionView?.layoutIfNeeded()
}
}
//-------------------------------------------------
// MARK: - Control Methods
//-------------------------------------------------
public func selectIndex(_ index: Int, animated: Bool) {
guard let _ = collectionView, tabsModel?.tabs.count ?? 0 > 0 else {
selectedIndex = index
tabsModel?.selectedIndex = index
return
}
MVMCoreDispatchUtility.performBlock(onMainThread: {
let currentIndex = self.selectedIndex
self.selectedIndex = index
self.tabsModel?.selectedIndex = index
self.deselect(indexPath: IndexPath(row: currentIndex, section: 0))
self.selectItem(atIndexPath: IndexPath(row: index, section: 0), animated: animated)
})
self.selectedIndex = index
}
public func reloadData() {
collectionView?.reloadData()
}
public func reloadData() { setNeedsUpdate() }
//-------------------------------------------------
// MARK: - Molecule Setup
//-------------------------------------------------
override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
self.additionalData = additionalData
selectedIndex = tabsModel?.selectedIndex ?? 0
selectionLine.backgroundColor = tabsModel?.selectedBarColor.uiColor
let lineModel = bottomLine.viewModel ?? LineModel(type: .secondary)
lineModel.inverted = tabsModel?.style == .dark
bottomLine.set(with: lineModel, delegateObject, additionalData)
reloadData()
open func viewModelDidUpdate() {
orientation = viewModel.orientation
indicatorPosition = viewModel.indicatorPosition
overflow = viewModel.overflow
size = viewModel.size
selectedIndex = viewModel.selectedIndex
surface = viewModel.style ?? .light
fillContainer = viewModel.fillContainer
tabModels = viewModel.tabs.compactMap { TabModel(text: $0.label.text) }
}
}
//-------------------------------------------------
// MARK: - Collection View Methods
//-------------------------------------------------
extension Tabs: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tabsModel?.tabs.count ?? 0
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TabCellId, for: indexPath) as? TabItemCell else {
return UICollectionViewCell()
}
cell.updateCell(indexPath: indexPath, delegateObject: delegateObject, additionalData: additionalData, selected: indexPath.row == selectedIndex, tabsModel: tabsModel)
updateView(collectionView.bounds.width)
return cell
}
}
extension Tabs: UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
guard self.collectionView(collectionView, numberOfItemsInSection: indexPath.section) != 2 else {
// If two tabs, take up the screen
let insets = self.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: indexPath.section)
let width = (collectionView.bounds.width / 2.0) - insets.left - insets.right
return CGSize(width: width, height: cellHeight)
}
guard let labelModel = tabsModel?.tabs[indexPath.row].label else {
return .zero
}
return CGSize(width: max(minimumItemWidth, getLabelWidth(labelModel).width), height: cellHeight)
}
//pre calculate the width of the collection cell
//when user select tabs, it will reload related collectionview, if we use autosize, it would relayout the width, need to keep the cell width constant.
func getLabelWidth(_ labelModel: LabelModel?) -> CGSize {
guard let labelModel = labelModel else { return .zero}
widthLabel.set(with: labelModel, nil, nil)
let cgSize = widthLabel.intrinsicContentSize
widthLabel.reset()
return cgSize
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
guard section == 0 else {
return UIEdgeInsets(top: 0, left: itemSpacing, bottom: 0, right: 0)
}
guard paddingBeforeFirstTab else {
return .zero
}
return UIEdgeInsets(top: 0, left: Padding.Component.horizontalPaddingForApplicationWidth, bottom: 0, right: 0)
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
// If two tabs, take up the screen, no space between items
guard self.collectionView(collectionView, numberOfItemsInSection: section) != 2 else {
return 0
}
return itemSpacing
}
public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return delegate?.shouldSelectItem(indexPath, tabs: self) ?? true
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectIndex(indexPath.row, animated: true)
}
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let tabCell = cell as? TabItemCell else { return }
if indexPath.row == selectedIndex {
DispatchQueue.main.async {
self.moveSelectionLine(toIndex: indexPath, animated: false, cell: tabCell)
}
}
}
func deselect(indexPath:IndexPath) {
collectionView?.deselectItem(at: indexPath, animated: false)
collectionView?.reloadItems(at: [indexPath])
}
func selectItem(atIndexPath indexPath: IndexPath, animated: Bool) {
guard let collect = collectionView, tabsModel?.tabs.count ?? 0 > 0 else { return }
collect.selectItem(at: indexPath, animated: animated, scrollPosition: .centeredHorizontally)
guard let tabCell = collect.cellForItem(at: indexPath) as? TabItemCell, let tabsModel = tabsModel else { return }
moveSelectionLine(toIndex: indexPath, animated: animated, cell: tabCell)
tabCell.label.textColor = tabsModel.selectedColor.uiColor
tabCell.updateAccessibility(indexPath: indexPath, selected: true, tabsModel: tabsModel)
tabCell.setNeedsDisplay()
tabCell.setNeedsLayout()
tabCell.layoutIfNeeded()
if let delegate = delegate {
delegate.didSelectItem(indexPath, tabs: self)
} else if let action = tabsModel.tabs[selectedIndex].action {
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: tabsModel, additionalData: nil, delegateObject: delegateObject)
}
if UIAccessibility.isVoiceOverRunning {
UIAccessibility.post(notification: .layoutChanged, argument: tabCell)
}
}
}
extension Tabs: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
/*bottomScrollview is subview of self, it's not belongs to collectionview.
When collectionview is scrolling, bottomScrollView will stay without moving
Adding collectionview's offset to bottomScrollView, will make the bottomScrollview looks like scrolling with the selected tab item.
*/
guard let offsetX = collectionView?.contentOffset.x else { return }
bottomScrollView.setContentOffset(CGPoint(x: offsetX, y: bottomScrollView.contentOffset.y), animated: false)
}
}
//-------------------------------------------------
// MARK: - Bottom Line Methods
//-------------------------------------------------
extension Tabs {
func moveSelectionLine(toIndex indexPath: IndexPath, animated: Bool, cell: TabItemCell) {
guard let collect = collectionView else {return}
let size = collectionView(collect, layout: layout, sizeForItemAt: indexPath)
let animationBlock = {
[weak self] in
self?.selectionLineWidthConstraint?.constant = size.width
self?.selectionLineLeftConstraint?.constant = cell.frame.origin.x
self?.bottomContentView.layoutIfNeeded()
}
if animated {
UIView.animate(withDuration: selectionLineMovingTime, animations: animationBlock)
} else {
animationBlock()
}
}
/// Adjust the line based on the percentage
func progress(from index: Int, toIndex: Int, percentage: CGFloat) {
let fromIndexPath = IndexPath(row: index, section: 0)
let toIndexPath = IndexPath(row: toIndex, section: 0)
guard let collection = collectionView,
let fromCell = collection.cellForItem(at: fromIndexPath),
let toCell = collection.cellForItem(at: toIndexPath) else { return }
// setting the width for percentage
selectionLineWidthConstraint?.constant = (toCell.bounds.width - fromCell.bounds.width) * percentage + fromCell.bounds.width
// setting the x for percentage
let originalX = fromCell.frame.origin.x
let toX = toCell.frame.origin.x
let xDifference = toX - originalX
let finalX = (xDifference * percentage) + originalX
selectionLineLeftConstraint?.constant = finalX
bottomContentView.layoutIfNeeded()
}
}
@objcMembers public class TabItemCell: CollectionViewCell {
public let label = Label()
public override func setupView() {
super.setupView()
contentView.addSubview(label)
NSLayoutConstraint.constraintPinSubview(label, pinTop: false, topConstant: 0, pinBottom: true, bottomConstant: 6, pinLeft: true, leftConstant: 0, pinRight: true, rightConstant: 0)
label.baselineAdjustment = .alignCenters
}
public override func updateView(_ size: CGFloat) {
super.updateView(size)
label.updateView(size)
}
public func updateCell(indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, selected: Bool, tabsModel: TabsModel?) {
guard let tabsModel = tabsModel else { return }
label.reset()
label.set(with: tabsModel.tabs[indexPath.row].label, delegateObject, additionalData)
if selected {
label.textColor = tabsModel.selectedColor.uiColor
} else {
label.textColor = tabsModel.unselectedColor.uiColor
}
updateAccessibility(indexPath: indexPath, selected: selected, tabsModel: tabsModel)
}
public func updateAccessibility(indexPath: IndexPath, selected: Bool, tabsModel: TabsModel?) {
//Accessibility
isAccessibilityElement = false
contentView.isAccessibilityElement = true
let accKey = selected ? "toptabbar_tab_selected" : "AccTab"
let accLabel = "\(label.text ?? "") \(MVMCoreUIUtility.hardcodedString(withKey: accKey) ?? "")"
let accOrder = String(format: MVMCoreUIUtility.hardcodedString(withKey: "AccTabIndex") ?? "", indexPath.row + 1, tabsModel?.tabs.count ?? 0)
contentView.accessibilityLabel = "\(accLabel) \(accOrder)"
contentView.accessibilityHint = selected ? nil : MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
}
}

View File

@ -8,67 +8,23 @@
import UIKit
import VDSColorTokens
import VDS
open class TabsModel: MoleculeModelProtocol {
public static var identifier: String = "tabs"
public var id: String = UUID().uuidString
open var tabs: [TabItemModel]
open var style: NavigationItemStyle?
private var _backgroundColor: Color?
open var backgroundColor: Color? {
get {
if let backgroundColor = _backgroundColor { return backgroundColor }
if let style = style,
style == .dark { return Color(uiColor: VDSColor.backgroundPrimaryDark) }
return Color(uiColor: VDSColor.backgroundPrimaryLight)
}
set {
_backgroundColor = newValue
}
}
private var _selectedColor: Color?
open var selectedColor: Color {
get {
if let selectedColor = _selectedColor { return selectedColor }
if let style = style,
style == .dark { return Color(uiColor: VDSColor.elementsPrimaryOndark) }
return Color(uiColor: VDSColor.elementsPrimaryOnlight)
}
set {
_selectedColor = newValue
}
}
private var _unselectedColor: Color?
open var unselectedColor: Color {
get {
if let unselectedColor = _unselectedColor { return unselectedColor }
if let style = style,
style == .dark { return Color(uiColor: VDSColor.elementsSecondaryOndark) }
return Color(uiColor: VDSColor.elementsSecondaryOnlight)
}
set {
_unselectedColor = newValue
}
}
private var _selectedBarColor: Color?
open var selectedBarColor: Color {
get {
if let selectedBarColor = _selectedBarColor { return selectedBarColor }
if let style = style,
style == .dark { return Color(uiColor: VDSColor.elementsPrimaryOndark) }
return Color(uiColor: VDSColor.paletteRed)
}
set {
_selectedBarColor = newValue
}
}
open var style: Surface?
open var orientation: Tabs.Orientation = .horizontal
open var indicatorPosition: Tabs.IndicatorPosition = .bottom
open var overflow: Tabs.Overflow = .scroll
open var fillContainer: Bool = false
open var size: Tabs.Size = .medium
public var backgroundColor: Color?
// Must be capped to 0...(tabs.count - 1)
open var selectedIndex: Int = 0
@ -77,10 +33,12 @@ open class TabsModel: MoleculeModelProtocol {
case moleculeName
case tabs
case backgroundColor
case selectedColor
case unselectedColor
case selectedBarColor
case selectedIndex
case orientation
case indicatorPosition
case fillContainer
case overflow
case size
case style
}
@ -92,14 +50,31 @@ open class TabsModel: MoleculeModelProtocol {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
tabs = try typeContainer.decode([TabItemModel].self, forKey: .tabs)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
_selectedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor)
_unselectedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .unselectedColor)
_selectedBarColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedBarColor)
style = try typeContainer.decodeIfPresent(NavigationItemStyle.self, forKey: .style)
style = try typeContainer.decodeIfPresent(Surface.self, forKey: .style)
if let fillContainer = try typeContainer.decodeIfPresent(Bool.self, forKey: .fillContainer) {
self.fillContainer = fillContainer
}
if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) {
selectedIndex = index
}
if let orientation = try typeContainer.decodeIfPresent(VDS.Tabs.Orientation.self, forKey: .orientation) {
self.orientation = orientation
}
if let indicatorPosition = try typeContainer.decodeIfPresent(VDS.Tabs.IndicatorPosition.self, forKey: .indicatorPosition) {
self.indicatorPosition = indicatorPosition
}
if let overflow = try typeContainer.decodeIfPresent(VDS.Tabs.Overflow.self, forKey: .overflow) {
self.overflow = overflow
}
if let size = try typeContainer.decodeIfPresent(VDS.Tabs.Size.self, forKey: .orientation) {
self.size = size
}
}
open func encode(to encoder: Encoder) throws {
@ -107,12 +82,14 @@ open class TabsModel: MoleculeModelProtocol {
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(tabs, forKey: .tabs)
try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(_selectedColor, forKey: .selectedColor)
try container.encodeIfPresent(_unselectedColor, forKey: .unselectedColor)
try container.encodeIfPresent(_selectedBarColor, forKey: .selectedBarColor)
try container.encode(selectedIndex, forKey: .selectedIndex)
try container.encode(fillContainer, forKey: .fillContainer)
try container.encodeIfPresent(style, forKey: .style)
try container.encode(orientation, forKey: .orientation)
try container.encode(overflow, forKey: .overflow)
try container.encode(size, forKey: .size)
try container.encode(indicatorPosition, forKey: .indicatorPosition)
}
}

View File

@ -9,144 +9,69 @@
import UIKit
import VDS
@objcMembers open class TwoButtonView: View, MVMCoreUIViewConstrainingProtocol {
@objcMembers open class TwoButtonView: VDS.View, VDSMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var primaryButton: PillButton = PillButton()
open var secondaryButton: PillButton = PillButton()
private var stack = UIStackView()
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
private var equalWidthConstraint: NSLayoutConstraint?
open var viewModel: TwoButtonViewModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
open var primaryButton = PillButton()
open var secondaryButton = PillButton()
private var buttonGroup = VDS.ButtonGroup()
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public func setDefaultAppearance() {
primaryButton.use = .primary
secondaryButton.use = .secondary
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
primaryButton.updateView(size)
secondaryButton.updateView(size)
}
open override func setupView() {
super.setupView()
stack.translatesAutoresizingMaskIntoConstraints = false
open override func setup() {
super.setup()
isAccessibilityElement = false
addSubview(stack)
stack.addArrangedSubview(secondaryButton)
stack.addArrangedSubview(primaryButton)
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
stack.axis = .horizontal
stack.spacing = Padding.Component.gutterForApplicationWidth
equalWidthConstraint = secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1)
equalWidthConstraint?.isActive = true
addSubview(buttonGroup)
buttonGroup.pinToSuperView()
buttonGroup.alignment = .center
buttonGroup.rowQuantityPhone = 2
buttonGroup.rowQuantityTablet = 2
}
//--------------------------------------------------
// MARK: - Stack Manipulation
//--------------------------------------------------
public func showPrimaryButton() {
if !stack.arrangedSubviews.contains(primaryButton) {
stack.addArrangedSubview(primaryButton)
primaryButton.isHidden = false
}
if secondaryButton.superview != nil {
equalWidthConstraint?.isActive = true
}
primaryButton.isAccessibilityElement = true
}
public func showSecondaryButton() {
if !stack.arrangedSubviews.contains(secondaryButton) {
stack.insertArrangedSubview(secondaryButton, at: 0)
secondaryButton.isHidden = false
}
if primaryButton.superview != nil {
equalWidthConstraint?.isActive = true
}
secondaryButton.isAccessibilityElement = true
}
public func hidePrimaryButton() {
if primaryButton.superview != nil {
stack.removeArrangedSubview(primaryButton)
primaryButton.isHidden = true
}
primaryButton.isAccessibilityElement = false
equalWidthConstraint?.isActive = false
}
public func hideSecondaryButton() {
if secondaryButton.superview != nil {
stack.removeArrangedSubview(secondaryButton)
secondaryButton.isHidden = true
}
secondaryButton.isAccessibilityElement = false
equalWidthConstraint?.isActive = false
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func reset() {
super.reset()
setDefaultAppearance()
buttonGroup.reset()
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
guard let model = model as? TwoButtonViewModel,
let buttonModel = model.primaryButton ?? model.secondaryButton
else { return 0 }
return PillButton.estimatedHeight(with: buttonModel, delegateObject)
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? TwoButtonViewModel else { return }
if let secondaryModel = model.secondaryButton {
showSecondaryButton()
public func viewModelDidUpdate() {
var buttons = [PillButton]()
if let secondaryModel = viewModel.secondaryButton {
secondaryButton.set(with: secondaryModel, delegateObject, additionalData)
} else {
hideSecondaryButton()
buttons.append(secondaryButton)
}
if let primaryModel = model.primaryButton {
showPrimaryButton()
if let primaryModel = viewModel.primaryButton {
primaryButton.set(with: primaryModel, delegateObject, additionalData)
} else {
hidePrimaryButton()
buttons.append(primaryButton)
}
buttonGroup.childWidth = viewModel.fillContainer ? .percentage(100) : nil
if buttons.count != buttonGroup.buttons.count {
buttonGroup.buttons = buttons
}
}
//--------------------------------------------------
// MARK: - MVMCoreUIViewConstrainingProtocol
//--------------------------------------------------
@ -154,4 +79,12 @@ import VDS
open func horizontalAlignment() -> UIStackView.Alignment {
return .center
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
public func updateView(_ size: CGFloat) {
setNeedsUpdate()
}
}

View File

@ -19,10 +19,9 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
public var backgroundColor: Color?
public var primaryButton: ButtonModel?
public var secondaryButton: ButtonModel?
public var fillContainer: Bool = false
public var children: [MoleculeModelProtocol] {
return [primaryButton, secondaryButton].compactMap { $0 }
}
public var children: [MoleculeModelProtocol] { [primaryButton, secondaryButton].compactMap { $0 } }
//--------------------------------------------------
// MARK: - Keys

View File

@ -7,79 +7,78 @@
//
import Foundation
import VDS
@objcMembers open class TwoLinkView: View, MVMCoreUIViewConstrainingProtocol {
@objcMembers open class TwoLinkView: VDS.View, VDSMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var viewModel: TwoLinkViewModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
open var leftLink = Link()
open var rightLink = Link()
private var stack = UIStackView()
private var buttonGroup = VDS.ButtonGroup()
private var buttons: [Link] = []
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func setup() {
super.setup()
isAccessibilityElement = false
addSubview(buttonGroup)
buttonGroup.pinToSuperView()
buttonGroup.alignment = .center
buttonGroup.rowQuantityPhone = 2
buttonGroup.rowQuantityTablet = 2
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open override func updateView(_ size: CGFloat) {
super.updateView(size)
stack.updateView(size)
open override func reset() {
super.reset()
buttonGroup.reset()
}
open override func setupView() {
super.setupView()
stack.translatesAutoresizingMaskIntoConstraints = false
addSubview(stack)
stack.addArrangedSubview(leftLink)
stack.addArrangedSubview(rightLink)
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
stack.axis = .horizontal
stack.spacing = 8
}
open func updateView(_ size: CGFloat) { }
//--------------------------------------------------
// MARK: - Stack Manipulation
//--------------------------------------------------
public func showRightLink() {
if !stack.arrangedSubviews.contains(rightLink) {
stack.addArrangedSubview(rightLink)
rightLink.isHidden = false
if !buttons.contains(rightLink) {
buttons.insert(rightLink, at: buttons.count)
}
buttonGroup.buttons = buttons
}
public func showLeftLink() {
if !stack.arrangedSubviews.contains(leftLink) {
stack.insertArrangedSubview(leftLink, at: 0)
leftLink.isHidden = false
if !buttons.contains(leftLink) {
buttons.insert(leftLink, at: 0)
}
buttonGroup.buttons = buttons
}
public func hideRightLink() {
if rightLink.superview != nil {
stack.removeArrangedSubview(rightLink)
rightLink.isHidden = true
if let index = buttons.firstIndex(of: rightLink) {
buttons.remove(at: index)
}
buttonGroup.buttons = buttons
}
public func hideLeftLink() {
if leftLink.superview != nil {
stack.removeArrangedSubview(leftLink)
leftLink.isHidden = true
if let index = buttons.firstIndex(of: leftLink) {
buttons.remove(at: index)
}
buttonGroup.buttons = buttons
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func reset() {
super.reset()
stack.reset()
}
//--------------------------------------------------
// MARK: - MVMCoreUIViewConstrainingProtocol
//--------------------------------------------------
@ -92,27 +91,28 @@ import Foundation
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 16
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
guard let model = model as? TwoButtonViewModel,
let buttonModel = model.primaryButton ?? model.secondaryButton
else { return 0 }
return PillButton.estimatedHeight(with: buttonModel, delegateObject)
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
public func viewModelDidUpdate() {
buttons.removeAll()
guard let model = model as? TwoLinkViewModel else { return }
if let model = model.leftLink {
showLeftLink()
if let model = viewModel.leftLink {
leftLink.set(with: model, delegateObject, additionalData)
} else {
hideLeftLink()
buttons.append(leftLink)
}
if let model = model.rightLink {
showRightLink()
if let model = viewModel.rightLink {
rightLink.set(with: model, delegateObject, additionalData)
} else {
hideRightLink()
buttons.append(rightLink)
}
buttonGroup.buttons = buttons
}
}

View File

@ -8,7 +8,7 @@
import Foundation
public class TwoLinkViewModel: MoleculeModelProtocol {
public class TwoLinkViewModel: ParentMoleculeModelProtocol {
public static var identifier: String = "twoLinkView"
public var id: String = UUID().uuidString
@ -16,6 +16,8 @@ public class TwoLinkViewModel: MoleculeModelProtocol {
public var rightLink: LinkModel?
public var leftLink: LinkModel?
public var children: [MoleculeModelProtocol] { [rightLink, leftLink].compactMap{ $0 } }
private enum CodingKeys: String, CodingKey {
case id
case moleculeName

View File

@ -18,8 +18,9 @@
public var hideArrow: Bool?
public var line: LineModel?
public var style: ListItemStyle?
public var accessibilityTraits: UIAccessibilityTraits?
public var accessibilityValue: String?
public var accessibilityText: String?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
@ -30,6 +31,8 @@
case hideArrow
case line
case style
case accessibilityTraits
case accessibilityValue
case accessibilityText
}
@ -104,7 +107,8 @@
hideArrow = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideArrow)
line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line)
style = try typeContainer.decodeIfPresent(ListItemStyle.self, forKey: .style)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits)
accessibilityValue = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityValue)
try super.init(from: decoder)
}
@ -116,6 +120,8 @@
try container.encodeIfPresent(hideArrow, forKey: .hideArrow)
try container.encodeIfPresent(line, forKey: .line)
try container.encodeIfPresent(style, forKey: .style)
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
try container.encodeIfPresent(accessibilityValue, forKey: .accessibilityValue)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
}
}

View File

@ -81,7 +81,9 @@ extension TabsListItemModel: PageBehaviorProtocolRequirer {
extension TabsListItemModel: AddMolecules {
public func moleculesToAdd() -> AddMolecules.AddParameters? {
guard addedMolecules == nil else { return nil }
let addedMolecules = molecules[tabs.selectedIndex]
let index = tabs.selectedIndex
guard molecules.count >= index else { return nil }
let addedMolecules = molecules[index]
self.addedMolecules = addedMolecules
return (addedMolecules, .below)
}

View File

@ -19,10 +19,8 @@ import UIKit
// MARK: - MFViewProtocol
override public func setupView() {
super.setupView()
tabs.paddingBeforeFirstTab = false
tabs.translatesAutoresizingMaskIntoConstraints = false
tabs.delegate = self
tabs.bottomLine.setStyle(.none)
contentView.addSubview(tabs)
NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values))
@ -47,7 +45,6 @@ import UIKit
public override func reset() {
super.reset()
tabs.reset()
tabs.paddingBeforeFirstTab = false
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 46 }

View File

@ -75,7 +75,7 @@ open class ModuleMolecule: Container {
let _ = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else {
if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) {
error?.pointee = errorObject
MVMCoreUILoggingHandler.addError(toLog: errorObject)
MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject)
}
return nil
}

View File

@ -88,15 +88,15 @@
var message = ""
if let eyebrowLabel = eyebrow.text {
if let eyebrowLabel = eyebrow.accessibilityLabel ?? eyebrow.text {
message += eyebrowLabel + ", "
}
if let headlineLabel = headline.text {
if let headlineLabel = headline.accessibilityLabel ?? headline.text {
message += headlineLabel + ", "
}
if let bodyLabel = body.text {
if let bodyLabel = body.accessibilityLabel ?? body.text {
message += bodyLabel
}

View File

@ -87,7 +87,7 @@ open class Carousel: View {
showPeaking(false)
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
guard let model = model as? CarouselModel,
guard let model = model as? CarouselModel, !model.molecules.isEmpty,
(model.paging == true || loop == true) else { return }
DispatchQueue.main.async {
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)

View File

@ -8,7 +8,6 @@
import UIKit
@objcMembers public class CarouselModel: ParentMoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
@ -57,14 +56,14 @@ import UIKit
guard selectable else {
// Use visible item value, else index
if let fieldValue = molecules[index].formFieldValue() {
if let fieldValue = molecules[safe: index]?.formFieldValue() {
return fieldValue
}
return index
}
// Use selected item value, else index
guard let selectedIndex = selectedIndex else { return nil }
guard let fieldValue = molecules[selectedIndex].formFieldValue() else { return selectedIndex }
guard let fieldValue = molecules[safe: selectedIndex]?.formFieldValue() else { return selectedIndex }
return fieldValue
}

View File

@ -15,6 +15,7 @@ public protocol AccessibilityModelProtocol {
var accessibilityTraits: UIAccessibilityTraits? { get set }
var accessibilityText: String? { get set }
var accessibilityValue: String? { get set }
var accessibilityHint: String? { get set }
}
public extension AccessibilityModelProtocol {
@ -38,4 +39,9 @@ public extension AccessibilityModelProtocol {
get { nil }
set { }
}
var accessibilityHint: String? {
get { nil }
set {}
}
}

View File

@ -15,7 +15,7 @@ public enum ListItemStyle: String, Codable {
case none
}
public protocol ListItemModelProtocol: ContainerModelProtocol {
public protocol ListItemModelProtocol: ContainerModelProtocol, AccessibilityModelProtocol {
var line: LineModel? { get set }
var action: ActionModelProtocol? { get set }
var hideArrow: Bool? { get set }

View File

@ -77,7 +77,7 @@ public extension ModelRegistry {
return type
} catch {
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) {
MVMCoreLoggingHandler.addError(toLog: errorObject)
MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject)
}
return nil
}

View File

@ -163,6 +163,15 @@ import UIKit
// align if needed.
containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol)
if let traits = model.accessibilityTraits {
accessibilityTraits.update(with: traits)
}
if let accessibilityText = model.accessibilityText {
accessibilityLabel = accessibilityText
isAccessibilityElement = true
}
accessibilityValue = model.accessibilityValue
}
open func reset() {

View File

@ -98,7 +98,6 @@ import UIKit
smartInsertDeleteType = .no
inputAccessoryView = nil
isAccessibilityElement = true
accessibilityTraits = .staticText
font = fontStyle.getFont()
keyboardType = .default
isEditable = true

View File

@ -237,6 +237,10 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController, Rotor
// if footer already exists, use the same y location to avoid strange moving animation
let y = tableView.tableFooterView?.frame.minY ?? 0.0
//force footerView to redraw
footerView.setNeedsLayout()
footerView.layoutIfNeeded()
// This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout.
MVMCoreUIUtility.sizeView(toFit: footerView)
let tableFooterView = UIView(frame: CGRect(x: 0, y: y, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height))

View File

@ -115,7 +115,7 @@ import MVMCore
})
} catch {
if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") {
MVMCoreLoggingHandler.addError(toLog: coreError)
MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
}
}
}

View File

@ -33,7 +33,7 @@ public extension PageBehaviorHandlerProtocol {
behaviors.append(behavior)
} catch {
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) {
MVMCoreLoggingHandler.addError(toLog: errorObject)
MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject)
}
}
}

View File

@ -83,7 +83,6 @@ import Combine
}
extension NavigationController: MVMCoreViewManagerProtocol {
public func getAccessibilityElements() -> [Any]? {
nil
}

View File

@ -249,7 +249,6 @@ public extension MVMCoreUISplitViewController {
}
extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol {
public func getAccessibilityElements() -> [Any]? {
nil
}
@ -263,7 +262,7 @@ extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol {
}
public func willDisplay(_ viewController: UIViewController) {
setupPanels()
setupPanels(viewController)
updateState(with: viewController)
}

View File

@ -99,7 +99,7 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) {
- (void)setNavigationIconColor:(nullable UIColor *)color;
/// Updates the panels that are used.
- (void)setupPanels;
- (void)setupPanels:(nullable UIViewController*)viewController;
/// Returns if the left panel is staying extended (usually do to screen size threshold)
- (BOOL)leftPanelStaysExtended;

View File

@ -723,9 +723,9 @@ CGFloat const PanelAnimationDuration = 0.2;
[panel removeFromParentViewController];
}
- (void)setupLeftPanel {
- (void)setupLeftPanel:(nullable UIViewController*)viewController {
UIViewController <MVMCoreUIPanelProtocol> *panel = nil;
UIViewController *currentViewController = [self getCurrentDetailViewController];
UIViewController *currentViewController = viewController ? viewController : [self getCurrentDetailViewController];
if ([currentViewController respondsToSelector:@selector(overrideLeftPanel)]) {
panel = [((UIViewController <MVMCoreUIDetailViewProtocol> *)currentViewController) overrideLeftPanel];
} else {
@ -735,6 +735,7 @@ CGFloat const PanelAnimationDuration = 0.2;
if (!panel) {
[self removePanel:self.leftPanel];
self.leftPanel = nil;
} else if (panel && panel != self.leftPanel) {
[self removePanel:self.leftPanel];
[self addPanel:panel];
@ -795,9 +796,9 @@ CGFloat const PanelAnimationDuration = 0.2;
}
}
- (void)setupPanels {
- (void)setupPanels:(nullable UIViewController*)viewController {
[self forceHideBothDrawers];
[self setupLeftPanel];
[self setupLeftPanel:viewController];
[self setupRightPanel];
self.explictlyShowingPanel = nil;
[self.view layoutIfNeeded];
@ -968,7 +969,7 @@ CGFloat const PanelAnimationDuration = 0.2;
[NSLayoutConstraint constraintWithItem:coverView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0].active = YES;
[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:coverView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0].active = YES;
[self setupPanels];
[self setupPanels:nil];
}
- (void)viewDidLoad {

View File

@ -84,10 +84,17 @@ public final class Color: Codable {
required public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let colorString = try container.decode(String.self)
let components = try Color.getColorComponents(for: colorString)
self.uiColor = components.color
hex = components.hex
name = components.name ?? ""
if let vdsColor = UIColor.VDSColor(rawValue: colorString) {
self.uiColor = vdsColor.uiColor
hex = uiColor.hexString ?? ""
} else {
let components = try Color.getColorComponents(for: colorString)
self.uiColor = components.color
hex = components.hex
name = components.name ?? ""
}
determineRGBA()
}

View File

@ -95,7 +95,7 @@ import MVMCore
groupValid = try validateGroup(group)
} catch {
if let err = MVMCoreErrorObject.createErrorObject(for: error, location: "FormValidator"){
MVMCoreLoggingHandler.addError(toLog: err)
MVMCoreLoggingHandler.shared()?.addError(toLog: err)
fatalError(err.description)
}
}

View File

@ -0,0 +1,41 @@
//
// MFLoadingSpinner+VDS.swift
// MVMCoreUI
//
// Created by Matt Bruce on 1/3/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
extension MFLoadingSpinner {
var loader: Loader? {
subviews.first as? Loader
}
@objc open func setSurface(_ strokeColor: UIColor?) {
if let strokeColor {
loader?.surface = strokeColor.isDark() ? .light : .dark
}
}
@objc open func pause() {
loader?.isActive = false
}
@objc open func resume() {
loader?.isActive = true
}
@objc open func resumeAfterDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
self?.loader?.isActive = true
}
}
@objc open func pin() -> NSDictionary? {
guard let size = loader?.size else { return nil }
return NSLayoutConstraint.constraintPinView(self, heightConstraint: true, heightConstant: CGFloat(size), widthConstraint: true, widthConstant: CGFloat(size)) as NSDictionary?
}
}

View File

@ -7,143 +7,73 @@
//
#import "MFLoadingSpinner.h"
#import "UIColor+MFConvenience.h"
#import "NSLayoutConstraint+MFConvenience.h"
#import <VDS/VDS.h>
#import <MVMCoreUI/MVMCoreUI-Swift.h>
@interface MFLoadingSpinner ()
@property (strong, nonatomic) CAShapeLayer *myCircle;
@property (strong, nonatomic) CADisplayLink *myDisplay;
@property (weak, nonatomic) dispatch_block_t resumeBlock;
@property (nonatomic) double prevFrame;
@property (nonatomic) BOOL isFast;
@property (strong, nonatomic) VDSLoader *loader;
@end
@implementation MFLoadingSpinner
- (instancetype)init {
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder: coder];
if (self) {
[self setup];
}
return self;
}
const float radius = 19;
const float lineWidth = 3.0;
const float slowSpeed = 0.5;
const float fastSpeed = 2.0;
const float startSpeed = 1.0;
const float fastDistance = .45;
const float slowDistance = 0.1;
-(void)finalize {
[self.myDisplay invalidate];
self.myDisplay = nil;
-(void) setup {
if (self.loader) { return; }
self.loader = [[VDSLoader alloc] init];
[self addSubview: self.loader];
[NSLayoutConstraint pinViewToSuperview:self.loader useMargins:false];
}
-(void)setUpCircle {
[self setUpCircle:[UIColor blackColor]];
[self setSurface: UIColor.blackColor];
}
-(void)setUpCircle:(UIColor *)strokeColor {
if(self.myCircle)
{
return;
}
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius + lineWidth/2, radius + lineWidth/2) radius:radius startAngle:-M_PI_2 endAngle:3.5*M_PI clockwise:YES].CGPath;
circle.lineWidth = lineWidth;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = strokeColor.CGColor;
circle.lineCap = kCALineCapButt;
circle.strokeStart = 0;
circle.strokeEnd = 0+.05;
[self.layer addSublayer:circle];
self.myCircle = circle;
self.isFast = YES;
NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"strokeStart",
[NSNull null], @"strokeEnd",
[NSNull null], @"strokeColor",
nil];
circle.actions = newActions;
self.myDisplay = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateSpinner)];
[self.myDisplay addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
self.myDisplay.frameInterval = 2;
self.prevFrame = CACurrentMediaTime();
-(void)setUpCircle:(nullable UIColor *)strokeColor {
[self setSurface: strokeColor];
}
-(void)changeColor:(UIColor *)strokeColor {
self.myCircle.strokeColor = strokeColor.CGColor;
}
-(void)updateSpinner {
double currentTime = CACurrentMediaTime();
double renderTime = currentTime - self.prevFrame;
self.prevFrame = currentTime;
if(self.myCircle.strokeStart > 0.5 && self.myCircle.strokeEnd > 0.5) {
self.myCircle.strokeStart -= 0.5;
self.myCircle.strokeEnd -= 0.5;
}
float distanceToStart = self.myCircle.strokeEnd - self.myCircle.strokeStart;
if(distanceToStart < slowDistance && !self.isFast) {
self.isFast = YES;
}
else if(distanceToStart > fastDistance && self.isFast) {
self.isFast = NO;
}
self.myCircle.strokeEnd += (self.isFast ? fastSpeed : slowSpeed) * renderTime;
self.myCircle.strokeStart+= startSpeed * renderTime;
-(void)changeColor:(nullable UIColor *)strokeColor {
[self setSurface: strokeColor];
}
- (void)pauseSpinner {
if (self.resumeBlock) {
// Cancel the current resume block if it hasn't run. dispatch our pause into the same queue incase the resume block is already running.
dispatch_block_cancel(self.resumeBlock);
self.resumeBlock = nil;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.myDisplay.paused = YES;
weakSelf.hidden = YES;
});
} else {
self.myDisplay.paused = YES;
self.hidden = YES;
}
[self pause];
}
- (void)resumeSpinner {
self.hidden = NO;
if (!self.myCircle) {
[self setUpCircle];
return;
}
self.myDisplay.paused = NO;
self.prevFrame = CACurrentMediaTime();
[self resume];
}
// Starts the spinner after a slight delay.
- (void)resumeSpinnerAfterDelay {
if (!self.resumeBlock) {
__weak typeof(self) weakSelf = self;
dispatch_block_t resume = dispatch_block_create(0, ^{
[weakSelf resumeSpinner];
weakSelf.resumeBlock = nil;
});
self.resumeBlock = resume;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), resume);
}
[self resumeAfterDelay];
}
- (nullable NSDictionary *)pinWidthAndHeight {
CGFloat diameter = radius*2 + lineWidth;
return [NSLayoutConstraint constraintPinView:self heightConstraint:YES heightConstant:diameter widthConstraint:YES widthConstant:diameter];
return [self pin];
}
@end

View File

@ -17,7 +17,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <MVMCoreUI/PublicHeader.h>
#pragma mark - OtherHandlers
#import <MVMCoreUI/MVMCoreUISession.h>
#import <MVMCoreUI/MVMCoreUILoggingHandler.h>
#import <MVMCoreUI/MVMCoreUIViewControllerMappingObject.h>
#import <MVMCoreUI/MVMCoreUIViewConstrainingProtocol.h>

View File

@ -8,9 +8,12 @@
import Foundation
import MVMCore
import VDSColorTokens
open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, TabsDelegate, MVMCorePresentationDelegateProtocol, SubNavSwipeNavigationProtocol {
/// The number of tabs count or less that will turn on the fillContainer
public var fillContainerTabsCount = 2 { didSet { tabs.fillContainer = tabsModel.tabs.count <= fillContainerTabsCount }}
/// The current managed view controller
private var viewController: UIViewController
@ -19,16 +22,37 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
/// Used to layout the ui.
public lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [tabs, subNavigationController.view])
let stackView = UIStackView(arrangedSubviews: [tabsWrapper, subNavigationController.view])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.isAccessibilityElement = false
stackView.axis = .vertical
return stackView
}()
private var tabsLeadingConstraint: NSLayoutConstraint?
private var tabsTrailingConstraint: NSLayoutConstraint?
private lazy var tabsWrapper: UIView = {
// create the wrapper view for tabs
let tabsWrapper = UIView()
tabsWrapper.translatesAutoresizingMaskIntoConstraints = false
tabsWrapper.addSubview(tabs)
// tabs constraints on wrapper
tabs.pinTop()
tabs.pinBottom()
tabsLeadingConstraint = tabs.pinLeading(anchor: tabsWrapper.leadingAnchor)?.activate()
tabsTrailingConstraint = tabsWrapper.pinTrailing(anchor: tabs.trailingAnchor)?.activate()
return tabsWrapper
}()
private var isFillContainer: Bool { tabsModel.tabs.count <= fillContainerTabsCount }
private var tabsModel: TabsModel
public lazy var tabs: Tabs = {
let tabs = Tabs(model: tabsModel, delegateObjectIVar, nil)
tabs.backgroundColor = (viewController as? PageProtocol)?.pageModel?.navigationBar?.backgroundColor?.uiColor ?? VDSColor.backgroundPrimaryLight
tabs.delegate = self
return tabs
}()
@ -56,7 +80,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
super.init(nibName: nil, bundle: nil)
setup(with: loadObject, shouldEnableSwipeGestures: shouldEnableSwipeGestures)
}
public init(viewControllers: [UIViewController], tabsModel: TabsModel, loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) {
self.tabsModel = tabsModel
self.viewControllers = viewControllers
@ -68,6 +92,11 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
func setup(with loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) {
self.loadObject = loadObject
pageType = loadObject.pageType
//update the tabs
tabs.fillContainer = isFillContainer
updateTabsMargin()
if shouldEnableSwipeGestures {
customInteractor = SubNavInteractor(viewController: self)
}
@ -121,6 +150,12 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
super.viewWillAppear(animated)
hideNavigationBarLine(true)
}
/// ensures margin for tabs are correct
private func updateTabsMargin() {
let tabsGutterSpacing = isFillContainer ? 0 : Padding.Component.horizontalPaddingForSize(view.bounds.size.width)
tabsLeadingConstraint?.constant = tabsGutterSpacing
tabsTrailingConstraint?.constant = tabsGutterSpacing
}
/// Hides/Shows the navigation bar for the page.
open func hideNavigationBarLine(_ isHidden: Bool) {
@ -139,6 +174,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
super.updateViews()
if screenSizeChanged() {
tabs.updateView(view.bounds.size.width)
updateTabsMargin()
}
}
@ -157,7 +193,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
/// Logs the action for the selected tab.
open func trackSelectTab() {
guard let action = tabs.tabsModel?.tabs[tabs.selectedIndex].action else { return }
guard let action = tabs.viewModel?.tabs[tabs.selectedIndex].action else { return }
MVMCoreUIActionHandler.shared()?.logAction(with: action.toJSON(), additionalData: getAdditionalDataForNewTabLoad(indexPath: IndexPath(row: tabs.selectedIndex, section: 0)), delegateObject: delegateObjectIVar)
}
@ -276,7 +312,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
Task(priority: .userInitiated) {
await NavigationHandler.shared().replace(viewController: controller, navigationController:subNavigationController, delegateObject:delegateObject(), tryToReplace: false, animated: true)
}
} else if let tabsModel = tabs.tabsModel,
} else if let tabsModel = tabs.viewModel,
let action = tabsModel.tabs[indexPath.row].action {
// Perform the tab action
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: tabsModel, additionalData: getAdditionalDataForNewTabLoad(indexPath: indexPath), delegateObject: delegateObject())
@ -329,7 +365,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
// MARK: - MVMCoreUISwipeNavigationProtocol
public func swipeLeft() {
guard tabs.selectedIndex < (tabs.tabsModel?.tabs.count ?? 0) - 1 else { return }
guard tabs.selectedIndex < (tabs.viewModel?.tabs.count ?? 0) - 1 else { return }
_ = shouldSelectItem(IndexPath(row: tabs.selectedIndex + 1, section: 0), tabs: tabs)
}
@ -341,6 +377,6 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
public func update(percentage: CGFloat) {
guard customInteractor?.interactive == true,
let index = index else { return }
tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage)
// tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage)
}
}

View File

@ -399,7 +399,7 @@ open class NotificationHandler {
try await showNotification(for: json, delegateObject: delegateObject)
} catch {
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: "\(self)") {
MVMCoreUILoggingHandler.addError(toLog: errorObject)
MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject)
}
}
}

View File

@ -36,7 +36,8 @@ open class CoreUIModelMapping: ModelMapping {
ModelRegistry.register(handler: ExternalLink.self, for: ExternalLinkModel.self)
ModelRegistry.register(handler: Link.self, for: LinkModel.self)
ModelRegistry.register(handler: CaretLink.self, for: CaretLinkModel.self)
ModelRegistry.register(handler: ButtonGroup.self, for: ButtonGroupModel.self)
// MARK:- Entry Field
ModelRegistry.register(handler: TextEntryField.self, for: TextEntryFieldModel.self)
ModelRegistry.register(handler: MdnEntryField.self, for: MdnEntryFieldModel.self)
@ -73,6 +74,8 @@ open class CoreUIModelMapping: ModelMapping {
ModelRegistry.register(handler: Video.self, for: VideoModel.self)
ModelRegistry.register(handler: Tilelet.self, for: TileletModel.self)
ModelRegistry.register(handler: Badge.self, for: BadgeModel.self)
ModelRegistry.register(handler: Icon.self, for: IconModel.self)
ModelRegistry.register(handler: Tooltip.self, for: TooltipModel.self)
// MARK:- Horizontal Combination Molecules
ModelRegistry.register(handler: StringAndMoleculeView.self, for: StringAndMoleculeModel.self)

View File

@ -1,24 +0,0 @@
//
// MVMCoreUILoggingHandler.h
// MVMCoreUI
//
// Created by Scott Pfeil on 1/10/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
@import MVMCore.MVMCoreLoggingHandler;
NS_ASSUME_NONNULL_BEGIN
@interface MVMCoreUILoggingHandler : MVMCoreLoggingHandler
// Page State Logging
- (void)defaultLogPageStateForController:(nonnull id <MVMCoreViewControllerProtocol>)controller;
// Action Logging
- (void)defaultLogActionForController:(nullable id <MVMCoreViewControllerProtocol>)controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData;
- (nullable NSDictionary *)defaultGetActionTrackDataDictionaryForController:(nullable id <MVMCoreViewControllerProtocol>)controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,23 +0,0 @@
//
// MVMCoreUILoggingHandler.m
// MVMCoreUI
//
// Created by Scott Pfeil on 1/10/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
#import "MVMCoreUILoggingHandler.h"
@implementation MVMCoreUILoggingHandler
- (void)defaultLogPageStateForController:(nonnull id <MVMCoreViewControllerProtocol>)controller {
}
- (void)defaultLogActionForController:(nullable id <MVMCoreViewControllerProtocol>)controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData {
}
- (nullable NSDictionary *)defaultGetActionTrackDataDictionaryForController:(nullable id <MVMCoreViewControllerProtocol>)controller actionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData {
return nil;
}
@end

View File

@ -0,0 +1,16 @@
//
// MVMCoreUILoggingHandler.swift
// MVMCoreUI
//
// Created by Nandhini Rajendran on 29/09/23.
// Copyright © 2023 Verizon Wireless. All rights reserved.
//
@objc open class MVMCoreUILoggingHandler: MVMCoreLoggingHandler {
// Page State Logging
@objc open func defaultLogPageState(forController controller: MVMCoreViewControllerProtocol) { }
// Action Logging
@objc open func defaultLogAction(forController controller: MVMCoreViewControllerProtocol?, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { }
}

View File

@ -27,10 +27,11 @@
// MARK: Textfield
"textfield_today_string" = "Today";
"textfield_error_message" = "%@.\n The error message.\n %@";
"textView_error_message" = "%@.\n The error message.\n %@";
"textfield_picker_item" = " picker item";
"textfield_regular" = " regular";
"textfield_disabled_state" = "disabled";
"textfield_optional" = "Optional";
// MARK: MDNTextfield
"textfield_contacts_barbutton" = "My Contacts";
@ -62,7 +63,7 @@
// MARK: Switch / Toggle
"mfswitch_buttonlabel" = "Switch Button";
"Toggle_buttonlabel" = "Toggle Button";
"Toggle_buttonlabel" = "Toggle";
"AccOn" = "on";
"AccOff" = "off";
"AccToggleHint" = "double tap to toggle";

View File

@ -23,9 +23,11 @@
// Textfield
"textfield_today_string" = "Hoy";
"textfield_error_message" = "%@.\n El mensaje de error.\n %@";
"textView_error_message" = "%@.\n El mensaje de error.\n %@";
"textfield_picker_item" = " artículo de selector";
"textfield_regular" = " regular";
"textfield_disabled_state" = "inactivo";
"textfield_optional" = "Opcional";
//MDNTextfield
"textfield_contacts_barbutton" = "Mis contactos";
"textfield_phone_format_error_message" = "Formato de número de teléfono inválido.";

View File

@ -23,9 +23,11 @@
// Textfield
"textfield_today_string" = "Hoy";
"textfield_error_message" = "%@.\n El mensaje de error.\n %@";
"textView_error_message" = "%@.\n El mensaje de error.\n %@";
"textfield_picker_item" = " artículo de selector";
"textfield_regular" = " regular";
"textfield_disabled_state" = "inactivo";
"textfield_optional" = "Opcional";
//MDNTextfield
"textfield_contacts_barbutton" = "Mis contactos";
"textfield_phone_format_error_message" = "Formato de número de teléfono inválido.";

View File

@ -9,8 +9,11 @@
#import "MFFonts.h"
#import <CoreText/CoreText.h>
#import "MVMCoreUIUtility.h"
@import MVMCore.MVMCoreLoggingHandler;
#import <MVMCore/MVMCoreLoggingHandlerHelper.h>
@import MVMCore.Swift;
@import MVMCore.MVMCoreErrorConstants;
@import MVMCore.MVMCoreLoadHandler;
@import MVMCore.MVMCoreErrorObject;
NSString * const DSBold = @"VerizonNHGeDS-Bold";
NSString * const DSRegular = @"VerizonNHGeDS-Regular";
@ -108,7 +111,7 @@ NSString * const TXRegular = @"VerizonNHGeTX-Regular";
+ (void)validFont:(UIFont *)font fontName:(NSString *)fontName {
if (font == nil) {
MVMCoreErrorObject *errorObject = [[MVMCoreErrorObject alloc] initWithTitle:@"font can not load" message:[NSString stringWithFormat:@"missing font name is %@", fontName] code:ErrorCodeFontNotFound domain:ErrorDomainNative location:@"MFStyler"];
[MVMCoreLoggingHandler addErrorToLog:errorObject];
[[MVMCoreLoggingHandler sharedLoggingHandler]addErrorToLog:errorObject];
}
}
@end