Merge branch 'release/11_2_0' into 'develop'
release/11_2_0 hotfix merge Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com> Co-authored-by: Pfeil, Scott Robert <scott.pfeil3@verizonwireless.com> Co-authored-by: Sumanth Nadigadda <sumanth.nadigadda@verizon.com> Co-authored-by: Matt Bruce <matt.bruce@verizon.com> See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1051
This commit is contained in:
commit
a8f8ff6424
@ -171,7 +171,6 @@
|
|||||||
608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; };
|
608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; };
|
||||||
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; };
|
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; };
|
||||||
71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; };
|
71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; };
|
||||||
608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; };
|
|
||||||
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
|
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
|
||||||
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; };
|
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; };
|
||||||
8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; };
|
8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; };
|
||||||
@ -300,8 +299,8 @@
|
|||||||
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; };
|
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; };
|
||||||
AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; };
|
AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; };
|
||||||
AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; };
|
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 */; };
|
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 */; };
|
BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; };
|
||||||
BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; };
|
BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; };
|
||||||
BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; };
|
BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; };
|
||||||
@ -574,10 +573,16 @@
|
|||||||
EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; };
|
EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; };
|
||||||
EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; };
|
EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; };
|
||||||
EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.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 */; };
|
EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */; };
|
||||||
EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.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 */; };
|
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 */; };
|
EA985C602970A3F000F2FF2E /* VDS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C5F2970A3F000F2FF2E /* VDS.framework */; };
|
||||||
EA985C642970A40E00F2FF2E /* VDSTypographyTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C632970A40E00F2FF2E /* VDSTypographyTokens.xcframework */; };
|
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 */; };
|
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 */; };
|
EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */; };
|
||||||
EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */; };
|
EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */; };
|
||||||
EAA0CFB3275E831E00D65EB0 /* DisableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.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 */; };
|
EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA7801F290081320057DFDF /* VDSMoleculeViewProtocol.swift */; };
|
||||||
EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */; };
|
EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */; };
|
||||||
EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */; };
|
EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */; };
|
||||||
@ -763,7 +769,6 @@
|
|||||||
608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.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>"; };
|
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>"; };
|
71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = "<group>"; };
|
||||||
608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = "<group>"; };
|
|
||||||
8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; };
|
8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; };
|
||||||
8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = "<group>"; };
|
8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = "<group>"; };
|
||||||
8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = "<group>"; };
|
8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = "<group>"; };
|
||||||
@ -892,8 +897,8 @@
|
|||||||
AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = "<group>"; };
|
||||||
@ -1167,6 +1172,12 @@
|
|||||||
EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
@ -1635,6 +1647,7 @@
|
|||||||
D20492A324329A2800A5EED6 /* MVMCoreUIPagingProtocol.h */,
|
D20492A324329A2800A5EED6 /* MVMCoreUIPagingProtocol.h */,
|
||||||
D29DF2B121E7B76C003B2FB9 /* MFLoadingSpinner.h */,
|
D29DF2B121E7B76C003B2FB9 /* MFLoadingSpinner.h */,
|
||||||
D29DF2B221E7B76D003B2FB9 /* MFLoadingSpinner.m */,
|
D29DF2B221E7B76D003B2FB9 /* MFLoadingSpinner.m */,
|
||||||
|
EAA482CD2B45F2F300978105 /* MFLoadingSpinner+VDS.swift */,
|
||||||
D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */,
|
D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */,
|
||||||
D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */,
|
D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */,
|
||||||
);
|
);
|
||||||
@ -2198,6 +2211,8 @@
|
|||||||
DBC4391A224421A0001AB423 /* CaretLink.swift */,
|
DBC4391A224421A0001AB423 /* CaretLink.swift */,
|
||||||
D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */,
|
D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */,
|
||||||
D2E2A99E23E07F8A000B42E6 /* PillButton.swift */,
|
D2E2A99E23E07F8A000B42E6 /* PillButton.swift */,
|
||||||
|
EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */,
|
||||||
|
EA6E8B942B504A43000139B4 /* ButtonGroup.swift */,
|
||||||
);
|
);
|
||||||
path = Buttons;
|
path = Buttons;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2242,6 +2257,10 @@
|
|||||||
B4CC8FBC29DF34680005D28B /* Badge.swift */,
|
B4CC8FBC29DF34680005D28B /* Badge.swift */,
|
||||||
EA985C3F2970939A00F2FF2E /* TileletModel.swift */,
|
EA985C3F2970939A00F2FF2E /* TileletModel.swift */,
|
||||||
EA985C3D2970938F00F2FF2E /* Tilelet.swift */,
|
EA985C3D2970938F00F2FF2E /* Tilelet.swift */,
|
||||||
|
EA7D81612B2B6E7F00D29F9E /* IconModel.swift */,
|
||||||
|
EA7D815F2B2B6E6800D29F9E /* Icon.swift */,
|
||||||
|
EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */,
|
||||||
|
EA7D81652B2BABD200D29F9E /* Tooltip.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2663,6 +2682,7 @@
|
|||||||
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
|
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
|
||||||
01F2C20527C81F9700DC3D36 /* SubNavSwipeAnimator.swift in Sources */,
|
01F2C20527C81F9700DC3D36 /* SubNavSwipeAnimator.swift in Sources */,
|
||||||
0AE277EC25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift in Sources */,
|
0AE277EC25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift in Sources */,
|
||||||
|
EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */,
|
||||||
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */,
|
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */,
|
||||||
3265B30424BCA749000D154B /* HeadersH1NoButtonsBodyText.swift in Sources */,
|
3265B30424BCA749000D154B /* HeadersH1NoButtonsBodyText.swift in Sources */,
|
||||||
AAA7CD69250641F90045B959 /* HeartModel.swift in Sources */,
|
AAA7CD69250641F90045B959 /* HeartModel.swift in Sources */,
|
||||||
@ -2687,6 +2707,7 @@
|
|||||||
0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */,
|
0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */,
|
||||||
AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */,
|
AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */,
|
||||||
AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */,
|
AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */,
|
||||||
|
EAA482CE2B45F2F300978105 /* MFLoadingSpinner+VDS.swift in Sources */,
|
||||||
D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */,
|
D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */,
|
||||||
D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */,
|
D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */,
|
||||||
0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */,
|
0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */,
|
||||||
@ -2934,12 +2955,15 @@
|
|||||||
011D958524042432000E3791 /* RulesProtocol.swift in Sources */,
|
011D958524042432000E3791 /* RulesProtocol.swift in Sources */,
|
||||||
4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */,
|
4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */,
|
||||||
D23118B325124E18001C8440 /* NotificationMoleculeView.swift in Sources */,
|
D23118B325124E18001C8440 /* NotificationMoleculeView.swift in Sources */,
|
||||||
|
EA7D81662B2BABD200D29F9E /* Tooltip.swift in Sources */,
|
||||||
AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */,
|
AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */,
|
||||||
|
EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */,
|
||||||
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
|
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
|
||||||
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
|
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
|
||||||
D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */,
|
D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */,
|
||||||
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */,
|
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */,
|
||||||
27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */,
|
27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */,
|
||||||
|
EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */,
|
||||||
D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */,
|
D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */,
|
||||||
526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */,
|
526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */,
|
||||||
B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */,
|
B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */,
|
||||||
@ -2961,6 +2985,7 @@
|
|||||||
C7F8012123E8303200396FBD /* ListRVWheel.swift in Sources */,
|
C7F8012123E8303200396FBD /* ListRVWheel.swift in Sources */,
|
||||||
BB2C968F24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift in Sources */,
|
BB2C968F24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift in Sources */,
|
||||||
D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */,
|
D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */,
|
||||||
|
EA7D81622B2B6E7F00D29F9E /* IconModel.swift in Sources */,
|
||||||
279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */,
|
279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */,
|
||||||
BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */,
|
BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */,
|
||||||
8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */,
|
8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */,
|
||||||
@ -3132,6 +3157,7 @@
|
|||||||
D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */,
|
D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */,
|
||||||
BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */,
|
BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */,
|
||||||
C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */,
|
C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */,
|
||||||
|
EA7D81602B2B6E6800D29F9E /* Icon.swift in Sources */,
|
||||||
D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */,
|
D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */,
|
||||||
D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */,
|
D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */,
|
||||||
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */,
|
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */,
|
||||||
|
|||||||
111
MVMCoreUI/Atomic/Atoms/Buttons/ButtonGroup.swift
Normal file
111
MVMCoreUI/Atomic/Atoms/Buttons/ButtonGroup.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
82
MVMCoreUI/Atomic/Atoms/Buttons/ButtonGroupModel.swift
Normal file
82
MVMCoreUI/Atomic/Atoms/Buttons/ButtonGroupModel.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,152 +6,61 @@
|
|||||||
// Created by Christiano, Kevin on 3/18/19.
|
// Created by Christiano, Kevin on 3/18/19.
|
||||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||||
//
|
//
|
||||||
|
import VDS
|
||||||
|
|
||||||
|
open class CaretLink: VDS.TextLinkCaret, VDSMoleculeViewProtocol {
|
||||||
|
|
||||||
open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
|
//--------------------------------------------------
|
||||||
//------------------------------------------------------
|
// MARK: - Public Properties
|
||||||
// MARK: - Constants
|
//--------------------------------------------------
|
||||||
//------------------------------------------------------
|
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: - Public Functions
|
||||||
|
//--------------------------------------------------
|
||||||
//------------------------------------------------------
|
open func viewModelDidUpdate() {
|
||||||
// MARK: - Properties
|
isEnabled = viewModel.enabled
|
||||||
//------------------------------------------------------
|
iconPosition = viewModel.iconPosition
|
||||||
|
text = viewModel.title
|
||||||
@objc public var rightView: UIView?
|
surface = viewModel.surface
|
||||||
@objc public var rightViewHeight: NSNumber?
|
onClick = { [weak self] control in
|
||||||
@objc public var rightViewWidth: NSNumber?
|
guard let self else { return }
|
||||||
|
MVMCoreUIActionHandler.performActionUnstructured(with: self.viewModel.action,
|
||||||
@objc public var enabledColor: UIColor = .mvmBlack {
|
sourceModel: self.viewModel,
|
||||||
didSet { changeCaretColor() }
|
additionalData: self.additionalData,
|
||||||
}
|
delegateObject: self.delegateObject)
|
||||||
|
|
||||||
@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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createCaretView() -> CaretView {
|
//--------------------------------------------------
|
||||||
let caret = CaretView()
|
// MARK: - Overrides
|
||||||
caret.lineWidth = 1.5
|
//--------------------------------------------------
|
||||||
return caret
|
open override func updateAccessibility() {
|
||||||
}
|
super.updateAccessibility()
|
||||||
|
|
||||||
private func addCaretImageView() {
|
guard let viewModel = viewModel else { return }
|
||||||
|
if let accessibilityText = viewModel.accessibilityText {
|
||||||
rightView?.removeFromSuperview()
|
self.accessibilityLabel = accessibilityText
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledColor = (model.inverted ? model.enabledColor_inverted : model.enabledColor).uiColor
|
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
|
||||||
disabledColor = (model.inverted ? model.disabledColor_inverted : model.disabledColor).uiColor
|
self.accessibilityIdentifier = accessibilityIdentifier
|
||||||
|
}
|
||||||
isEnabled = model.enabled
|
|
||||||
set(with: model.action, delegateObject: delegateObject, additionalData: additionalData)
|
|
||||||
setTitle(model.title, for: .normal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func reset() {
|
//--------------------------------------------------
|
||||||
super.reset()
|
// MARK: - MVMCoreViewProtocol
|
||||||
rightView?.removeFromSuperview()
|
//--------------------------------------------------
|
||||||
}
|
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 }
|
public func needsToBeConstrained() -> Bool { true }
|
||||||
|
|
||||||
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
|
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
|
||||||
|
|
||||||
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 10.5 }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import VDS
|
||||||
|
|
||||||
public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableModelProtocol {
|
public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableModelProtocol {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -20,13 +20,11 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
|
|||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var accessibilityIdentifier: String?
|
public var accessibilityIdentifier: String?
|
||||||
public var title: String
|
public var title: String
|
||||||
|
public var accessibilityText: String?
|
||||||
public var action: ActionModelProtocol
|
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 enabled = true
|
||||||
public var inverted = false
|
public var inverted = false
|
||||||
|
public var iconPosition: TextLinkCaret.IconPosition = .right
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Initializer
|
// MARK: - Initializer
|
||||||
@ -45,12 +43,10 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
|
|||||||
case id
|
case id
|
||||||
case backgroundColor
|
case backgroundColor
|
||||||
case accessibilityIdentifier
|
case accessibilityIdentifier
|
||||||
|
case accessibilityText
|
||||||
|
case iconPosition
|
||||||
case title
|
case title
|
||||||
case action
|
case action
|
||||||
case enabledColor_inverted
|
|
||||||
case disabledColor_inverted
|
|
||||||
case enabledColor
|
|
||||||
case disabledColor
|
|
||||||
case enabled
|
case enabled
|
||||||
case inverted
|
case inverted
|
||||||
case moleculeName
|
case moleculeName
|
||||||
@ -66,22 +62,11 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
|
|||||||
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||||
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
|
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
|
||||||
|
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
|
||||||
title = try typeContainer.decode(String.self, forKey: .title)
|
title = try typeContainer.decode(String.self, forKey: .title)
|
||||||
|
|
||||||
if let enabledColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor_inverted) {
|
if let iconPosition = try typeContainer.decodeIfPresent(TextLinkCaret.IconPosition.self, forKey: .iconPosition) {
|
||||||
self.enabledColor_inverted = enabledColor_inverted
|
self.iconPosition = iconPosition
|
||||||
}
|
|
||||||
|
|
||||||
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 enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
|
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)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
try container.encode(id, forKey: .id)
|
try container.encode(id, forKey: .id)
|
||||||
try container.encode(moleculeName, forKey: .moleculeName)
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
|
try container.encode(iconPosition, forKey: .iconPosition)
|
||||||
try container.encode(title, forKey: .title)
|
try container.encode(title, forKey: .title)
|
||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||||
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
||||||
|
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
|
||||||
try container.encodeModel(action, forKey: .action)
|
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(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)
|
try container.encode(inverted, forKey: .inverted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CaretLinkModel {
|
||||||
|
public var surface: Surface { inverted ? .dark : .light }
|
||||||
|
}
|
||||||
|
|||||||
@ -60,7 +60,6 @@ open class Link: VDS.TextLink, VDSMoleculeViewProtocol {
|
|||||||
|
|
||||||
open func updateView(_ size: CGFloat) { }
|
open func updateView(_ size: CGFloat) { }
|
||||||
|
|
||||||
open func setupView() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - MVMCoreUIViewConstrainingProtocol
|
// MARK: - MVMCoreUIViewConstrainingProtocol
|
||||||
|
|||||||
@ -92,6 +92,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
|
|||||||
try container.encode(moleculeName, forKey: .moleculeName)
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||||
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
||||||
|
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
|
||||||
try container.encodeModel(action, forKey: .action)
|
try container.encodeModel(action, forKey: .action)
|
||||||
try container.encode(inverted, forKey: .inverted)
|
try container.encode(inverted, forKey: .inverted)
|
||||||
try container.encode(enabled, forKey: .enabled)
|
try container.encode(enabled, forKey: .enabled)
|
||||||
|
|||||||
@ -187,7 +187,7 @@ import UIKit
|
|||||||
let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing))
|
let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing))
|
||||||
entryFieldContainer.addGestureRecognizer(tap)
|
entryFieldContainer.addGestureRecognizer(tap)
|
||||||
|
|
||||||
accessibilityElements = [titleLabel, textField, feedbackLabel]
|
accessibilityElements = [textField, feedbackLabel]
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc open override func updateView(_ size: CGFloat) {
|
@objc open override func updateView(_ size: CGFloat) {
|
||||||
|
|||||||
60
MVMCoreUI/Atomic/Atoms/Views/Icon.swift
Normal file
60
MVMCoreUI/Atomic/Atoms/Views/Icon.swift
Normal 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 }
|
||||||
|
}
|
||||||
38
MVMCoreUI/Atomic/Atoms/Views/IconModel.swift
Normal file
38
MVMCoreUI/Atomic/Atoms/Views/IconModel.swift
Normal 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?
|
||||||
|
}
|
||||||
@ -7,207 +7,33 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import VDS
|
||||||
|
|
||||||
|
open class LoadingSpinner: VDS.Loader, VDSMoleculeViewProtocol {
|
||||||
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
|
// MARK: - Public Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
open var viewModel: LoadingSpinnerModel!
|
||||||
public var heightConstraint: NSLayoutConstraint?
|
open var delegateObject: MVMCoreUIDelegateObject?
|
||||||
public var widthConstraint: NSLayoutConstraint?
|
open var additionalData: [AnyHashable : Any]?
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Lifecycle
|
// MARK: - Public Functions
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
open func viewModelDidUpdate() {
|
||||||
override open var layer: CAShapeLayer {
|
size = Int(viewModel.diameter)
|
||||||
get { return super.layer as! CAShapeLayer }
|
surface = viewModel.surface
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
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
|
// MARK: - MVMCoreViewProtocol
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
|
||||||
override open func didMoveToWindow() {
|
guard let model = model as? LoadingSpinnerModel else { return 0 }
|
||||||
animate()
|
return model.diameter
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Pose {
|
open func updateView(_ size: CGFloat) { }
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import VDS
|
||||||
|
|
||||||
open class LoadingSpinnerModel: MoleculeModelProtocol {
|
open class LoadingSpinnerModel: MoleculeModelProtocol {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -17,8 +18,7 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
|
|||||||
public var id: String = UUID().uuidString
|
public var id: String = UUID().uuidString
|
||||||
|
|
||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var strokeColor = Color(uiColor: .mvmBlack)
|
public var inverted: Bool = false
|
||||||
public var lineWidth: CGFloat = 4
|
|
||||||
public var diameter: CGFloat = 40
|
public var diameter: CGFloat = 40
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -28,10 +28,9 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
|
|||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case id
|
case id
|
||||||
case moleculeName
|
case moleculeName
|
||||||
case backgroundColor
|
|
||||||
case strokeColor
|
case strokeColor
|
||||||
case lineWidth
|
|
||||||
case diameter
|
case diameter
|
||||||
|
case inverted
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -48,18 +47,17 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
|
|||||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
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) {
|
if let diameter = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .diameter) {
|
||||||
self.diameter = diameter
|
self.diameter = diameter
|
||||||
}
|
}
|
||||||
|
|
||||||
if let strokeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .strokeColor) {
|
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
|
||||||
self.strokeColor = strokeColor
|
self.inverted = inverted
|
||||||
}
|
}
|
||||||
|
|
||||||
if let lineWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .lineWidth) {
|
if let strokeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .strokeColor) {
|
||||||
self.lineWidth = lineWidth
|
self.inverted = !strokeColor.uiColor.isDark()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +65,11 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
|
|||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
try container.encode(id, forKey: .id)
|
try container.encode(id, forKey: .id)
|
||||||
try container.encode(moleculeName, forKey: .moleculeName)
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
|
||||||
try container.encodeIfPresent(diameter, forKey: .diameter)
|
try container.encodeIfPresent(diameter, forKey: .diameter)
|
||||||
try container.encode(strokeColor, forKey: .strokeColor)
|
try container.encodeIfPresent(inverted, forKey: .inverted)
|
||||||
try container.encode(lineWidth, forKey: .lineWidth)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension LoadingSpinnerModel {
|
||||||
|
public var surface: Surface { inverted ? .dark : .light }
|
||||||
|
}
|
||||||
|
|||||||
66
MVMCoreUI/Atomic/Atoms/Views/Tooltip.swift
Normal file
66
MVMCoreUI/Atomic/Atoms/Views/Tooltip.swift
Normal 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 }
|
||||||
|
}
|
||||||
98
MVMCoreUI/Atomic/Atoms/Views/TooltipModel.swift
Normal file
98
MVMCoreUI/Atomic/Atoms/Views/TooltipModel.swift
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -8,19 +8,56 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import VDS
|
import VDS
|
||||||
|
import VDSColorTokens
|
||||||
|
|
||||||
extension Surface: Codable {}
|
//--------------------------------------------------
|
||||||
extension Badge.FillColor: Codable {}
|
// MARK: - Codable Extensions
|
||||||
extension Icon.Name: Codable {}
|
//--------------------------------------------------
|
||||||
extension Icon.Size: Codable {}
|
|
||||||
extension TileContainer.BackgroundColor: Codable {}
|
extension VDS.Surface: Codable {}
|
||||||
extension TileContainer.Padding: Codable {}
|
extension VDS.Badge.FillColor: Codable {}
|
||||||
extension TileContainer.AspectRatio: Codable {}
|
extension VDS.ButtonGroup.Alignment: Codable {}
|
||||||
extension TextLink.Size: 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.Tooltip.FillColor: Codable {}
|
||||||
|
extension VDS.Tooltip.Size: Codable {}
|
||||||
extension VDS.Line.Style: Codable {}
|
extension VDS.Line.Style: Codable {}
|
||||||
extension VDS.Line.Orientation: Codable {}
|
extension VDS.Line.Orientation: Codable {}
|
||||||
extension Use: Codable {}
|
extension VDS.Use: Codable {}
|
||||||
|
|
||||||
extension VDS.Button.Size: RawRepresentableCodable {
|
extension VDS.Button.Size: RawRepresentableCodable {
|
||||||
public static var mapping: [String : VDS.Button.Size] { ["standard": .large, "tiny": .small] }
|
public static var mapping: [String : VDS.Button.Size] { ["standard": .large, "tiny": .small] }
|
||||||
public static var defaultValue: VDS.Button.Size? { nil }
|
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>
|
||||||
|
}
|
||||||
|
|||||||
@ -29,8 +29,8 @@
|
|||||||
|
|
||||||
if let _ = molecule as? ButtonModel {
|
if let _ = molecule as? ButtonModel {
|
||||||
horizontalAlignment = .fill
|
horizontalAlignment = .fill
|
||||||
} else if let model = molecule as? TwoButtonViewModel,
|
} else if let model = molecule as? TwoButtonViewModel {
|
||||||
model.primaryButton == nil || model.secondaryButton == nil {
|
model.fillContainer = true
|
||||||
horizontalAlignment = .fill
|
horizontalAlignment = .fill
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,379 +8,67 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import VDSColorTokens
|
import VDSColorTokens
|
||||||
|
import VDS
|
||||||
|
|
||||||
@objc public protocol TabsDelegate {
|
@objc public protocol TabsDelegate {
|
||||||
func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool
|
func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool
|
||||||
func didSelectItem(_ indexPath: IndexPath, tabs: Tabs)
|
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 }
|
// MARK: - Public Properties
|
||||||
}
|
//--------------------------------------------------
|
||||||
|
open var viewModel: TabsModel!
|
||||||
var delegateObject: MVMCoreUIDelegateObject?
|
open var delegateObject: MVMCoreUIDelegateObject?
|
||||||
var additionalData: [AnyHashable: Any]?
|
open 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()
|
|
||||||
|
|
||||||
//delegate
|
//delegate
|
||||||
weak public var delegate: TabsDelegate?
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
//control var
|
onTabShouldSelect = { [weak self] index in
|
||||||
public var selectedIndex: Int = 0
|
guard let self else { return true }
|
||||||
public var paddingBeforeFirstTab: Bool = true
|
return delegate.shouldSelectItem(.init(row: index, section: 0), tabs: self)
|
||||||
|
}
|
||||||
//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
|
|
||||||
|
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
// MARK: - Layout Views
|
// MARK: - Layout Views
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
|
|
||||||
open override func reset() {
|
open func updateView(_ size: CGFloat) {}
|
||||||
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
|
|
||||||
|
|
||||||
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
|
// MARK: - Control Methods
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
|
|
||||||
public func selectIndex(_ index: Int, animated: Bool) {
|
public func selectIndex(_ index: Int, animated: Bool) {
|
||||||
guard let _ = collectionView, tabsModel?.tabs.count ?? 0 > 0 else {
|
self.selectedIndex = index
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reloadData() {
|
public func reloadData() { setNeedsUpdate() }
|
||||||
collectionView?.reloadData()
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
// MARK: - Molecule Setup
|
// MARK: - Molecule Setup
|
||||||
//-------------------------------------------------
|
//-------------------------------------------------
|
||||||
|
|
||||||
override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
open func viewModelDidUpdate() {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
orientation = viewModel.orientation
|
||||||
self.delegateObject = delegateObject
|
indicatorPosition = viewModel.indicatorPosition
|
||||||
self.additionalData = additionalData
|
overflow = viewModel.overflow
|
||||||
selectedIndex = tabsModel?.selectedIndex ?? 0
|
size = viewModel.size
|
||||||
selectionLine.backgroundColor = tabsModel?.selectedBarColor.uiColor
|
selectedIndex = viewModel.selectedIndex
|
||||||
let lineModel = bottomLine.viewModel ?? LineModel(type: .secondary)
|
surface = viewModel.style ?? .light
|
||||||
lineModel.inverted = tabsModel?.style == .dark
|
fillContainer = viewModel.fillContainer
|
||||||
bottomLine.set(with: lineModel, delegateObject, additionalData)
|
tabModels = viewModel.tabs.compactMap { TabModel(text: $0.label.text) }
|
||||||
reloadData()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -8,66 +8,22 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import VDSColorTokens
|
import VDSColorTokens
|
||||||
|
import VDS
|
||||||
open class TabsModel: MoleculeModelProtocol {
|
open class TabsModel: MoleculeModelProtocol {
|
||||||
|
|
||||||
public static var identifier: String = "tabs"
|
public static var identifier: String = "tabs"
|
||||||
public var id: String = UUID().uuidString
|
public var id: String = UUID().uuidString
|
||||||
|
|
||||||
open var tabs: [TabItemModel]
|
open var tabs: [TabItemModel]
|
||||||
|
|
||||||
open var style: NavigationItemStyle?
|
open var style: Surface?
|
||||||
|
|
||||||
private var _backgroundColor: Color?
|
open var orientation: Tabs.Orientation = .horizontal
|
||||||
open var backgroundColor: Color? {
|
open var indicatorPosition: Tabs.IndicatorPosition = .bottom
|
||||||
get {
|
open var overflow: Tabs.Overflow = .scroll
|
||||||
if let backgroundColor = _backgroundColor { return backgroundColor }
|
open var fillContainer: Bool = false
|
||||||
if let style = style,
|
open var size: Tabs.Size = .medium
|
||||||
style == .dark { return Color(uiColor: VDSColor.backgroundPrimaryDark) }
|
public var backgroundColor: Color?
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be capped to 0...(tabs.count - 1)
|
// Must be capped to 0...(tabs.count - 1)
|
||||||
open var selectedIndex: Int = 0
|
open var selectedIndex: Int = 0
|
||||||
@ -77,10 +33,12 @@ open class TabsModel: MoleculeModelProtocol {
|
|||||||
case moleculeName
|
case moleculeName
|
||||||
case tabs
|
case tabs
|
||||||
case backgroundColor
|
case backgroundColor
|
||||||
case selectedColor
|
|
||||||
case unselectedColor
|
|
||||||
case selectedBarColor
|
|
||||||
case selectedIndex
|
case selectedIndex
|
||||||
|
case orientation
|
||||||
|
case indicatorPosition
|
||||||
|
case fillContainer
|
||||||
|
case overflow
|
||||||
|
case size
|
||||||
case style
|
case style
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,14 +50,31 @@ open class TabsModel: MoleculeModelProtocol {
|
|||||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
||||||
tabs = try typeContainer.decode([TabItemModel].self, forKey: .tabs)
|
tabs = try typeContainer.decode([TabItemModel].self, forKey: .tabs)
|
||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
style = try typeContainer.decodeIfPresent(Surface.self, forKey: .style)
|
||||||
_selectedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor)
|
|
||||||
_unselectedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .unselectedColor)
|
if let fillContainer = try typeContainer.decodeIfPresent(Bool.self, forKey: .fillContainer) {
|
||||||
_selectedBarColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedBarColor)
|
self.fillContainer = fillContainer
|
||||||
style = try typeContainer.decodeIfPresent(NavigationItemStyle.self, forKey: .style)
|
}
|
||||||
|
|
||||||
if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) {
|
if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) {
|
||||||
selectedIndex = index
|
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 {
|
open func encode(to encoder: Encoder) throws {
|
||||||
@ -107,12 +82,14 @@ open class TabsModel: MoleculeModelProtocol {
|
|||||||
try container.encode(id, forKey: .id)
|
try container.encode(id, forKey: .id)
|
||||||
try container.encode(moleculeName, forKey: .moleculeName)
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
try container.encode(tabs, forKey: .tabs)
|
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(selectedIndex, forKey: .selectedIndex)
|
||||||
|
try container.encode(fillContainer, forKey: .fillContainer)
|
||||||
try container.encodeIfPresent(style, forKey: .style)
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,104 +9,31 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import VDS
|
import VDS
|
||||||
|
|
||||||
@objcMembers open class TwoButtonView: View, MVMCoreUIViewConstrainingProtocol {
|
@objcMembers open class TwoButtonView: VDS.View, VDSMoleculeViewProtocol {
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
open var viewModel: TwoButtonViewModel!
|
||||||
|
open var delegateObject: MVMCoreUIDelegateObject?
|
||||||
|
open var additionalData: [AnyHashable : Any]?
|
||||||
|
|
||||||
open var primaryButton: PillButton = PillButton()
|
open var primaryButton = PillButton()
|
||||||
open var secondaryButton: PillButton = PillButton()
|
open var secondaryButton = PillButton()
|
||||||
private var stack = UIStackView()
|
private var buttonGroup = VDS.ButtonGroup()
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Constraints
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
private var equalWidthConstraint: NSLayoutConstraint?
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
public func setDefaultAppearance() {
|
open override func setup() {
|
||||||
primaryButton.use = .primary
|
super.setup()
|
||||||
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
|
|
||||||
isAccessibilityElement = false
|
isAccessibilityElement = false
|
||||||
addSubview(stack)
|
addSubview(buttonGroup)
|
||||||
stack.addArrangedSubview(secondaryButton)
|
buttonGroup.pinToSuperView()
|
||||||
stack.addArrangedSubview(primaryButton)
|
buttonGroup.alignment = .center
|
||||||
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
|
buttonGroup.rowQuantityPhone = 2
|
||||||
stack.axis = .horizontal
|
buttonGroup.rowQuantityTablet = 2
|
||||||
stack.spacing = Padding.Component.gutterForApplicationWidth
|
|
||||||
equalWidthConstraint = secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1)
|
|
||||||
equalWidthConstraint?.isActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -115,12 +42,10 @@ import VDS
|
|||||||
|
|
||||||
open override func reset() {
|
open override func reset() {
|
||||||
super.reset()
|
super.reset()
|
||||||
|
buttonGroup.reset()
|
||||||
setDefaultAppearance()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
guard let model = model as? TwoButtonViewModel,
|
||||||
let buttonModel = model.primaryButton ?? model.secondaryButton
|
let buttonModel = model.primaryButton ?? model.secondaryButton
|
||||||
else { return 0 }
|
else { return 0 }
|
||||||
@ -128,25 +53,25 @@ import VDS
|
|||||||
return PillButton.estimatedHeight(with: buttonModel, delegateObject)
|
return PillButton.estimatedHeight(with: buttonModel, delegateObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
public func viewModelDidUpdate() {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
var buttons = [PillButton]()
|
||||||
|
if let secondaryModel = viewModel.secondaryButton {
|
||||||
guard let model = model as? TwoButtonViewModel else { return }
|
|
||||||
|
|
||||||
if let secondaryModel = model.secondaryButton {
|
|
||||||
showSecondaryButton()
|
|
||||||
secondaryButton.set(with: secondaryModel, delegateObject, additionalData)
|
secondaryButton.set(with: secondaryModel, delegateObject, additionalData)
|
||||||
} else {
|
buttons.append(secondaryButton)
|
||||||
hideSecondaryButton()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let primaryModel = model.primaryButton {
|
if let primaryModel = viewModel.primaryButton {
|
||||||
showPrimaryButton()
|
|
||||||
primaryButton.set(with: primaryModel, delegateObject, additionalData)
|
primaryButton.set(with: primaryModel, delegateObject, additionalData)
|
||||||
} else {
|
buttons.append(primaryButton)
|
||||||
hidePrimaryButton()
|
}
|
||||||
|
|
||||||
|
buttonGroup.childWidth = viewModel.fillContainer ? .percentage(100) : nil
|
||||||
|
|
||||||
|
if buttons.count != buttonGroup.buttons.count {
|
||||||
|
buttonGroup.buttons = buttons
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - MVMCoreUIViewConstrainingProtocol
|
// MARK: - MVMCoreUIViewConstrainingProtocol
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -154,4 +79,12 @@ import VDS
|
|||||||
open func horizontalAlignment() -> UIStackView.Alignment {
|
open func horizontalAlignment() -> UIStackView.Alignment {
|
||||||
return .center
|
return .center
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - MVMCoreViewProtocol
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
public func updateView(_ size: CGFloat) {
|
||||||
|
setNeedsUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,10 +19,9 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
|
|||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var primaryButton: ButtonModel?
|
public var primaryButton: ButtonModel?
|
||||||
public var secondaryButton: ButtonModel?
|
public var secondaryButton: ButtonModel?
|
||||||
|
public var fillContainer: Bool = false
|
||||||
|
|
||||||
public var children: [MoleculeModelProtocol] {
|
public var children: [MoleculeModelProtocol] { [primaryButton, secondaryButton].compactMap { $0 } }
|
||||||
return [primaryButton, secondaryButton].compactMap { $0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Keys
|
// MARK: - Keys
|
||||||
|
|||||||
@ -7,77 +7,76 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import VDS
|
||||||
|
|
||||||
|
@objcMembers open class TwoLinkView: VDS.View, VDSMoleculeViewProtocol {
|
||||||
|
|
||||||
@objcMembers open class TwoLinkView: View, MVMCoreUIViewConstrainingProtocol {
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
open var viewModel: TwoLinkViewModel!
|
||||||
|
open var delegateObject: MVMCoreUIDelegateObject?
|
||||||
|
open var additionalData: [AnyHashable : Any]?
|
||||||
|
|
||||||
open var leftLink = Link()
|
open var leftLink = Link()
|
||||||
open var rightLink = 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
|
// MARK: - MVMCoreViewProtocol
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
open override func reset() {
|
||||||
open override func updateView(_ size: CGFloat) {
|
super.reset()
|
||||||
super.updateView(size)
|
buttonGroup.reset()
|
||||||
stack.updateView(size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func setupView() {
|
open func updateView(_ size: CGFloat) { }
|
||||||
super.setupView()
|
|
||||||
|
|
||||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(stack)
|
|
||||||
stack.addArrangedSubview(leftLink)
|
|
||||||
stack.addArrangedSubview(rightLink)
|
|
||||||
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
|
|
||||||
stack.axis = .horizontal
|
|
||||||
stack.spacing = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Stack Manipulation
|
// MARK: - Stack Manipulation
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
public func showRightLink() {
|
public func showRightLink() {
|
||||||
if !stack.arrangedSubviews.contains(rightLink) {
|
if !buttons.contains(rightLink) {
|
||||||
stack.addArrangedSubview(rightLink)
|
buttons.insert(rightLink, at: buttons.count)
|
||||||
rightLink.isHidden = false
|
|
||||||
}
|
}
|
||||||
|
buttonGroup.buttons = buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
public func showLeftLink() {
|
public func showLeftLink() {
|
||||||
if !stack.arrangedSubviews.contains(leftLink) {
|
if !buttons.contains(leftLink) {
|
||||||
stack.insertArrangedSubview(leftLink, at: 0)
|
buttons.insert(leftLink, at: 0)
|
||||||
leftLink.isHidden = false
|
|
||||||
}
|
}
|
||||||
|
buttonGroup.buttons = buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hideRightLink() {
|
public func hideRightLink() {
|
||||||
if rightLink.superview != nil {
|
if let index = buttons.firstIndex(of: rightLink) {
|
||||||
stack.removeArrangedSubview(rightLink)
|
buttons.remove(at: index)
|
||||||
rightLink.isHidden = true
|
|
||||||
}
|
}
|
||||||
|
buttonGroup.buttons = buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hideLeftLink() {
|
public func hideLeftLink() {
|
||||||
if leftLink.superview != nil {
|
if let index = buttons.firstIndex(of: leftLink) {
|
||||||
stack.removeArrangedSubview(leftLink)
|
buttons.remove(at: index)
|
||||||
leftLink.isHidden = true
|
|
||||||
}
|
}
|
||||||
}
|
buttonGroup.buttons = buttons
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - MoleculeViewProtocol
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
open override func reset() {
|
|
||||||
super.reset()
|
|
||||||
stack.reset()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -92,27 +91,28 @@ import Foundation
|
|||||||
// MARK: - MoleculeViewProtocol
|
// MARK: - MoleculeViewProtocol
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
|
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
|
||||||
return 16
|
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]?) {
|
public func viewModelDidUpdate() {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
buttons.removeAll()
|
||||||
|
|
||||||
guard let model = model as? TwoLinkViewModel else { return }
|
if let model = viewModel.leftLink {
|
||||||
|
|
||||||
if let model = model.leftLink {
|
|
||||||
showLeftLink()
|
|
||||||
leftLink.set(with: model, delegateObject, additionalData)
|
leftLink.set(with: model, delegateObject, additionalData)
|
||||||
} else {
|
buttons.append(leftLink)
|
||||||
hideLeftLink()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let model = model.rightLink {
|
if let model = viewModel.rightLink {
|
||||||
showRightLink()
|
|
||||||
rightLink.set(with: model, delegateObject, additionalData)
|
rightLink.set(with: model, delegateObject, additionalData)
|
||||||
} else {
|
buttons.append(rightLink)
|
||||||
hideRightLink()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buttonGroup.buttons = buttons
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class TwoLinkViewModel: MoleculeModelProtocol {
|
public class TwoLinkViewModel: ParentMoleculeModelProtocol {
|
||||||
public static var identifier: String = "twoLinkView"
|
public static var identifier: String = "twoLinkView"
|
||||||
public var id: String = UUID().uuidString
|
public var id: String = UUID().uuidString
|
||||||
|
|
||||||
@ -16,6 +16,8 @@ public class TwoLinkViewModel: MoleculeModelProtocol {
|
|||||||
public var rightLink: LinkModel?
|
public var rightLink: LinkModel?
|
||||||
public var leftLink: LinkModel?
|
public var leftLink: LinkModel?
|
||||||
|
|
||||||
|
public var children: [MoleculeModelProtocol] { [rightLink, leftLink].compactMap{ $0 } }
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case id
|
case id
|
||||||
case moleculeName
|
case moleculeName
|
||||||
|
|||||||
@ -81,7 +81,9 @@ extension TabsListItemModel: PageBehaviorProtocolRequirer {
|
|||||||
extension TabsListItemModel: AddMolecules {
|
extension TabsListItemModel: AddMolecules {
|
||||||
public func moleculesToAdd() -> AddMolecules.AddParameters? {
|
public func moleculesToAdd() -> AddMolecules.AddParameters? {
|
||||||
guard addedMolecules == nil else { return nil }
|
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
|
self.addedMolecules = addedMolecules
|
||||||
return (addedMolecules, .below)
|
return (addedMolecules, .below)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,10 +19,8 @@ import UIKit
|
|||||||
// MARK: - MFViewProtocol
|
// MARK: - MFViewProtocol
|
||||||
override public func setupView() {
|
override public func setupView() {
|
||||||
super.setupView()
|
super.setupView()
|
||||||
tabs.paddingBeforeFirstTab = false
|
|
||||||
tabs.translatesAutoresizingMaskIntoConstraints = false
|
tabs.translatesAutoresizingMaskIntoConstraints = false
|
||||||
tabs.delegate = self
|
tabs.delegate = self
|
||||||
tabs.bottomLine.setStyle(.none)
|
|
||||||
contentView.addSubview(tabs)
|
contentView.addSubview(tabs)
|
||||||
|
|
||||||
NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values))
|
NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values))
|
||||||
@ -47,7 +45,6 @@ import UIKit
|
|||||||
public override func reset() {
|
public override func reset() {
|
||||||
super.reset()
|
super.reset()
|
||||||
tabs.reset()
|
tabs.reset()
|
||||||
tabs.paddingBeforeFirstTab = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 46 }
|
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 46 }
|
||||||
|
|||||||
@ -237,6 +237,10 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController, Rotor
|
|||||||
// if footer already exists, use the same y location to avoid strange moving animation
|
// if footer already exists, use the same y location to avoid strange moving animation
|
||||||
let y = tableView.tableFooterView?.frame.minY ?? 0.0
|
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.
|
// This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout.
|
||||||
MVMCoreUIUtility.sizeView(toFit: footerView)
|
MVMCoreUIUtility.sizeView(toFit: footerView)
|
||||||
let tableFooterView = UIView(frame: CGRect(x: 0, y: y, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height))
|
let tableFooterView = UIView(frame: CGRect(x: 0, y: y, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height))
|
||||||
|
|||||||
@ -84,10 +84,17 @@ public final class Color: Codable {
|
|||||||
required public init(from decoder: Decoder) throws {
|
required public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
let colorString = try container.decode(String.self)
|
let colorString = try container.decode(String.self)
|
||||||
let components = try Color.getColorComponents(for: colorString)
|
|
||||||
self.uiColor = components.color
|
if let vdsColor = UIColor.VDSColor(rawValue: colorString) {
|
||||||
hex = components.hex
|
self.uiColor = vdsColor.uiColor
|
||||||
name = components.name ?? ""
|
hex = uiColor.hexString ?? ""
|
||||||
|
} else {
|
||||||
|
let components = try Color.getColorComponents(for: colorString)
|
||||||
|
self.uiColor = components.color
|
||||||
|
hex = components.hex
|
||||||
|
name = components.name ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
determineRGBA()
|
determineRGBA()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
41
MVMCoreUI/Legacy/Views/MFLoadingSpinner+VDS.swift
Normal file
41
MVMCoreUI/Legacy/Views/MFLoadingSpinner+VDS.swift
Normal 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?
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,143 +7,73 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MFLoadingSpinner.h"
|
#import "MFLoadingSpinner.h"
|
||||||
#import "UIColor+MFConvenience.h"
|
#import <VDS/VDS.h>
|
||||||
#import "NSLayoutConstraint+MFConvenience.h"
|
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||||
|
|
||||||
@interface MFLoadingSpinner ()
|
@interface MFLoadingSpinner ()
|
||||||
|
@property (strong, nonatomic) VDSLoader *loader;
|
||||||
@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;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MFLoadingSpinner
|
@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;
|
-(void) setup {
|
||||||
const float lineWidth = 3.0;
|
if (self.loader) { return; }
|
||||||
const float slowSpeed = 0.5;
|
self.loader = [[VDSLoader alloc] init];
|
||||||
const float fastSpeed = 2.0;
|
[self addSubview: self.loader];
|
||||||
const float startSpeed = 1.0;
|
[NSLayoutConstraint pinViewToSuperview:self.loader useMargins:false];
|
||||||
const float fastDistance = .45;
|
|
||||||
const float slowDistance = 0.1;
|
|
||||||
|
|
||||||
|
|
||||||
-(void)finalize {
|
|
||||||
[self.myDisplay invalidate];
|
|
||||||
self.myDisplay = nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)setUpCircle {
|
-(void)setUpCircle {
|
||||||
[self setUpCircle:[UIColor blackColor]];
|
[self setSurface: UIColor.blackColor];
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)setUpCircle:(UIColor *)strokeColor {
|
-(void)setUpCircle:(nullable UIColor *)strokeColor {
|
||||||
if(self.myCircle)
|
[self setSurface: strokeColor];
|
||||||
{
|
|
||||||
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)changeColor:(UIColor *)strokeColor {
|
-(void)changeColor:(nullable UIColor *)strokeColor {
|
||||||
self.myCircle.strokeColor = strokeColor.CGColor;
|
[self setSurface: strokeColor];
|
||||||
}
|
|
||||||
|
|
||||||
-(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)pauseSpinner {
|
- (void)pauseSpinner {
|
||||||
if (self.resumeBlock) {
|
[self pause];
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resumeSpinner {
|
- (void)resumeSpinner {
|
||||||
self.hidden = NO;
|
[self resume];
|
||||||
if (!self.myCircle) {
|
|
||||||
[self setUpCircle];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.myDisplay.paused = NO;
|
|
||||||
self.prevFrame = CACurrentMediaTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Starts the spinner after a slight delay.
|
||||||
- (void)resumeSpinnerAfterDelay {
|
- (void)resumeSpinnerAfterDelay {
|
||||||
if (!self.resumeBlock) {
|
[self resumeAfterDelay];
|
||||||
__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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable NSDictionary *)pinWidthAndHeight {
|
- (nullable NSDictionary *)pinWidthAndHeight {
|
||||||
CGFloat diameter = radius*2 + lineWidth;
|
return [self pin];
|
||||||
return [NSLayoutConstraint constraintPinView:self heightConstraint:YES heightConstant:diameter widthConstraint:YES widthConstant:diameter];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -8,8 +8,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import VDSColorTokens
|
||||||
|
|
||||||
open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, TabsDelegate, MVMCorePresentationDelegateProtocol, SubNavSwipeNavigationProtocol {
|
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
|
/// The current managed view controller
|
||||||
private var viewController: UIViewController
|
private var viewController: UIViewController
|
||||||
@ -19,16 +22,37 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
|
|||||||
|
|
||||||
/// Used to layout the ui.
|
/// Used to layout the ui.
|
||||||
public lazy var stackView: UIStackView = {
|
public lazy var stackView: UIStackView = {
|
||||||
let stackView = UIStackView(arrangedSubviews: [tabs, subNavigationController.view])
|
let stackView = UIStackView(arrangedSubviews: [tabsWrapper, subNavigationController.view])
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.isAccessibilityElement = false
|
stackView.isAccessibilityElement = false
|
||||||
stackView.axis = .vertical
|
stackView.axis = .vertical
|
||||||
return stackView
|
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
|
private var tabsModel: TabsModel
|
||||||
|
|
||||||
public lazy var tabs: Tabs = {
|
public lazy var tabs: Tabs = {
|
||||||
let tabs = Tabs(model: tabsModel, delegateObjectIVar, nil)
|
let tabs = Tabs(model: tabsModel, delegateObjectIVar, nil)
|
||||||
|
tabs.backgroundColor = (viewController as? PageProtocol)?.pageModel?.navigationBar?.backgroundColor?.uiColor ?? VDSColor.backgroundPrimaryLight
|
||||||
tabs.delegate = self
|
tabs.delegate = self
|
||||||
return tabs
|
return tabs
|
||||||
}()
|
}()
|
||||||
@ -68,6 +92,11 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
|
|||||||
func setup(with loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) {
|
func setup(with loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) {
|
||||||
self.loadObject = loadObject
|
self.loadObject = loadObject
|
||||||
pageType = loadObject.pageType
|
pageType = loadObject.pageType
|
||||||
|
|
||||||
|
//update the tabs
|
||||||
|
tabs.fillContainer = isFillContainer
|
||||||
|
updateTabsMargin()
|
||||||
|
|
||||||
if shouldEnableSwipeGestures {
|
if shouldEnableSwipeGestures {
|
||||||
customInteractor = SubNavInteractor(viewController: self)
|
customInteractor = SubNavInteractor(viewController: self)
|
||||||
}
|
}
|
||||||
@ -121,6 +150,12 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
|
|||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
hideNavigationBarLine(true)
|
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.
|
/// Hides/Shows the navigation bar for the page.
|
||||||
open func hideNavigationBarLine(_ isHidden: Bool) {
|
open func hideNavigationBarLine(_ isHidden: Bool) {
|
||||||
@ -139,6 +174,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
|
|||||||
super.updateViews()
|
super.updateViews()
|
||||||
if screenSizeChanged() {
|
if screenSizeChanged() {
|
||||||
tabs.updateView(view.bounds.size.width)
|
tabs.updateView(view.bounds.size.width)
|
||||||
|
updateTabsMargin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +193,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
|
|||||||
|
|
||||||
/// Logs the action for the selected tab.
|
/// Logs the action for the selected tab.
|
||||||
open func trackSelectTab() {
|
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)
|
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) {
|
Task(priority: .userInitiated) {
|
||||||
await NavigationHandler.shared().replace(viewController: controller, navigationController:subNavigationController, delegateObject:delegateObject(), tryToReplace: false, animated: true)
|
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 {
|
let action = tabsModel.tabs[indexPath.row].action {
|
||||||
// Perform the tab action
|
// Perform the tab action
|
||||||
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: tabsModel, additionalData: getAdditionalDataForNewTabLoad(indexPath: indexPath), delegateObject: delegateObject())
|
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: tabsModel, additionalData: getAdditionalDataForNewTabLoad(indexPath: indexPath), delegateObject: delegateObject())
|
||||||
@ -329,7 +365,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
|
|||||||
// MARK: - MVMCoreUISwipeNavigationProtocol
|
// MARK: - MVMCoreUISwipeNavigationProtocol
|
||||||
|
|
||||||
public func swipeLeft() {
|
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)
|
_ = shouldSelectItem(IndexPath(row: tabs.selectedIndex + 1, section: 0), tabs: tabs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +377,6 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
|
|||||||
public func update(percentage: CGFloat) {
|
public func update(percentage: CGFloat) {
|
||||||
guard customInteractor?.interactive == true,
|
guard customInteractor?.interactive == true,
|
||||||
let index = index else { return }
|
let index = index else { return }
|
||||||
tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage)
|
// tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ open class CoreUIModelMapping: ModelMapping {
|
|||||||
ModelRegistry.register(handler: ExternalLink.self, for: ExternalLinkModel.self)
|
ModelRegistry.register(handler: ExternalLink.self, for: ExternalLinkModel.self)
|
||||||
ModelRegistry.register(handler: Link.self, for: LinkModel.self)
|
ModelRegistry.register(handler: Link.self, for: LinkModel.self)
|
||||||
ModelRegistry.register(handler: CaretLink.self, for: CaretLinkModel.self)
|
ModelRegistry.register(handler: CaretLink.self, for: CaretLinkModel.self)
|
||||||
|
ModelRegistry.register(handler: ButtonGroup.self, for: ButtonGroupModel.self)
|
||||||
|
|
||||||
// MARK:- Entry Field
|
// MARK:- Entry Field
|
||||||
ModelRegistry.register(handler: TextEntryField.self, for: TextEntryFieldModel.self)
|
ModelRegistry.register(handler: TextEntryField.self, for: TextEntryFieldModel.self)
|
||||||
@ -73,6 +74,8 @@ open class CoreUIModelMapping: ModelMapping {
|
|||||||
ModelRegistry.register(handler: Video.self, for: VideoModel.self)
|
ModelRegistry.register(handler: Video.self, for: VideoModel.self)
|
||||||
ModelRegistry.register(handler: Tilelet.self, for: TileletModel.self)
|
ModelRegistry.register(handler: Tilelet.self, for: TileletModel.self)
|
||||||
ModelRegistry.register(handler: Badge.self, for: BadgeModel.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
|
// MARK:- Horizontal Combination Molecules
|
||||||
ModelRegistry.register(handler: StringAndMoleculeView.self, for: StringAndMoleculeModel.self)
|
ModelRegistry.register(handler: StringAndMoleculeView.self, for: StringAndMoleculeModel.self)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user