Merge branch 'develop' into bugfix/CXTDT-139439

* develop: (75 commits)
  spelling fix
  default
  moved by request
  updates
  asd
  latest
  change
  noself
  put back
  multi select
  latest
  current
  move flag set
  debug cleanup
  active fix
  ugh timing workaround
  timing issue
  restructure video
  load image needed
  Active listener
  ...
This commit is contained in:
Damodaram 2021-02-23 10:37:15 +05:30
commit ef77483f8b
110 changed files with 1979 additions and 710 deletions

View File

@ -62,6 +62,8 @@
01EB369323609801006832FA /* HeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368C23609801006832FA /* HeaderModel.swift */; };
01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368D23609801006832FA /* HeadlineBodyModel.swift */; };
01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */; };
0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0FEC7325D42A5E00AF2548 /* BaseItemPickerEntryField.swift */; };
0A0FEC7825D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0FEC7725D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift */; };
0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; };
0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */; };
0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.swift */; };
@ -94,7 +96,7 @@
0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */; };
0A7EF85F23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */; };
0A7EF86123D8AC2500B2AAD1 /* DigitEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86023D8AC2500B2AAD1 /* DigitEntryFieldModel.swift */; };
0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */; };
0A7EF86323D8AFA000B2AAD1 /* BaseDropdownFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownFieldModel.swift */; };
0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */; };
0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */; };
0A849EFE246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */; };
@ -105,6 +107,7 @@
0A9D09212433796500D2E6C0 /* CarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D091B2433796500D2E6C0 /* CarouselIndicatorModel.swift */; };
0A9D09222433796500D2E6C0 /* CarouselIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D091C2433796500D2E6C0 /* CarouselIndicator.swift */; };
0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; };
0AA4D2E125CAEC72008DB32D /* AccessibilityModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA4D2E025CAEC72008DB32D /* AccessibilityModelProtocol.swift */; };
0AB000BA24BF63490090C5E7 /* ModalListPageTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB000B924BF63490090C5E7 /* ModalListPageTemplateModel.swift */; };
0AB000BC24BF64A50090C5E7 /* ModalStackPageTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB000BB24BF64A50090C5E7 /* ModalStackPageTemplateModel.swift */; };
0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */; };
@ -113,6 +116,8 @@
0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; };
0AD93A9F24C0AA5100E56A97 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7918F423F5E7EA00772FF4 /* ImageView.swift */; };
0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; };
0AE277E925D2ED4B0048A38D /* MultiItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE277E825D2ED4B0048A38D /* MultiItemDropdownEntryField.swift */; };
0AE277EC25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE277EB25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift */; };
0AE98BAF23FEF956004C5109 /* ExternalLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BAE23FEF956004C5109 /* ExternalLink.swift */; };
0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB223FF0934004C5109 /* ExternalLinkModel.swift */; };
0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB423FF18D2004C5109 /* Arrow.swift */; };
@ -346,6 +351,8 @@
D23EA800247EBD6C00D60C34 /* LabelBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */; };
D23EA802247EBED400D60C34 /* ImageBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */; };
D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; };
D24918F625D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */; };
D24918FA25D5ADBB00CAB4B1 /* PageScrolledClosureBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */; };
D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */; };
D2509ED62472EE2F001BFB9D /* NavigationImageButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */; };
D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; };
@ -402,6 +409,11 @@
D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */; };
D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */; };
D29C558A25C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C558925C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift */; };
D29C558D25C05C990082E7D6 /* BGVideoImageMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C558C25C05C990082E7D6 /* BGVideoImageMolecule.swift */; };
D29C559025C095210082E7D6 /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C558F25C095210082E7D6 /* Video.swift */; };
D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559225C0992D0082E7D6 /* VideoModel.swift */; };
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C559525C099630082E7D6 /* VideoDataManager.swift */; };
D29C94D5242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */; };
D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */; };
@ -604,6 +616,8 @@
01EB368C23609801006832FA /* HeaderModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderModel.swift; sourceTree = "<group>"; };
01EB368D23609801006832FA /* HeadlineBodyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyModel.swift; sourceTree = "<group>"; };
01F2A03123A4498200D954D8 /* CaretLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinkModel.swift; sourceTree = "<group>"; };
0A0FEC7325D42A5E00AF2548 /* BaseItemPickerEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemPickerEntryField.swift; sourceTree = "<group>"; };
0A0FEC7725D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemPickerEntryFieldModel.swift; sourceTree = "<group>"; };
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; };
0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackViewAlignment+Extension.swift"; sourceTree = "<group>"; };
0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
@ -637,7 +651,7 @@
0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryFieldModel.swift; sourceTree = "<group>"; };
0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryFieldModel.swift; sourceTree = "<group>"; };
0A7EF86023D8AC2500B2AAD1 /* DigitEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryFieldModel.swift; sourceTree = "<group>"; };
0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryFieldModel.swift; sourceTree = "<group>"; };
0A7EF86223D8AFA000B2AAD1 /* BaseDropdownFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownFieldModel.swift; sourceTree = "<group>"; };
0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryFieldModel.swift; sourceTree = "<group>"; };
0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryFieldModel.swift; sourceTree = "<group>"; };
0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = "<group>"; };
@ -650,6 +664,7 @@
0A9D091C2433796500D2E6C0 /* CarouselIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarouselIndicator.swift; sourceTree = "<group>"; };
0AA33B33239813C50067DD0F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = "<group>"; };
0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = "<group>"; };
0AA4D2E025CAEC72008DB32D /* AccessibilityModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityModelProtocol.swift; sourceTree = "<group>"; };
0AB000B924BF63490090C5E7 /* ModalListPageTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalListPageTemplateModel.swift; sourceTree = "<group>"; };
0AB000BB24BF64A50090C5E7 /* ModalStackPageTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalStackPageTemplateModel.swift; sourceTree = "<group>"; };
0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDatePicker+Extension.swift"; sourceTree = "<group>"; };
@ -657,6 +672,8 @@
0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = "<group>"; };
0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = "<group>"; };
0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
0AE277E825D2ED4B0048A38D /* MultiItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiItemDropdownEntryField.swift; sourceTree = "<group>"; };
0AE277EB25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiItemDropdownEntryFieldModel.swift; sourceTree = "<group>"; };
0AE98BAE23FEF956004C5109 /* ExternalLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalLink.swift; sourceTree = "<group>"; };
0AE98BB223FF0934004C5109 /* ExternalLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalLinkModel.swift; sourceTree = "<group>"; };
0AE98BB423FF18D2004C5109 /* Arrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arrow.swift; sourceTree = "<group>"; };
@ -890,6 +907,8 @@
D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelBarButtonItem.swift; sourceTree = "<group>"; };
D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBarButtonItem.swift; sourceTree = "<group>"; };
D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = "<group>"; };
D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageVisibilityClosureBehavior.swift; sourceTree = "<group>"; };
D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageScrolledClosureBehavior.swift; sourceTree = "<group>"; };
D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = "<group>"; };
D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationImageButtonModel.swift; sourceTree = "<group>"; };
D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = "<group>"; };
@ -944,6 +963,11 @@
D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPageModelProtocol.swift; sourceTree = "<group>"; };
D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewConstrainingProtocol.h; sourceTree = "<group>"; };
D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleMolecule.swift; sourceTree = "<group>"; };
D29C558925C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGVideoImageMoleculeModel.swift; sourceTree = "<group>"; };
D29C558C25C05C990082E7D6 /* BGVideoImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGVideoImageMolecule.swift; sourceTree = "<group>"; };
D29C558F25C095210082E7D6 /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = "<group>"; };
D29C559225C0992D0082E7D6 /* VideoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoModel.swift; sourceTree = "<group>"; };
D29C559525C099630082E7D6 /* VideoDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDataManager.swift; sourceTree = "<group>"; };
D29C94D4242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUICommonViewsUtility+Extension.swift"; sourceTree = "<group>"; };
D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUI.h; sourceTree = "<group>"; };
@ -1109,6 +1133,7 @@
011B58EE23A2AA850085F53C /* ModelProtocols */ = {
isa = PBXGroup;
children = (
0AA4D2E025CAEC72008DB32D /* AccessibilityModelProtocol.swift */,
D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */,
014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */,
D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */,
@ -1166,6 +1191,39 @@
path = FormUIHelpers;
sourceTree = "<group>";
};
0A0FEC7125D4246000AF2548 /* Dropdown Fields */ = {
isa = PBXGroup;
children = (
0A7EF86223D8AFA000B2AAD1 /* BaseDropdownFieldModel.swift */,
0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */,
0A0FEC8D25D4487F00AF2548 /* Date Dropdown */,
0A0FEC8A25D4486F00AF2548 /* Item Dropdown */,
);
path = "Dropdown Fields";
sourceTree = "<group>";
};
0A0FEC8A25D4486F00AF2548 /* Item Dropdown */ = {
isa = PBXGroup;
children = (
0A0FEC7725D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift */,
0A0FEC7325D42A5E00AF2548 /* BaseItemPickerEntryField.swift */,
0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */,
0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */,
0AE277EB25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift */,
0AE277E825D2ED4B0048A38D /* MultiItemDropdownEntryField.swift */,
);
path = "Item Dropdown";
sourceTree = "<group>";
};
0A0FEC8D25D4487F00AF2548 /* Date Dropdown */ = {
isa = PBXGroup;
children = (
0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */,
0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */,
);
path = "Date Dropdown";
sourceTree = "<group>";
};
0A5D59C323AD488600EFD9E9 /* Protocols */ = {
isa = PBXGroup;
children = (
@ -1225,6 +1283,8 @@
children = (
27F973522466074500CAB5C5 /* PageBehavior.swift */,
27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */,
D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */,
D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */,
);
path = Behaviors;
sourceTree = "<group>";
@ -1676,6 +1736,8 @@
D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */,
D253BB9D2458751F002DE544 /* BGImageMoleculeModel.swift */,
D253BB9B245874F8002DE544 /* BGImageMolecule.swift */,
D29C558925C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift */,
D29C558C25C05C990082E7D6 /* BGVideoImageMolecule.swift */,
);
path = OtherContainers;
sourceTree = "<group>";
@ -1710,6 +1772,8 @@
D2092348244A51D40044AD09 /* RadioSwatchModel.swift */,
AAB9C109243496DD00151545 /* RadioSwatch.swift */,
AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */,
D260105223CEA61600764D80 /* ToggleModel.swift */,
0AA33B392398524F0067DD0F /* Toggle.swift */,
AAA7CD68250641F90045B959 /* HeartModel.swift */,
AAA7CD6A250642080045B959 /* Heart.swift */,
);
@ -2007,8 +2071,6 @@
D28A838223CCBD3F00DFE4FC /* WheelModel.swift */,
943784F3236B77BB006A1E82 /* Wheel.swift */,
943784F4236B77BB006A1E82 /* WheelAnimationHandler.swift */,
D260105223CEA61600764D80 /* ToggleModel.swift */,
0AA33B392398524F0067DD0F /* Toggle.swift */,
0AE98BB623FF18E9004C5109 /* ArrowModel.swift */,
0AE98BB423FF18D2004C5109 /* Arrow.swift */,
94382085243238D100B43AF3 /* WebViewModel.swift */,
@ -2020,6 +2082,9 @@
AA37CBD42519072F0027344C /* Stars.swift */,
AA07EA902510A442009A2AE3 /* StarModel.swift */,
AA07EA922510A451009A2AE3 /* Star.swift */,
D29C559525C099630082E7D6 /* VideoDataManager.swift */,
D29C559225C0992D0082E7D6 /* VideoModel.swift */,
D29C558F25C095210082E7D6 /* Video.swift */,
);
path = Views;
sourceTree = "<group>";
@ -2027,23 +2092,18 @@
D29DF22B21E6A0FA003B2FB9 /* TextFields */ = {
isa = PBXGroup;
children = (
0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */,
0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */,
0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */,
0A21DB7E235DECC500C160A2 /* EntryField.swift */,
0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */,
0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */,
0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */,
0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */,
0A8321AE2355FE9500CB7F00 /* DigitBox.swift */,
0A7EF86023D8AC2500B2AAD1 /* DigitEntryFieldModel.swift */,
0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */,
0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */,
0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */,
0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */,
0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */,
0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */,
0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */,
0A25209724645B76000FA9F6 /* TextViewEntryFieldModel.swift */,
0A25209524645AFD000FA9F6 /* TextViewEntryField.swift */,
0A0FEC7125D4246000AF2548 /* Dropdown Fields */,
);
path = TextFields;
sourceTree = "<group>";
@ -2157,6 +2217,7 @@
D2B18B7D236090D500A9AEDC /* BaseClasses */ = {
isa = PBXGroup;
children = (
0A5D59C323AD488600EFD9E9 /* Protocols */,
0A6682B3243769C700AD3CA1 /* TextView.swift */,
C003506023AA94CD00B6AC29 /* Button.swift */,
D2B18B7E2360913400A9AEDC /* Control.swift */,
@ -2167,7 +2228,6 @@
BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */,
D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */,
D264FAA92440F97600D98315 /* CollectionView.swift */,
0A5D59C323AD488600EFD9E9 /* Protocols */,
0A7918F423F5E7EA00772FF4 /* ImageView.swift */,
D272F5F82473163100BD1A8F /* BarButtonItem.swift */,
D2EC7BDC2527B83700F540AF /* SectionHeaderFooterView.swift */,
@ -2449,6 +2509,7 @@
D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */,
AAC23FAD24D92A0D009208DF /* ListThreeColumnSpeedTestModel.swift in Sources */,
BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */,
0AE277EC25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift in Sources */,
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */,
3265B30424BCA749000D154B /* HeadersH1NoButtonsBodyText.swift in Sources */,
AAA7CD69250641F90045B959 /* HeartModel.swift in Sources */,
@ -2463,6 +2524,7 @@
D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */,
94382086243238D100B43AF3 /* WebViewModel.swift in Sources */,
D28764F9245A327200CB882D /* TwoLinkView.swift in Sources */,
0AE277E925D2ED4B0048A38D /* MultiItemDropdownEntryField.swift in Sources */,
D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */,
0A6682B5243769C700AD3CA1 /* TextView.swift in Sources */,
D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */,
@ -2563,6 +2625,7 @@
D2CAC7CB251104E100C75681 /* NotificationXButtonModel.swift in Sources */,
014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */,
AAC23FAF24D92A1E009208DF /* ListThreeColumnSpeedTest.swift in Sources */,
0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */,
D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */,
D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */,
D2ED27ED254B0CE700A1C293 /* ActionPopupModel.swift in Sources */,
@ -2678,6 +2741,7 @@
BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */,
017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */,
323AC96C24C837FF00F8E4C4 /* ListThreeColumnBillChanges.swift in Sources */,
0A0FEC7825D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift in Sources */,
D28A837923C7D5BC00DFE4FC /* PageModelProtocol.swift in Sources */,
D2351C7C24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift in Sources */,
017BEB7B236763000024EF95 /* LineModel.swift in Sources */,
@ -2685,6 +2749,7 @@
94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */,
011D958724042492000E3791 /* FormFieldProtocol.swift in Sources */,
011D95AF2407266E000E3791 /* RadioButtonModel.swift in Sources */,
D24918F625D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift in Sources */,
D20492A624329CE200A5EED6 /* LoadImageView.swift in Sources */,
017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */,
D274CA332236A78900B01B62 /* FooterView.swift in Sources */,
@ -2742,6 +2807,7 @@
AA104AC924472DC7004D2810 /* HeadersH1ButtonModel.swift in Sources */,
0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */,
D20C7009250BF99B0095B21C /* TopNotificationModel.swift in Sources */,
D29C558A25C05C7D0082E7D6 /* BGVideoImageMoleculeModel.swift in Sources */,
8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */,
BB2FB3BD247E7EF200DF73CD /* Tags.swift in Sources */,
AA104ADC244734EA004D2810 /* HeadersH1LandingPageHeaderModel.swift in Sources */,
@ -2769,6 +2835,7 @@
012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */,
D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */,
0AE14F64238315D2005417F8 /* TextField.swift in Sources */,
D24918FA25D5ADBB00CAB4B1 /* PageScrolledClosureBehavior.swift in Sources */,
0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */,
D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */,
0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */,
@ -2781,6 +2848,7 @@
D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */,
AAB7EDF1246ADA2A00E54929 /* ListProgressBarThin.swift in Sources */,
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */,
D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */,
D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */,
D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */,
AA71AD3E24A32FCE00ACA76F /* HeadersH2LinkModel.swift in Sources */,
@ -2844,8 +2912,10 @@
012A889C23889E8400FE3DA1 /* TemplateModelProtocol.swift in Sources */,
52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */,
D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */,
0AA4D2E125CAEC72008DB32D /* AccessibilityModelProtocol.swift in Sources */,
C003506123AA94CD00B6AC29 /* Button.swift in Sources */,
DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */,
D29C559025C095210082E7D6 /* Video.swift in Sources */,
D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */,
AA104B1C24474A76004D2810 /* HeadersH2ButtonsModel.swift in Sources */,
0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */,
@ -2859,7 +2929,7 @@
D2D2FCF0252B72AF0033EAAA /* MoleculeSectionFooterModel.swift in Sources */,
BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */,
D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */,
0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */,
0A7EF86323D8AFA000B2AAD1 /* BaseDropdownFieldModel.swift in Sources */,
0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */,
D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */,
D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */,
@ -2871,6 +2941,7 @@
BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */,
C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */,
D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */,
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */,
8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */,
D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */,
012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */,
@ -2888,6 +2959,7 @@
AA633B3324989ED500731E80 /* HeadersH2PricingTwoRows.swift in Sources */,
01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */,
0A6682A22434DB4F00AD3CA1 /* ListLeftVariableRadioButtonBodyText.swift in Sources */,
D29C558D25C05C990082E7D6 /* BGVideoImageMolecule.swift in Sources */,
EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */,
0105618D224BBE7700E1557D /* FormValidator.swift in Sources */,
01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */,
@ -2961,6 +3033,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@ -3026,6 +3099,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@ -18,6 +18,7 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW
public static var identifier: String = "button"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var title: String
public var action: ActionModelProtocol
public var enabled: Bool = true
@ -93,27 +94,27 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW
//--------------------------------------------------
public func enabled_fillColor() -> UIColor? {
return (inverted ? enabledFillColor_inverted : enabledFillColor)?.uiColor
(inverted ? enabledFillColor_inverted : enabledFillColor)?.uiColor
}
public func enabled_textColor() -> UIColor? {
return (inverted ? enabledTextColor_inverted : enabledTextColor)?.uiColor
(inverted ? enabledTextColor_inverted : enabledTextColor)?.uiColor
}
public func enabled_borderColor() -> UIColor? {
return (inverted ? enabledBorderColor_inverted : enabledBorderColor)?.uiColor
(inverted ? enabledBorderColor_inverted : enabledBorderColor)?.uiColor
}
public func disabled_fillColor() -> UIColor? {
return (inverted ? disabledFillColor_inverted : disabledFillColor)?.uiColor
(inverted ? disabledFillColor_inverted : disabledFillColor)?.uiColor
}
public func disabled_textColor() -> UIColor? {
return (inverted ? disabledTextColor_inverted : disabledTextColor)?.uiColor
(inverted ? disabledTextColor_inverted : disabledTextColor)?.uiColor
}
public func disabled_borderColor() -> UIColor? {
return (inverted ? disabledBorderColor_inverted : disabledBorderColor)?.uiColor
(inverted ? disabledBorderColor_inverted : disabledBorderColor)?.uiColor
}
/// Defines the default appearance for the primary style.
@ -172,6 +173,7 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case title
case inverted
case action
@ -195,6 +197,7 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
title = try typeContainer.decode(String.self, forKey: .title)
action = try typeContainer.decodeModel(codingKey: .action)
@ -252,6 +255,7 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW
try container.encode(inverted, forKey: .inverted)
try container.encodeModel(action, forKey: .action)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(enabledFillColor, forKey: .fillColor)
try container.encodeIfPresent(enabledTextColor, forKey: .textColor)
try container.encodeIfPresent(enabledBorderColor, forKey: .borderColor)

View File

@ -127,7 +127,6 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
}
public func updateCaretSpacing(_ spacing: CGFloat) {
caretSpacingConstraint?.constant = spacing
}
@ -151,15 +150,9 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
setTitle(model.title, for: .normal)
}
public func needsToBeConstrained() -> Bool {
return true
}
public func needsToBeConstrained() -> Bool { true }
open func horizontalAlignment() -> UIStackView.Alignment {
return .leading
}
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 10.5
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 10.5 }
}

View File

@ -17,6 +17,7 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
public static var identifier: String = "caretLink"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var title: String
public var action: ActionModelProtocol
public var enabledColor: Color = Color(uiColor: .mvmBlack)
@ -41,6 +42,7 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
private enum CodingKeys: String, CodingKey {
case backgroundColor
case accessibilityIdentifier
case title
case action
case enabledColor_inverted
@ -60,6 +62,7 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
title = try typeContainer.decode(String.self, forKey: .title)
if let enabledColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor_inverted) {
@ -94,6 +97,7 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(title, forKey: .title)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeModel(action, forKey: .action)
try container.encode(enabled, forKey: .enabledColor)
try container.encodeIfPresent(disabledColor, forKey: .disabledColor)

View File

@ -11,18 +11,18 @@ import UIKit
@objcMembers open class Link: Button {
//--------------------------------------------------
// MARK: - Lifecycle
// MARK: - Draw
//--------------------------------------------------
open override func draw(_ rect: CGRect) {
guard let textRect = titleLabel?.frame else { return }
let context = UIGraphicsGetCurrentContext()
guard let textRect = titleLabel?.frame,
let context = UIGraphicsGetCurrentContext()
else { return }
// Set line to the same color as the text
if let color = titleLabel?.textColor?.cgColor {
context?.setStrokeColor(color)
context.setStrokeColor(color)
}
// x should be according to the text, not the button
@ -31,9 +31,9 @@ import UIKit
// Line is 1 point below the text
let y = textRect.origin.y + textRect.size.height + 1
context?.move(to: CGPoint(x: x, y: y))
context?.addLine(to: CGPoint(x: x + textRect.size.width, y: y))
context?.strokePath()
context.move(to: CGPoint(x: x, y: y))
context.addLine(to: CGPoint(x: x + textRect.size.width, y: y))
context.strokePath()
}
open override var intrinsicContentSize: CGSize {
@ -58,9 +58,7 @@ import UIKit
set(with: model.action, delegateObject: delegateObject, additionalData: additionalData)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 31
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 31 }
}
// MARK: - MVMCoreViewProtocol
@ -69,16 +67,12 @@ extension Link {
open override func updateView(_ size: CGFloat) {
super.updateView(size)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
var width = size
if MVMCoreGetterUtility.fequal(a: Float.leastNormalMagnitude, b: Float(size)) {
width = MVMCoreUIUtility.getWidth()
}
self.titleLabel?.font = MFStyler.fontB2(forWidth: width)
var width = size
if MVMCoreGetterUtility.fequal(a: Float.leastNormalMagnitude, b: Float(size)) {
width = MVMCoreUIUtility.getWidth()
}
titleLabel?.font = MFStyler.fontB2(forWidth: width)
}
open override func setupView() {
@ -98,7 +92,5 @@ extension Link {
// MARK: - MVMCoreUIViewConstrainingProtocol
extension Link: MVMCoreUIViewConstrainingProtocol {
open func horizontalAlignment() -> UIStackView.Alignment {
return .leading
}
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
}

View File

@ -14,11 +14,10 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String {
return "link"
}
public class var identifier: String { "link" }
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var title: String
public var action: ActionModelProtocol
public var enabled = true
@ -44,6 +43,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case title
case action
case enabled
@ -62,6 +62,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
title = try typeContainer.decode(String.self, forKey: .title)
action = try typeContainer.decodeModel(codingKey: .action)
@ -95,6 +96,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
try container.encode(title, forKey: .title)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeModel(action, forKey: .action)
try container.encode(inverted, forKey: .inverted)
try container.encode(enabled, forKey: .enabled)

View File

@ -18,7 +18,7 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
var size = MVMCoreUIUtility.getWidth()
var buttonModel: ButtonModel? {
get { return model as? ButtonModel }
get { model as? ButtonModel }
}
/// Need to re-style on set.
@ -27,9 +27,7 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
}
open var buttonSize: Styler.Button.Size = .standard {
didSet {
buttonModel?.size = buttonSize
}
didSet { buttonModel?.size = buttonSize }
}
//--------------------------------------------------
@ -47,12 +45,12 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
//--------------------------------------------------
public var enabledTitleColor: UIColor? {
get { return titleColor(for: .normal) }
get { titleColor(for: .normal) }
set { setTitleColor(newValue, for: .normal) }
}
public var disabledTitleColor: UIColor? {
get { return titleColor(for: .disabled) }
get { titleColor(for: .disabled) }
set { setTitleColor(newValue, for: .disabled) }
}
@ -106,6 +104,11 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
self.disabledTitleColor = disabledTitleColor
}
#if DEBUG
// Useful to detect with isHittable when performing UI testing.
isAccessibilityElement = isEnabled
#endif
if isEnabled {
if let fillColor = buttonModel?.enabledColors.fill {
backgroundColor = fillColor
@ -128,7 +131,7 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
}
private func getInnerPadding() -> CGFloat {
return getHeight() / 2.0
getHeight() / 2.0
}
private func getHeight() -> CGFloat {
@ -181,9 +184,11 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
guard let model = model as? ButtonModel else { return }
setTitle(model.title, for: .normal)
if let size = model.size {
buttonSize = size
}
model.updateUI = { [weak self] in
MVMCoreDispatchUtility.performBlock(onMainThread: {
self?.enableField(model.enabled)
@ -228,9 +233,7 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
// MARK: - MVMCoreUIViewConstrainingProtocol
//--------------------------------------------------
open func horizontalAlignment() -> UIStackView.Alignment {
return .center
}
open func horizontalAlignment() -> UIStackView.Alignment { .center }
public func enableField(_ enable: Bool) {
isEnabled = enable

View File

@ -43,7 +43,7 @@ import UIKit
//--------------------------------------------------
public override var showError: Bool {
get { return super.showError }
get { super.showError }
set (error) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
@ -80,7 +80,7 @@ import UIKit
open override func setupView() {
super.setupView()
addSubview(digitField)
digitField.delegate = self
digitField.didDeleteDelegate = self
@ -140,7 +140,7 @@ import UIKit
super.updateView(size)
if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(previousSize)) {
var width: CGFloat = 0
var height: CGFloat = 0
var pointSize: CGFloat = 13

View File

@ -75,7 +75,7 @@ import UIKit
private var selectedDigitBox: DigitBox?
public var digitEntryModel: DigitEntryFieldModel? {
return model as? DigitEntryFieldModel
model as? DigitEntryFieldModel
}
//--------------------------------------------------
@ -83,7 +83,7 @@ import UIKit
//--------------------------------------------------
public override var isEnabled: Bool {
get { return super.isEnabled }
get { super.isEnabled }
set (enabled) {
digitBoxes.forEach { $0.isEnabled = enabled }
super.isEnabled = enabled
@ -91,7 +91,7 @@ import UIKit
}
public override var showError: Bool {
get { return super.showError }
get { super.showError }
set (error) {
digitBoxes.forEach { $0.showError = error }
super.showError = error
@ -99,7 +99,7 @@ import UIKit
}
public override var isLocked: Bool {
get { return super.isLocked }
get { super.isLocked }
set (locked) {
digitBoxes.forEach { $0.isLocked = locked }
super.isLocked = locked
@ -162,7 +162,7 @@ import UIKit
/// If you're using a MFViewController, you must set this to it
public override weak var uiTextFieldDelegate: UITextFieldDelegate? {
get { return textField.delegate }
get { textField.delegate }
set {
textField.delegate = self
proprietorTextDelegate = newValue
@ -357,9 +357,7 @@ import UIKit
super.set(with: model, delegateObject, additionalData)
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 115
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 115 }
}
// MARK: - TextField Delegate
@ -452,11 +450,11 @@ extension DigitEntryField {
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
}
@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
}
}

View File

@ -12,12 +12,10 @@
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String {
return "digitTextField"
}
public override class var identifier: String { "digitTextField" }
public var digits: Int = 4
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------

View File

@ -1,5 +1,5 @@
//
// BaseDropdownEntryField.swift
// BaseDropdownField.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 10/23/19.
@ -28,8 +28,9 @@ import UIKit
}()
public var baseDropdownEntryFieldModel: BaseDropdownEntryFieldModel? {
return model as? BaseDropdownEntryFieldModel
model as? BaseDropdownEntryFieldModel
}
var additionalData: [AnyHashable: Any]?
//--------------------------------------------------
@ -54,7 +55,7 @@ import UIKit
@objc required public init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("DropdownEntryField does not support xib.")
fatalError("\(String(describing: Self.self)) does not support xib.")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
@ -86,15 +87,15 @@ import UIKit
dropDownCaretView.setOptional(with: model.caretView, delegateObject, additionalData)
}
public override func dismissFieldInput(_ sender: Any?) {
performDropdownAction()
super.dismissFieldInput(sender)
}
@objc public override func dismissFieldInput(_ sender: Any?) {
performDropdownAction()
super.dismissFieldInput(sender)
}
func performDropdownAction() {
if let baseDropdownEntryFieldModel = baseDropdownEntryFieldModel, let actionModel = baseDropdownEntryFieldModel.action, let actionMap = actionModel.toJSON() {
if let baseDropdownEntryFieldModel = baseDropdownEntryFieldModel, let actionModel = baseDropdownEntryFieldModel.action {
let additionalDataWithSource = additionalData.dictionaryAdding(key: KeySourceModel, value: baseDropdownEntryFieldModel)
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject)
MVMCoreActionHandler.shared()?.asyncHandleAction(with: actionModel, additionalData: additionalDataWithSource, delegateObject: delegateObject)
}
}
}

View File

@ -14,9 +14,7 @@
public var caretView: CaretViewModel?
public var action: ActionModelProtocol?
public override class var identifier: String {
return ""
}
public override class var identifier: String { "" }
//--------------------------------------------------
// MARK: - Keys
@ -29,7 +27,7 @@
}
//--------------------------------------------------
// MARK: - Initializers
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {

View File

@ -23,15 +23,15 @@ import UIKit
}()
public var dateFormat: String? {
get { dateDropdownModel?.dateFormat }
set {
guard let newValue = newValue else { return }
dateDropdownModel?.dateFormat = newValue
}
get { return dateDropdownModel?.dateFormat }
}
public var dateDropdownModel: DateDropdownEntryFieldModel? {
return model as? DateDropdownEntryFieldModel
model as? DateDropdownEntryFieldModel
}
//--------------------------------------------------
@ -69,6 +69,7 @@ import UIKit
datePicker = UIDatePicker.addDatePicker(to: textField)
datePicker?.addTarget(self, action: #selector(pickerValueChanged), for: .valueChanged)
datePicker?.timeZone = NSTimeZone.system
textField.inputView = datePicker
UIToolbar.addDismissToolbar(to: textField, delegate: self, action: #selector(dismissFieldInput))
}

View File

@ -11,9 +11,7 @@
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String {
return "dateDropdownEntryField"
}
public override class var identifier: String { "dateDropdownEntryField" }
public var dateFormatter: DateFormatter = {
let formatter = DateFormatter()

View File

@ -0,0 +1,85 @@
//
// BaseItemPickerEntryField.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/10/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import UIKit
public typealias TextFieldAndPickerDelegate = (UITextFieldDelegate & UIPickerViewDelegate & UIPickerViewDataSource)
open class BaseItemPickerEntryField: BaseDropdownEntryField, UIPickerViewDelegate, UIPickerViewDataSource {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
open lazy var pickerView = UIPickerView.addPicker(to: textField, delegate: self, dismissAction: #selector(dismissFieldInput))
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
/// Closure passed here will run as picker changes items.
public var observeDropdownChange: ((String, String) -> ())?
/// Closure passed here will run upon dismissing the selection picker.
public var observeDropdownSelection: ((String) -> ())?
/// When selecting for first responder, allow initial selected value to appear in empty text field.
public var setInitialValueInTextField = true
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
@objc open override func setupFieldContainerContent(_ container: UIView) {
super.setupFieldContainerContent(container)
textField.hideBlinkingCaret = true
textField.autocorrectionType = .no
uiTextFieldDelegate = self
}
@objc public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) {
pickerView.delegate = delegate
pickerView.dataSource = delegate
}
//--------------------------------------------------
// MARK: - Molecular
//--------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
setPickerDelegates(delegate: self)
}
//--------------------------------------------------
// MARK: - Picker Delegate to Override
//--------------------------------------------------
public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 0 }
public func numberOfComponents(in pickerView: UIPickerView) -> Int { 0 }
}
// MARK: - Accessibility
extension BaseItemPickerEntryField {
@objc open override func setAccessibilityString(_ accessibilityString: String?) {
var accessibilityString = accessibilityString ?? ""
if let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") {
accessibilityString += textPickerItem
}
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
}
}

View File

@ -0,0 +1,18 @@
//
// BaseItemPickerEntryFieldModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/10/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import UIKit
open class BaseItemPickerEntryFieldModel: BaseDropdownEntryFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String { "" }
}

View File

@ -8,28 +8,16 @@
import UIKit
public typealias TextFieldAndPickerDelegate = (UITextFieldDelegate & UIPickerViewDelegate & UIPickerViewDataSource)
open class ItemDropdownEntryField: BaseDropdownEntryField {
open class ItemDropdownEntryField: BaseItemPickerEntryField {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var pickerData: [String] = []
open var pickerView: UIPickerView?
/// When selecting for first responder, allow initial selected value to appear in empty text field.
public var setInitialValueInTextField = true
/// Closure passed here will run as picker changes items.
public var observeDropdownChange: ((String, String)->())?
/// Closure passed here will run upon dismissing the selection picker.
public var observeDropdownSelection: ((String)->())?
public var itemDropdownEntryFieldModel: ItemDropdownEntryFieldModel? {
return model as? ItemDropdownEntryFieldModel
model as? ItemDropdownEntryFieldModel
}
//--------------------------------------------------
@ -61,26 +49,13 @@ open class ItemDropdownEntryField: BaseDropdownEntryField {
// MARK: - Methods
//--------------------------------------------------
@objc open override func setupFieldContainerContent(_ container: UIView) {
super.setupFieldContainerContent(container)
pickerView = UIPickerView.addPicker(to: textField, delegate: self, dismissAction: #selector(dismissFieldInput))
textField.hideBlinkingCaret = true
textField.autocorrectionType = .no
uiTextFieldDelegate = self
}
@objc public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) {
pickerView?.delegate = delegate
pickerView?.dataSource = delegate
}
/// Sets the textField with the first value of the available picker data.
@objc private func setInitialValueFromPicker() {
guard !pickerData.isEmpty else { return }
if setInitialValueInTextField, let pickerIndex = pickerView?.selectedRow(inComponent: 0) {
if setInitialValueInTextField {
let pickerIndex = pickerView.selectedRow(inComponent: 0)
observeDropdownChange?(text ?? "", pickerData[pickerIndex])
text = pickerData[pickerIndex]
itemDropdownEntryFieldModel?.selectedIndex = pickerIndex
@ -98,9 +73,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField {
guard !pickerData.isEmpty else { return }
if let pickerIndex = pickerView?.selectedRow(inComponent: 0) {
observeDropdownSelection?(pickerData[pickerIndex])
}
observeDropdownSelection?(pickerData[pickerView.selectedRow(inComponent: 0)])
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
@ -109,23 +82,21 @@ open class ItemDropdownEntryField: BaseDropdownEntryField {
guard let model = model as? ItemDropdownEntryFieldModel else { return }
pickerData = model.options
setPickerDelegates(delegate: self)
if let pickerView = pickerView, let index = model.selectedIndex {
if let index = model.selectedIndex {
self.pickerView.selectRow(index, inComponent: 0, animated: false)
self.pickerView(pickerView, didSelectRow: index, inComponent: 0)
}
}
}
// MARK:- Base Picker Delegate
extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource {
//--------------------------------------------------
// MARK: - Picker Delegate
//--------------------------------------------------
@objc public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
@objc public override func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 }
@objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
@objc public override func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
pickerData.count
}
@objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
@ -142,18 +113,3 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource {
itemDropdownEntryFieldModel?.selectedIndex = row
}
}
// MARK: - Accessibility
extension ItemDropdownEntryField {
@objc open override func setAccessibilityString(_ accessibilityString: String?) {
var accessibilityString = accessibilityString ?? ""
if let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") {
accessibilityString += textPickerItem
}
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
}
}

View File

@ -6,22 +6,24 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
@objcMembers open class ItemDropdownEntryFieldModel: BaseDropdownEntryFieldModel {
@objcMembers open class ItemDropdownEntryFieldModel: BaseItemPickerEntryFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String {
return "dropDown"
}
public override class var identifier: String { "dropDown" }
public var options: [String] = []
public var selectedIndex: Int?
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
public override func formFieldValue() -> AnyHashable? {
guard !options.isEmpty,
let index = selectedIndex
else { return nil }
let index = selectedIndex
else { return nil }
return options[index]
}
@ -34,7 +36,7 @@
case options
case selectedIndex
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -47,10 +49,7 @@
if let selectedIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) {
self.selectedIndex = selectedIndex
}
if let index = selectedIndex {
baseValue = options.indices.contains(index) ? options[index] : nil
baseValue = options.indices.contains(selectedIndex) ? options[selectedIndex] : nil
}
}

View File

@ -0,0 +1,144 @@
//
// MultiItemDropdownEndryField.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/9/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import UIKit
open class MultiItemDropdownEntryField: BaseItemPickerEntryField {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
/// Datasource of the picker view.
open var pickerComponents: [[String]] {
dropdownModel?.components ?? [[]]
}
public var dropdownModel: MultiItemDropdownEntryFieldModel? {
model as? MultiItemDropdownEntryFieldModel
}
/// The number of components available
public var componentCount: Int {
pickerComponents.count
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@objc public override init(frame: CGRect) {
super.init(frame: frame)
}
@objc public convenience init() {
self.init(frame: .zero)
}
@objc required public init?(coder: NSCoder) {
fatalError("MultiItemDropdownEntryField init(coder:) has not been implemented")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Sets the textField with the first value of the available picker data.
@objc private func setInitialValueFromPicker() {
guard setInitialValueInTextField,
!pickerComponents.isEmpty,
let rowText = dropdownModel?.selectedRowText
else { return }
// Update observing function and update text UI.
observeDropdownChange?(text ?? "", rowText)
text = rowText
// Set row index value of selected component.
for component in 0..<componentCount {
let pickerIndex = pickerView.selectedRow(inComponent: component)
dropdownModel?.selectedIndexes[component] = pickerIndex
}
}
func pickerHasComponent(_ index: Int) -> Bool {
!pickerComponents.isEmpty && !pickerComponents[index].isEmpty
}
//--------------------------------------------------
// MARK: - TextField Observation
//--------------------------------------------------
/// Observing action of the textfield for initial interaction with the picker.
@objc override func startEditing() {
super.startEditing()
setInitialValueFromPicker()
}
/// Observing action for when the user has ended inputting with the picker.
@objc override func endInputing() {
super.endInputing()
guard !pickerComponents.isEmpty,
let rowText = dropdownModel?.selectedRowText
else { return }
// update observing function with most recent selection.
observeDropdownSelection?(rowText)
}
//--------------------------------------------------
// 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? MultiItemDropdownEntryFieldModel else { return }
// Select initial rows if selectedIndexes retains value.
for (component, row) in model.selectedIndexes {
self.pickerView.selectRow(row, inComponent: component, animated: false)
self.pickerView(pickerView, didSelectRow: row, inComponent: component)
}
}
//--------------------------------------------------
// MARK: - Picker Delegate
//--------------------------------------------------
@objc public override func numberOfComponents(in pickerView: UIPickerView) -> Int { componentCount }
@objc public override func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
pickerComponents[component].count
}
@objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
guard pickerHasComponent(component) else { return nil }
return pickerComponents[component][row]
}
@objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
guard pickerHasComponent(component) else { return }
let oldText = text ?? ""
dropdownModel?.selectedIndexes[component] = row
let newText = dropdownModel?.selectedRowText
observeDropdownChange?(oldText, newText ?? "")
text = newText
}
}

View File

@ -0,0 +1,126 @@
//
// MultiItemDropdownEntryFieldModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/9/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers open class MultiItemDropdownEntryFieldModel: BaseItemPickerEntryFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String { "multiDropdown" }
public var components: [[String]] = [[]]
public var selectedIndexes: [Int: Int] = [:]
public var delimiters: [Int: String] = [:]
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
public override func formFieldValue() -> AnyHashable? {
guard !components.isEmpty && !selectedIndexes.isEmpty else { return nil }
return selectedRowText
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// - parameter index: The index of the delimiter.
/// - returns: The delimiter for a given index. Defaults to whitespace for valid index if no delimiters is provided. If invalid index, empty string.
public func delimiter(for index: Int) -> String {
guard index != components.count - 1 else { return "" }
return delimiters[index, default: " "]
}
/// A string of the picker row concatenated by whitespace or delimiters if provided.
public var selectedRowText: String {
var text = ""
for i in 0..<components.count {
let pickerIndex = selectedIndexes[i] ?? 0
text += components[i][pickerIndex] + delimiter(for: i)
}
return text
}
public var selectedIndexesArray: [Int] {
var indexArray: [Int] = []
for i in 0..<selectedIndexes.count {
guard let selectIndex = selectedIndexes[i] else { return [] }
indexArray.append(selectIndex)
}
return indexArray
}
public var delimiterArray: [String] {
var array: [String] = []
for i in 0..<delimiters.count {
guard let delimiterIndex = delimiters[i] else { return [] }
array.append(delimiterIndex)
}
return array
}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case components
case selectedIndexes
case delimiters
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
components = try typeContainer.decode([[String]].self, forKey: .components)
if let delimiters = try typeContainer.decodeIfPresent([String].self, forKey: .delimiters) {
for (index, delimiter) in delimiters.enumerated() {
self.delimiters[index] = delimiter
}
}
if let indexes = try typeContainer.decodeIfPresent([Int].self, forKey: .selectedIndexes) {
for (component, index) in indexes.enumerated() {
self.selectedIndexes[component] = index
}
baseValue = selectedRowText
}
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(components, forKey: .components)
try container.encode(selectedIndexesArray, forKey: .selectedIndexes)
try container.encodeIfPresent(delimiterArray, forKey: .delimiters)
}
}

View File

@ -36,7 +36,7 @@ import UIKit
label.setContentCompressionResistancePriority(.required, for: .vertical)
return label
}()
//--------------------------------------------------
// MARK: - Delegate
//--------------------------------------------------
@ -58,23 +58,23 @@ import UIKit
/// Toggles enabled (original) or disabled UI.
public var isEnabled: Bool {
get { return entryFieldContainer.isEnabled }
get { entryFieldContainer.isEnabled }
set (enabled) {
self.titleLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
self.feedbackLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
self.entryFieldContainer.isEnabled = enabled
self.entryFieldModel?.enabled = enabled
titleLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
feedbackLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
entryFieldContainer.isEnabled = enabled
entryFieldModel?.enabled = enabled
}
}
/// Toggles error or original UI.
public var showError: Bool {
get { return entryFieldContainer.showError }
get { entryFieldContainer.showError }
set (error) {
self.feedback = error ? errorMessage : entryFieldModel?.feedback
self.feedbackLabel.textColor = error ? entryFieldModel?.errorTextColor?.uiColor ?? .mvmBlack : .mvmBlack
self.entryFieldContainer.showError = error
self.entryFieldModel?.showError = error
feedback = error ? errorMessage : entryFieldModel?.feedback
feedbackLabel.textColor = error ? entryFieldModel?.errorTextColor?.uiColor ?? .mvmBlack : .mvmBlack
entryFieldContainer.showError = error
entryFieldModel?.showError = error
}
}
@ -84,49 +84,48 @@ import UIKit
/// Toggles original or locked UI.
public var isLocked: Bool {
get { return entryFieldContainer.isLocked }
get { entryFieldContainer.isLocked }
set (locked) {
self.entryFieldContainer.isLocked = locked
self.entryFieldModel?.locked = locked
entryFieldContainer.isLocked = locked
entryFieldModel?.locked = locked
}
}
/// Toggles selected or original (unselected) UI.
public var isSelected: Bool {
get { return entryFieldContainer.isSelected }
get { entryFieldContainer.isSelected }
set (selected) {
self.entryFieldContainer.isSelected = selected
self.entryFieldModel?.selected = selected
entryFieldContainer.isSelected = selected
entryFieldModel?.selected = selected
}
}
/// Sets the text of titleLabel
public var title: String? {
get { return titleLabel.text }
set (newText) {
titleLabel.text = newText
setAccessibilityString(newText)
get { titleLabel.text }
set {
titleLabel.text = newValue
setAccessibilityString(newValue)
}
}
/// Override this to conveniently get/set the textfield(s).
public var text: String? {
get { return nil }
get { nil }
set { fatalError("You MUST override EntryField's 'text' variable in your subclass.") }
}
/// Sets feedback text in the textField.
public var feedback: String? {
get { return feedbackLabel.text }
set (newFeedback) {
feedbackLabel.text = newFeedback
get { feedbackLabel.text }
set {
feedbackLabel.text = newValue
feedbackLabel.accessibilityElementsHidden = feedbackLabel.text?.isEmpty ?? true
entryFieldContainer.refreshUI(updateMoleculeLayout: true)
}
}
public var entryFieldModel: EntryFieldModel? {
return model as? EntryFieldModel
model as? EntryFieldModel
}
//--------------------------------------------------
@ -223,7 +222,7 @@ import UIKit
entryFieldContainer.refreshUI()
}
/// Intended to add the interactive content (i.e. textField) to the entryFieldContainer.
/// Intended to add the interactive content (i.e. textField) to the entryFieldContainer.
@objc open func setupFieldContainerContent(_ container: UIView) {
// To Be Overriden
}
@ -345,9 +344,7 @@ import UIKit
}
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 115
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 115 }
}
// MARK: - Accessibility

View File

@ -14,9 +14,7 @@ import Foundation
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String {
return ""
}
public class var identifier: String { "" }
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
@ -48,7 +46,7 @@ import Foundation
/// Temporary binding mechanism for the view to update on enable changes.
public var updateUI: ActionBlock?
// TODO: Remove once updateUI is fixed with isSelected
public var updateUIDynamicError: ActionBlock?
@ -142,9 +140,9 @@ import Foundation
try container.encodeIfPresent(selected, forKey: .selected)
try container.encodeIfPresent(errorTextColor, forKey: .errorTextColor)
try container.encodeIfPresent(errorMessage, forKey: .errorMessage)
try container.encode(enabled, forKey: .enabled)
try container.encode(hideBorders, forKey: .hideBorders)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(enabled, forKey: .enabled)
try container.encode(hideBorders, forKey: .hideBorders)
}
}

View File

@ -31,7 +31,7 @@ import MVMCore
/// If you're using a MFViewController, you must set this to it
public override weak var uiTextFieldDelegate: UITextFieldDelegate? {
get { return textField.delegate }
get { textField.delegate }
set {
textField.delegate = self
proprietorTextDelegate = newValue
@ -44,7 +44,7 @@ import MVMCore
/// Formats the MDN when setting and removes format of MDN when reading.
public var mdn: String? {
get { return MVMCoreUIUtility.removeMdnFormat(text) }
get { MVMCoreUIUtility.removeMdnFormat(text) }
set { text = MVMCoreUIUtility.formatMdn(newValue) }
}
@ -160,9 +160,9 @@ import MVMCore
// Sometimes user add extra 1 in front of mdn in their address book
if isNationalMDN,
let unformedMDN = unformattedMDN,
unformedMDN.count == 11,
unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" {
let unformedMDN = unformattedMDN,
unformedMDN.count == 11,
unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" {
let startIndex = unformedMDN.index(unformedMDN.startIndex, offsetBy: 1)
unformattedMDN = String(unformedMDN[startIndex...])
@ -211,17 +211,14 @@ import MVMCore
}
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
}
@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
}
@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
}
}

View File

@ -11,7 +11,5 @@
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String {
return "mdnEntryField"
}
public override class var identifier: String { "mdnEntryField" }
}

View File

@ -54,16 +54,14 @@ import UIKit
/// Validate when user resigns editing. Default: true
public var validateWhenDoneEditing: Bool = true
public var textEntryFieldModel: TextEntryFieldModel? {
return model as? TextEntryFieldModel
}
public var textEntryFieldModel: TextEntryFieldModel? { model as? TextEntryFieldModel }
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
public override var isEnabled: Bool {
get { return super.isEnabled }
get { super.isEnabled }
set (enabled) {
super.isEnabled = enabled
@ -77,7 +75,7 @@ import UIKit
}
public override var showError: Bool {
get { return super.showError }
get { super.showError }
set (error) {
if error {
@ -96,16 +94,16 @@ import UIKit
/// The text of this TextField.
open override var text: String? {
get { return textField.text }
get { textField.text }
set {
textField.text = newValue
textEntryFieldModel?.text = newValue
textField.text = newValue
}
}
/// Placeholder access for the TextField.
public var placeholder: String? {
get { return textField.placeholder }
get { textField.placeholder }
set { textField.placeholder = newValue }
}
@ -133,7 +131,7 @@ import UIKit
/// If you're using a ViewController, you must set this to it
public weak var uiTextFieldDelegate: UITextFieldDelegate? {
get { return textField.delegate }
get { textField.delegate }
set { textField.delegate = newValue }
}
@ -222,9 +220,7 @@ import UIKit
@discardableResult
@objc override open func resignFirstResponder() -> Bool {
if validateWhenDoneEditing {
validateText()
}
if validateWhenDoneEditing { validateText() }
textField.resignFirstResponder()
isSelected = false
return true
@ -239,6 +235,11 @@ import UIKit
/// Executes on UITextField.textDidBeginEditingNotification
@objc override func startEditing() {
super.startEditing()
if textEntryFieldModel?.clearTextOnTap ?? false {
text = ""
}
textField.becomeFirstResponder()
}
@ -257,14 +258,33 @@ import UIKit
showError = false
return
}
if let isValid = textEntryFieldModel?.isValid {
self.isValid = isValid
}
regexTextFieldOutputIfAvailable()
shouldShowError(!isValid)
}
func regexTextFieldOutputIfAvailable() {
if let regex = textEntryFieldModel?.displayFormat,
let mask = textEntryFieldModel?.displayMask,
let finalText = text {
let range = NSRange(finalText.startIndex..., in: finalText)
if let regex = try? NSRegularExpression(pattern: regex) {
let maskedText = regex.stringByReplacingMatches(in: finalText,
range: range,
withTemplate: mask)
textField.text = maskedText
}
}
}
@objc public func dismissFieldInput(_ sender: Any?) {
resignFirstResponder()
}
@ -317,6 +337,10 @@ import UIKit
case .password, .secure:
textField.isSecureTextEntry = true
case .numberSecure:
textField.isSecureTextEntry = true
textField.keyboardType = .numberPad
case .number:
textField.keyboardType = .numberPad
@ -326,17 +350,20 @@ import UIKit
case .phone:
textField.keyboardType = .phonePad
default:
break
default: break
}
// Override the preset keyboard set in type.
if let keyboardType = model.assignKeyboardType() {
textField.keyboardType = keyboardType
}
textField.accessibilityIdentifier = model.accessibilityIdentifier
uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
setupTextFieldToolbar()
if isSelected {
startEditing()
}
if isSelected { startEditing() }
}
}

View File

@ -16,6 +16,7 @@
case password
case secure
case number
case numberSecure
case email
case text
case phone
@ -25,15 +26,69 @@
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String {
return "textField"
}
public override class var identifier: String { "textField" }
public var placeholder: String?
public var enabledTextColor: Color = Color(uiColor: .mvmBlack)
public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3)
public var textAlignment: NSTextAlignment = .left
public var keyboardOverride: String?
public var type: EntryType?
public var clearTextOnTap: Bool = false
public var displayFormat: String?
public var displayMask: String?
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Reads the keyboardOverride set by server and returns the keyboard type associated with it.
func assignKeyboardType() -> UIKeyboardType? {
guard let keyboardType = keyboardOverride else { return nil }
var typeInt = 0
switch keyboardType {
case "asciiCapable":
typeInt = 1 // Displays a keyboard which can enter ASCII characters
case "numbersAndPunctuation":
typeInt = 2 // Numbers and assorted punctuation.
case "URL":
typeInt = 3 // A type optimized for URL entry (shows . / .com prominently).
case "numberPad":
typeInt = 4 // A number pad with locale-appropriate digits (0-9, ۰-۹, -, etc.). Suitable for PIN entry.
case "phonePad":
typeInt = 5 // A phone pad (1-9, *, 0, #, with letters under the numbers).
case "namePhonePad":
typeInt = 6 // A type optimized for entering a person's name or phone number.
case "emailAddress":
typeInt = 7 // A type optimized for multiple email address entry (shows space @ . prominently).
case "decimalPad":
typeInt = 8 // A number pad with a decimal point.
case "twitter":
typeInt = 9 // A type optimized for twitter text entry (easy access to @ #)
case "webSearch":
typeInt = 10 // A default keyboard type with URL-oriented addition (shows space . prominently).
case "asciiCapableNumberPad":
typeInt = 11 // A number pad (0-9) that will always be ASCII digits.
default:
typeInt = 0 // Default type for the current input method.
}
return UIKeyboardType(rawValue: typeInt)
}
//--------------------------------------------------
// MARK: - Keys
@ -44,7 +99,11 @@
case textAlignment
case enabledTextColor
case disabledTextColor
case keyboardOverride
case type
case clearTextOnTap
case displayFormat
case displayMask
}
//--------------------------------------------------
@ -56,8 +115,15 @@
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
placeholder = try typeContainer.decodeIfPresent(String.self, forKey: .placeholder)
displayFormat = try typeContainer.decodeIfPresent(String.self, forKey: .displayFormat)
keyboardOverride = try typeContainer.decodeIfPresent(String.self, forKey: .keyboardOverride)
displayMask = try typeContainer.decodeIfPresent(String.self, forKey: .displayMask)
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type)
if let clearTextOnTap = try typeContainer.decodeIfPresent(Bool.self, forKey: .clearTextOnTap) {
self.clearTextOnTap = clearTextOnTap
}
if let enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledTextColor) {
self.enabledTextColor = enabledTextColor
}
@ -76,8 +142,12 @@
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(placeholder, forKey: .placeholder)
try container.encodeIfPresent(textAlignment, forKey: .textAlignment)
try container.encodeIfPresent(type, forKey: .type)
try container.encodeIfPresent(displayFormat, forKey: .displayFormat)
try container.encodeIfPresent(keyboardOverride, forKey: .keyboardOverride)
try container.encodeIfPresent(displayMask, forKey: .displayMask)
try container.encode(enabledTextColor, forKey: .enabledTextColor)
try container.encode(disabledTextColor, forKey: .disabledTextColor)
try container.encodeIfPresent(type, forKey: .type)
try container.encode(clearTextOnTap, forKey: .clearTextOnTap)
}
}

View File

@ -31,11 +31,11 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
//--------------------------------------------------
public var textViewEntryFieldModel: TextViewEntryFieldModel? {
return model as? TextViewEntryFieldModel
model as? TextViewEntryFieldModel
}
public override var isEnabled: Bool {
get { return super.isEnabled }
get { super.isEnabled }
set (enabled) {
super.isEnabled = enabled
@ -53,7 +53,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
}
public override var showError: Bool {
get { return super.showError }
get { super.showError }
set (error) {
if error {
@ -68,7 +68,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
/// The text of this textView.
open override var text: String? {
get { return textViewEntryFieldModel?.text }
get { textViewEntryFieldModel?.text }
set {
textView.text = newValue
textViewEntryFieldModel?.text = newValue
@ -77,7 +77,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
/// Placeholder access for the textView.
public var placeholder: String? {
get { return textViewEntryFieldModel?.placeholder }
get { textViewEntryFieldModel?.placeholder }
set {
textView.placeholder = newValue ?? ""
textViewEntryFieldModel?.placeholder = newValue
@ -127,7 +127,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
/// If you're using a ViewController, you must set this to it
public weak var uiTextViewDelegate: UITextViewDelegate? {
get { return textView.delegate }
get { textView.delegate }
set { textView.delegate = newValue }
}
@ -149,17 +149,17 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
@objc open override func setupFieldContainerContent(_ container: UIView) {
container.addSubview(textView)
topConstraint = textView.topAnchor.constraint(equalTo: container.topAnchor, constant: Padding.Three)
leadingConstraint = textView.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: Padding.Three)
trailingConstraint = container.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: Padding.Three)
bottomConstraint = container.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: Padding.Three)
topConstraint?.isActive = true
leadingConstraint?.isActive = true
trailingConstraint?.isActive = true
bottomConstraint?.isActive = true
heightConstraint = textView.heightAnchor.constraint(equalToConstant: 0)
accessibilityElements = [titleLabel, textView, feedbackLabel]
}
@ -203,7 +203,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
/// Executes on UITextView.textDidEndEditingNotification
@objc override func endInputing() {
super.endInputing()
// Don't show error till user starts typing.
guard text?.count ?? 0 != 0 else {
showError = false
@ -241,6 +241,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
textView.isEditable = model.editable
textView.textAlignment = model.textAlignment
textView.accessibilityIdentifier = model.accessibilityIdentifier
textView.textColor = model.enabled ? model.enabledTextColor.uiColor : model.disabledTextColor.uiColor
textView.font = model.fontStyle.getFont()
textView.placeholder = model.placeholder ?? ""
@ -252,13 +253,17 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
case .secure, .password:
textView.isSecureTextEntry = true
case .numberSecure:
textView.isSecureTextEntry = true
textView.keyboardType = .numberPad
case .number:
textView.keyboardType = .numberPad
case .email:
textView.keyboardType = .emailAddress
default: break
default: break
}
/// No point in configuring if the TextView is Read-only.

View File

@ -14,9 +14,7 @@ class TextViewEntryFieldModel: TextEntryFieldModel {
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String {
return "textView"
}
public override class var identifier: String { "textView" }
public var accessibilityText: String?
public var fontStyle: Styler.Font = Styler.Font.RegularBodyLarge
@ -25,7 +23,7 @@ class TextViewEntryFieldModel: TextEntryFieldModel {
public var placeholderFontStyle: Styler.Font = Styler.Font.RegularMicro
public var editable: Bool = true
public var showsPlaceholder: Bool = false
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------

View File

@ -25,7 +25,7 @@ import MVMCore
var delegateObject: MVMCoreUIDelegateObject?
public var checkboxModel: CheckboxModel? {
return model as? CheckboxModel
model as? CheckboxModel
}
public static let defaultHeightWidth: CGFloat = 18.0
@ -59,7 +59,7 @@ import MVMCore
/// Retrieves ideeal radius value to curve square into a circle.
public var cornerRadiusValue: CGFloat {
return bounds.size.height / 2
bounds.size.height / 2
}
/// Action Block called when the switch is selected.
@ -102,23 +102,17 @@ import MVMCore
/// Color of the check mark.
public var checkColor: UIColor = .mvmBlack {
didSet {
setShapeLayerStrokeColor(checkColor)
}
didSet { setShapeLayerStrokeColor(checkColor) }
}
/// Border width of the checkbox
public var borderWidth: CGFloat = 1 {
didSet {
layer.borderWidth = borderWidth
}
didSet { layer.borderWidth = borderWidth }
}
/// border color of the Checkbox
public var borderColor: UIColor = .mvmBlack {
didSet {
layer.borderColor = borderColor.cgColor
}
didSet { layer.borderColor = borderColor.cgColor }
}
/**
@ -359,6 +353,7 @@ import MVMCore
}
override open func accessibilityActivate() -> Bool {
guard isEnabled else { return false }
sendActions(for: .touchUpInside)
return true
}
@ -367,9 +362,7 @@ import MVMCore
// MARK: - Molecular
//--------------------------------------------------
open func needsToBeConstrained() -> Bool {
return true
}
open func needsToBeConstrained() -> Bool { true }
open override func reset() {
super.reset()
@ -396,11 +389,9 @@ import MVMCore
}
private func performCheckboxAction(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
if let actionMap = actionModel.toJSON() {
var additionalDatatoUpdate = additionalData ?? [:]
additionalDatatoUpdate[KeySourceModel] = checkboxModel
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDatatoUpdate, delegateObject: delegateObject)
}
var additionalDataToUpdate = additionalData ?? [:]
additionalDataToUpdate[KeySourceModel] = checkboxModel
MVMCoreActionHandler.shared()?.asyncHandleAction(with: actionModel, additionalData: additionalDataToUpdate, delegateObject: delegateObject)
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {

View File

@ -16,6 +16,7 @@ import Foundation
public static var identifier: String = "checkbox"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var checked: Bool = false
public var enabled: Bool = true
public var animated: Bool = true
@ -44,6 +45,7 @@ import Foundation
private enum CodingKeys: String, CodingKey {
case moleculeName
case accessibilityIdentifier
case checked
case enabled
case inverted
@ -69,9 +71,7 @@ import Foundation
// MARK: - Methods
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
return checked
}
public func formFieldValue() -> AnyHashable? { checked }
//--------------------------------------------------
// MARK: - Initializer
@ -89,6 +89,8 @@ import Foundation
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let borderWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .borderWidth) {
self.borderWidth = borderWidth
}
@ -169,6 +171,7 @@ import Foundation
try container.encode(borderWidth, forKey: .borderWidth)
try container.encode(checked, forKey: .checked)
try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(checkColor, forKey: .checkColor)
try container.encodeIfPresent(invertedColor, forKey: .invertedColor)
try container.encodeIfPresent(invertedBackgroundColor, forKey: .invertedBackgroundColor)

View File

@ -71,6 +71,7 @@ import UIKit
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func setupView() {
super.setupView()
addTarget(self, action: #selector(tapAction), for: .touchUpInside)

View File

@ -6,15 +6,16 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "heart"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var isActive: Bool = false
public var activeColor: Color = Color(uiColor: .mvmRed)
public var inActiveColor: Color = Color(uiColor: .clear)
@ -24,9 +25,11 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case isActive
case activeColor
case inActiveColor
@ -43,13 +46,18 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
if let isActive = try typeContainer.decodeIfPresent(Bool.self, forKey: .isActive) {
self.isActive = isActive
}
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let activeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .activeColor) {
self.activeColor = activeColor
}
if let inActiveColor = try typeContainer.decodeIfPresent(Color.self, forKey: .inActiveColor) {
self.inActiveColor = inActiveColor
}
if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) {
self.action = action
}
@ -61,6 +69,7 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(isActive, forKey: .isActive)
try container.encode(activeColor, forKey: .activeColor)

View File

@ -6,9 +6,12 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class RadioBox: Control, MFButtonProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public let label = Label(fontStyle: .RegularBodySmall)
public let subTextLabel = Label(fontStyle: .RegularMicro)
public var isOutOfStock = false
@ -26,22 +29,20 @@ open class RadioBox: Control, MFButtonProtocol {
var additionalData: [AnyHashable: Any]?
public var radioBoxModel: RadioBoxModel? {
return model as? RadioBoxModel
model as? RadioBoxModel
}
public override var isSelected: Bool {
didSet {
updateAccessibility()
}
didSet { updateAccessibility() }
}
public override var isEnabled: Bool {
didSet {
updateAccessibility()
}
didSet { updateAccessibility() }
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open override func updateView(_ size: CGFloat) {
super.updateView(size)
@ -75,8 +76,6 @@ open class RadioBox: Control, MFButtonProtocol {
isAccessibilityElement = true
}
// MARK: - MoleculeViewProtocol
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? RadioBoxModel else { return }
@ -99,7 +98,9 @@ open class RadioBox: Control, MFButtonProtocol {
accentColor = .mvmRed
}
//--------------------------------------------------
// MARK: - State Handling
//--------------------------------------------------
open override func draw(_ layer: CALayer, in ctx: CGContext) {
// Draw the strikethrough
@ -213,21 +214,29 @@ open class RadioBox: Control, MFButtonProtocol {
return mask
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
public func updateAccessibility() {
var message = ""
if let labelText = label.text, label.hasText {
message += labelText + ", "
}
if let subLabelText = subTextLabel.text, subTextLabel.hasText {
message += subLabelText + ", "
}
accessibilityLabel = message
accessibilityLabel = message
accessibilityTraits = .button
if isSelected {
accessibilityTraits.insert(.selected)
}
if !isEnabled {
accessibilityTraits.insert(.notEnabled)
}

View File

@ -6,10 +6,17 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class RadioBoxCollectionViewCell: CollectionViewCell {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
public let radioBox = RadioBox()
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func reset() {
super.reset()
backgroundColor = .clear

View File

@ -6,12 +6,16 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class RadioBoxModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioBox"
public var text: String
public var subText: String?
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var selectedAccentColor: Color?
public var selected: Bool = false
public var enabled: Bool = true
@ -19,12 +23,17 @@ import Foundation
public var fieldValue: String?
public var action: ActionModelProtocol?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case text
case subText
case selectedAccentColor
case backgroundColor
case accessibilityIdentifier
case selected
case enabled
case strikethrough
@ -32,18 +41,26 @@ import Foundation
case action
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
text = try typeContainer.decode(String.self, forKey: .text)
subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText)
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
selected = isSelected
}
if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
enabled = isEnabled
}
if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) {
strikethrough = isStrikeTrough
}
@ -59,6 +76,7 @@ import Foundation
try container.encodeIfPresent(subText, forKey: .subText)
try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(selected, forKey: .selected)
try container.encode(enabled, forKey: .enabled)
try container.encode(strikethrough, forKey: .strikethrough)

View File

@ -57,11 +57,12 @@ open class RadioBoxes: View {
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
guard let radioBoxesModel = model as? RadioBoxesModel else { return }
boxes = radioBoxesModel.boxes
FormValidator.setupValidation(for: radioBoxesModel, delegate: delegateObject?.formHolderDelegate)
guard let model = model as? RadioBoxesModel else { return }
boxes = model.boxes
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
backgroundColor = radioBoxesModel.backgroundColor?.uiColor
backgroundColor = model.backgroundColor?.uiColor
registerCells()
setHeight()
collectionView.reloadData()
@ -168,4 +169,3 @@ extension RadioBoxes: UICollectionViewDelegate {
cell.updateAccessibility()
}
}

View File

@ -6,17 +6,25 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioBoxes"
public var boxes: [RadioBoxModel]
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var selectedAccentColor: Color?
public var boxesColor: Color?
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Returns the fieldValue of the selected box, otherwise the text of the selected box.
public func formFieldValue() -> AnyHashable? {
let selectedBox = boxes.first { (box) -> Bool in
@ -25,20 +33,30 @@ import Foundation
return selectedBox?.fieldValue ?? selectedBox?.text
}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case selectedAccentColor
case backgroundColor
case accessibilityIdentifier
case boxesColor
case boxes
case fieldKey
case groupName
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
boxesColor = try typeContainer.decodeIfPresent(Color.self, forKey: .boxesColor)
boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes)
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
@ -54,6 +72,7 @@ import Foundation
try container.encode(boxes, forKey: .boxes)
try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encode(groupName, forKey: .groupName)
}

View File

@ -15,9 +15,7 @@ import UIKit
//--------------------------------------------------
public var diameter: CGFloat = 30 {
didSet {
widthConstraint?.constant = diameter
}
didSet { widthConstraint?.constant = diameter }
}
@objc public override var isSelected: Bool {
@ -33,12 +31,10 @@ import UIKit
var additionalData: [AnyHashable: Any]?
public var radioModel: RadioButtonModel? {
return model as? RadioButtonModel
model as? RadioButtonModel
}
lazy public var radioGroupName: String? = {
return radioModel?.fieldKey
}()
lazy public var radioGroupName: String? = { radioModel?.fieldKey }()
lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = {
@ -95,33 +91,34 @@ import UIKit
if !isEnabled {
return
}
let wasPreviouslySelected = isSelected
if let radioButtonModel = radioButtonSelectionHelper {
radioButtonModel.selected(self)
} else {
isSelected = !isSelected
}
if let radioModel = radioModel, let actionModel = radioModel.action, isSelected, !wasPreviouslySelected {
Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioModel)
}
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
setNeedsDisplay()
}
public func isValidField() -> Bool {
return isSelected
}
public func isValidField() -> Bool { isSelected }
public func formFieldName() -> String? {
return radioModel?.fieldKey
radioModel?.fieldKey
}
public func formFieldGroupName() -> String? {
return radioModel?.fieldKey
radioModel?.fieldKey
}
public func formFieldValue() -> AnyHashable? {
return radioModel?.fieldValue
radioModel?.fieldValue
}
//--------------------------------------------------

View File

@ -6,7 +6,6 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
import MVMCore
@ -17,6 +16,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
public static var identifier: String = "radioButton"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var state: Bool = false
public var enabled: Bool = true
@ -35,6 +35,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case state
case enabled
case fieldValue
@ -56,9 +57,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
// MARK: - Validation
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
return fieldValue
}
public func formFieldValue() -> AnyHashable? { fieldValue }
//--------------------------------------------------
// MARK: - Codec
@ -76,6 +75,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
}
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
baseValue = state
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
@ -89,6 +89,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(state, forKey: .state)
try container.encode(enabled, forKey: .enabled)

View File

@ -6,8 +6,6 @@
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class RadioButtonSelectionHelper: FormFieldProtocol {
//--------------------------------------------------
@ -77,7 +75,5 @@ import Foundation
// MARK: - FormValidationFormFieldProtocol
extension RadioButtonSelectionHelper {
public func formFieldValue() -> AnyHashable? {
return selectedRadioButtonModel?.fieldValue
}
public func formFieldValue() -> AnyHashable? { selectedRadioButtonModel?.fieldValue }
}

View File

@ -77,6 +77,7 @@ open class RadioSwatch: Control, MFButtonProtocol {
//------------------------------------------------------
// MARK: - State Handling
//------------------------------------------------------
open override func draw(_ layer: CALayer, in ctx: CGContext) {
//Draw the swatch
circleLayer?.removeFromSuperlayer()

View File

@ -6,7 +6,7 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class RadioSwatchCollectionViewCell: CollectionViewCell {
public let radioSwatch = RadioSwatch()

View File

@ -6,11 +6,15 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class RadioSwatchModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioSwatch"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var color: Color = Color(uiColor: .mvmBlue)
public var text: String?
public var selected: Bool = false
@ -19,9 +23,14 @@ import Foundation
public var fieldValue: String?
public var action: ActionModelProtocol?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case color
case text
case selected
@ -31,22 +40,33 @@ import Foundation
case action
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) {
self.color = color
}
text = try typeContainer.decodeIfPresent(String.self, forKey: .text)
if let selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
self.selected = selected
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
if let strikethrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) {
self.strikethrough = strikethrough
}
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
}
@ -55,6 +75,7 @@ import Foundation
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(color, forKey: .color)
try container.encodeIfPresent(text, forKey: .text)
try container.encode(selected, forKey: .selected)
@ -64,5 +85,3 @@ import Foundation
try container.encodeModelIfPresent(action, forKey: .action)
}
}

View File

@ -55,9 +55,9 @@ open class RadioSwatches: View {
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
guard let radioSwatchesModel = model as? RadioSwatchesModel else { return }
swatches = radioSwatchesModel.swatches
FormValidator.setupValidation(for: radioSwatchesModel, delegate: delegateObject?.formHolderDelegate)
guard let model = model as? RadioSwatchesModel else { return }
swatches = model.swatches
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
collectionView.reloadData()
}

View File

@ -6,16 +6,24 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class RadioSwatchesModel: MoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioSwatches"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var swatches: [RadioSwatchModel]
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Returns the fieldValue of the selected swatch, otherwise the text of selected swatch.
public func formFieldValue() -> AnyHashable? {
let selectedSwatch = swatches.first { (swatch) -> Bool in
@ -24,17 +32,27 @@ import Foundation
return selectedSwatch?.fieldValue ?? selectedSwatch?.text
}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case swatches
case fieldKey
case groupName
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
swatches = try typeContainer.decode([RadioSwatchModel].self, forKey: .swatches)
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
@ -47,6 +65,7 @@ import Foundation
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(swatches, forKey: .swatches)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encode(groupName, forKey: .groupName)

View File

@ -40,7 +40,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
/// Executes logic before state change. If false, then toggle state will not change and the didToggleAction will not execute.
public var shouldToggleAction: ActionBlockConfirmation? = {
return { return true }
return { true }
}()
// Sizes are from InVision design specs.
@ -69,9 +69,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
/// Simple means to prevent user interaction with the toggle.
public var isLocked: Bool = false {
didSet {
isUserInteractionEnabled = !isLocked
}
didSet { isUserInteractionEnabled = !isLocked }
}
/// The state on the toggle. Default value: false.
@ -109,7 +107,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
}
public var toggleModel: ToggleModel? {
return model as? ToggleModel
model as? ToggleModel
}
//--------------------------------------------------
@ -392,21 +390,17 @@ public typealias ActionBlockConfirmation = () -> (Bool)
accessibilityLabel = accessibileString
}
let actionMap = model.action?.toJSON()
let alternateActionMap = model.alternateAction?.toJSON()
let additionalDataWithSource = additionalData.dictionaryAdding(key: KeySourceModel, value: model)
if actionMap != nil || alternateActionMap != nil {
if model.action != nil || model.alternateAction != nil {
didToggleAction = { [weak self] in
guard let self = self else { return }
if self.isOn {
if actionMap != nil {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject)
if let action = model.action {
MVMCoreActionHandler.shared()?.asyncHandleAction(with: action, additionalData: additionalDataWithSource, delegateObject: delegateObject)
}
} else {
if alternateActionMap != nil {
MVMCoreActionHandler.shared()?.handleAction(with: alternateActionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject)
} else if actionMap != nil {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalDataWithSource, delegateObject: delegateObject)
if let action = model.alternateAction ?? model.action {
MVMCoreActionHandler.shared()?.asyncHandleAction(with: action, additionalData: additionalDataWithSource, delegateObject: delegateObject)
}
}
}
@ -414,18 +408,14 @@ public typealias ActionBlockConfirmation = () -> (Bool)
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return Self.getContainerHeight()
Self.getContainerHeight()
}
}
// MARK: - MVMCoreUIViewConstrainingProtocol
extension Toggle {
public func needsToBeConstrained() -> Bool {
return true
}
public func needsToBeConstrained() -> Bool { true }
public func horizontalAlignment() -> UIStackView.Alignment {
return .trailing
}
public func horizontalAlignment() -> UIStackView.Alignment { .trailing }
}

View File

@ -6,8 +6,6 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableModelProtocol {
//--------------------------------------------------
@ -15,6 +13,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo
//--------------------------------------------------
public static var identifier: String = "toggle"
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var state: Bool = false
public var animated: Bool = true
@ -42,6 +41,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo
case enabled
case action
case backgroundColor
case accessibilityIdentifier
case alternateAction
case accessibilityText
case onTintColor
@ -56,9 +56,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo
// MARK: - Methods
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
return state
}
public func formFieldValue() -> AnyHashable? { state }
//--------------------------------------------------
// MARK: - Initializer
@ -91,6 +89,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let onTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onTintColor) {
self.onTintColor = onTintColor
@ -120,6 +119,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeModelIfPresent(action, forKey: .action)
try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction)
try container.encode(moleculeName, forKey: .moleculeName)

View File

@ -16,6 +16,7 @@ import Foundation
public static var identifier: String = "dashLine"
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var dashColor: Color = Color(uiColor: .mvmCoolGray3)
public var dashColor_inverted: Color = Color(uiColor: .mvmWhite)
public var isHidden: Bool = false
@ -36,6 +37,7 @@ import Foundation
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case dashColor_inverted
case dashColor
case isHidden
@ -57,6 +59,7 @@ import Foundation
}
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let isHidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .isHidden) {
self.isHidden = isHidden
@ -69,5 +72,6 @@ import Foundation
try container.encode(dashColor, forKey: .dashColor)
try container.encode(isHidden, forKey: .isHidden)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
}
}

View File

@ -38,7 +38,7 @@ public typealias ActionBlock = () -> ()
}
public var getRange: NSRange {
return NSRange(location: 0, length: text?.count ?? 0)
NSRange(location: 0, length: text?.count ?? 0)
}
//------------------------------------------------------
@ -209,7 +209,7 @@ public typealias ActionBlock = () -> ()
/// Default
@objc open class func label() -> Label {
return Label(frame: .zero)
Label(frame: .zero)
}
//------------------------------------------------------
@ -381,9 +381,7 @@ public typealias ActionBlock = () -> ()
}
case let actionAtt as LabelAttributeActionModel:
addTappableLinkAttribute(range: NSRange(location: range.location, length: range.length)) {
if let data = try? actionAtt.action.encode(using: JSONEncoder()), let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any] {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
}
MVMCoreActionHandler.shared()?.asyncHandleAction(with: actionAtt.action, additionalData: additionalData, delegateObject: delegateObject)
}
addActionAttributes(range: range, string: attributedString)
@ -794,17 +792,11 @@ extension Label {
accessibilityTraits = .staticText
}
public func needsToBeConstrained() -> Bool {
return true
}
public func needsToBeConstrained() -> Bool { true }
public func horizontalAlignment() -> UIStackView.Alignment {
return .leading
}
public func horizontalAlignment() -> UIStackView.Alignment { .leading }
public func copyBackgroundColor() -> Bool {
return true
}
public func copyBackgroundColor() -> Bool { true }
}
// MARK: - Multi-Link Functionality
@ -916,6 +908,7 @@ extension UITapGestureRecognizer {
if label.makeWholeViewClickable {
return true
}
guard let abstractContainer = label.abstractTextContainer() else { return false }
let textContainer = abstractContainer.0
let layoutManager = abstractContainer.1
@ -990,6 +983,7 @@ extension Label {
}
}
}
return false
}
}

View File

@ -12,9 +12,7 @@ open class LabelAttributeActionModel: LabelAttributeModel {
// MARK: - Properties
//--------------------------------------------------
override public class var identifier: String {
return "action"
}
override public class var identifier: String { "action" }
var action: ActionModelProtocol

View File

@ -12,9 +12,7 @@
// MARK: - Properties
//--------------------------------------------------
override public class var identifier: String {
return "color"
}
override public class var identifier: String { "color" }
var textColor: Color?

View File

@ -12,9 +12,7 @@
// MARK: - Properties
//--------------------------------------------------
override public class var identifier: String {
return "font"
}
override public class var identifier: String { "font" }
var style: Styler.Font?
var name: String?

View File

@ -12,9 +12,7 @@ class LabelAttributeImageModel: LabelAttributeModel {
// MARK: - Properties
//--------------------------------------------------
override public class var identifier: String {
return "image"
}
override public class var identifier: String { "image" }
var size: CGFloat?
var name: String?

View File

@ -12,20 +12,14 @@
// MARK: - Properties
//--------------------------------------------------
public static var categoryName: String {
return "\(LabelAttributeModel.self)"
}
public static var categoryName: String { "\(LabelAttributeModel.self)" }
public static var categoryCodingKey: String {
return "type"
}
public static var categoryCodingKey: String { "type" }
public class var identifier: String {
return ""
}
public class var identifier: String { "" }
var type: String {
get { return Self.identifier }
get { Self.identifier }
}
var location: Int

View File

@ -12,9 +12,7 @@
// MARK: - Properties
//--------------------------------------------------
override public class var identifier: String {
return "strikethrough"
}
override public class var identifier: String { "strikethrough" }
//--------------------------------------------------
// MARK: - Initializer

View File

@ -14,9 +14,7 @@ import UIKit
// MARK: - Properties
//--------------------------------------------------
override public class var identifier: String {
return "underline"
}
override public class var identifier: String { "underline" }
/// This returns the NSUnderlineStyle used in NSAttributedValue. If there is a pattern, it will return
/// a new NSUnderlineStyle derived from the bitmask of style | pattern.

View File

@ -21,7 +21,8 @@
public var addSizeConstraintsForAspectRatio = true
public var shouldNotifyDelegateOnUpdate = true
public var shouldNotifyDelegateOnDefaultSizeChange = false
// Allows for a view to hardcode which height to use if there is none in the json.
var imageWidth: CGFloat?
var imageHeight: CGFloat?
@ -228,13 +229,13 @@
let widthWillChange = !MVMCoreGetterUtility.cgfequal(widthConstraint?.constant ?? 0, width ?? 0)
let heightWillChange = !MVMCoreGetterUtility.cgfequal(heightConstraint?.constant ?? 0, height ?? 0)
let sizeWillChange = (width == nil || height == nil) && !(size?.equalTo(imageView.image?.size ?? CGSize.zero) ?? false)
let sizeWillChange = shouldNotifyDelegateOnDefaultSizeChange && (width == nil || height == nil) && !(size?.equalTo(imageView.image?.size ?? CGSize.zero) ?? false)
let heightChangeFromSpinner = (heightConstraint?.isActive ?? false) ? false : ((height ?? size?.height) ?? 0) < loadingSpinnerHeightConstraint?.constant ?? CGFloat.leastNormalMagnitude
return widthWillChange || heightWillChange || sizeWillChange || heightChangeFromSpinner
}
// Constrains the image view to be the size provided. Used to size it to the image to fix aspect fit defect.
func addConstraints(width: NSNumber?, height: NSNumber?, size: CGSize?) {
func addConstraints(width: CGFloat?, height: CGFloat?, size: CGSize?) {
widthConstraint?.isActive = false
heightConstraint?.isActive = false
@ -242,15 +243,15 @@
guard addSizeConstraintsForAspectRatio else { return }
if let width = width, let height = height {
setHeight(height.cgfloat())
setWidth(width.cgfloat())
setHeight(height)
setWidth(width)
} else if let width = width, let size = size {
setWidth(width.cgfloat())
setWidth(width)
heightConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: size.height/size.width)
heightConstraint?.priority = UILayoutPriority(rawValue: 900)
heightConstraint?.isActive = true
} else if let height = height, let size = size {
setHeight(height.cgfloat())
setHeight(height)
widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: size.width/size.height)
widthConstraint?.priority = UILayoutPriority(rawValue: 900)
widthConstraint?.isActive = true
@ -288,7 +289,7 @@
if shouldLoadImage(withName: imageModel.image, width: width, height: height) {
imageView.image = nil
imageView.animatedImage = nil
loadImage(withName: imageModel.image, format: imageModel.imageFormat, width: width as NSNumber?, height: height as NSNumber?, customFallbackImage: imageModel.fallbackImage, localBundle: imageModel.localBundle)
loadImage(withName: imageModel.image, format: imageModel.imageFormat, width: width, height: height, customFallbackImage: imageModel.fallbackImage, localBundle: imageModel.localBundle)
}
if let contentMode = imageModel.contentMode {
@ -309,13 +310,13 @@
// MARK: - Load Methods
//--------------------------------------------------
public func loadImage(withName imageName: String?, format: String? = nil, width: NSNumber? = nil, height: NSNumber? = nil, customFallbackImage: String? = nil, allowServerParameters: Bool = false, localBundle: Bundle? = nil, completionHandler: MVMCoreGetImageBlock? = nil) {
public func loadImage(withName imageName: String?, format: String? = nil, width: CGFloat? = nil, height: CGFloat? = nil, customFallbackImage: String? = nil, allowServerParameters: Bool = false, localBundle: Bundle? = nil, completionHandler: MVMCoreGetImageBlock? = nil) {
let completionBlock = completionHandler ?? defaultCompletionBlock()
MVMCoreDispatchUtility.performBlock(onMainThread: { [unowned self] in
self.currentImageName = imageName
self.currentImageWidth = width?.cgfloat()
self.currentImageHeight = height?.cgfloat()
self.currentImageWidth = width
self.currentImageHeight = height
if MVMCoreCache.isHostedImage(imageName) {
self.loadingSpinner.resumeSpinnerAfterDelay()
self.loadingSpinnerHeightConstraint?.constant = self.spinnerHeight
@ -338,9 +339,9 @@
let fallbackImageName = customFallbackImage ?? MVMCoreUIUtility.localizedImageName("fallback")
if let format = format, format.lowercased().contains("gif") {
// Gifs aren't supported by default and need special handling
MVMCoreCache.shared()?.getGif(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock)
MVMCoreCache.shared()?.getGif(imageName, useWidth: width != nil, widthForS7: Int(width ?? 0), useHeight: height != nil, heightForS7: Int(height ?? 0), format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock)
} else {
MVMCoreCache.shared()?.getImage(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock)
MVMCoreCache.shared()?.getImage(imageName, useWidth: width != nil, widthForS7: Int(width ?? 0), useHeight: height != nil, heightForS7: Int(height ?? 0), format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock)
}
})
}
@ -409,6 +410,6 @@
}
public func loadImage(withName imageName: String?, format: String?, width: NSNumber?, height: NSNumber?, customFallbackImage: String?, completionHandler: @escaping MVMCoreGetImageBlock) {
loadImage(withName: imageName, format: format, width: width, height: height, customFallbackImage: customFallbackImage, allowServerParameters: false, completionHandler: completionHandler)
loadImage(withName: imageName, format: format, width: width?.cgfloat(), height: height?.cgfloat(), customFallbackImage: customFallbackImage, allowServerParameters: false, completionHandler: completionHandler)
}
}

View File

@ -17,7 +17,7 @@ import UIKit
private let stack = Stack<StackModel>()
var multiProgressModel: MultiProgressBarModel? {
get { return model as? MultiProgressBarModel }
get { model as? MultiProgressBarModel }
}
var roundedCorners: Bool = false {
@ -85,7 +85,7 @@ import UIKit
//--------------------------------------------------
/// Creates the bars
open func set(with progressList: Array<SingleProgressBarModel>, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
open func set(with progressList: [SingleProgressBarModel], _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
stack.removeAllItemViews()
guard let stackModel = stack.stackModel else { return }
@ -103,7 +103,6 @@ import UIKit
stack.set(with: stackModel, delegateObject, additionalData)
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
@ -115,6 +114,6 @@ import UIKit
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return (model as? MultiProgressBarModel)?.thickness ?? 8
(model as? MultiProgressBarModel)?.thickness ?? 8
}
}

View File

@ -0,0 +1,108 @@
//
// Video.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 1/26/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import AVKit
open class Video: View {
public let videoViewController = AVPlayerViewController()
private weak var containingView: UIView?
/// Used to track the state and respond..
private var stateKVOToken: NSKeyValueObservation?
open override func setupView() {
super.setupView()
videoViewController.view.translatesAutoresizingMaskIntoConstraints = false
addSubview(videoViewController.view)
NSLayoutConstraint.constraintPinSubview(toSuperview: videoViewController.view)
videoViewController.videoGravity = .resizeAspectFill
}
/// Checks if the video is visible in the molecule delegate
open func isVisibleInDelegate() -> Bool {
guard let containingView = containingView else { return true }
return isVisible(in: containingView)
}
/// Checks if the video is visible in the passed in view
open func isVisible(in view: UIView) -> Bool {
return MVMCoreUIUtility.isView(self, visibleIn: view)
}
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
/// Detach the view from it's previous model before setting.
(self.model as? VideoModel)?.view = nil
containingView = (delegateObject?.moleculeDelegate as? UIViewController)?.view
super.set(with: model, delegateObject, additionalData)
guard let model = model as? VideoModel else { return }
if let controller = delegateObject?.moleculeDelegate as? UIViewController {
controller.addChild(videoViewController)
videoViewController.didMove(toParent: controller)
}
videoViewController.showsPlaybackControls = model.showControls
videoViewController.player = model.videoDataManager.player
addStateObserver()
model.addVisibilityHalting(for: self, delegateObject: delegateObject)
switch (model.videoDataManager.videoState) {
case .none:
// Begin loading the video
model.videoDataManager.loadVideo()
case .loaded:
guard isVisibleInDelegate() else { return }
// Video loaded, unhalt it if necessary.
model.halted = false
default:
break
}
}
/// Listens and responds to video loading state changes.
private func addStateObserver() {
removeStateObserver()
guard stateKVOToken == nil,
let model = model as? VideoModel else { return }
// To know when the video player item is done loading.
stateKVOToken =
model.videoDataManager.observe(\.videoState) { [weak self] (item, change) in
guard let self = self,
let model = self.model as? VideoModel,
item == model.videoDataManager else { return }
switch item.videoState {
case .loaded:
// Setting videoController's player must be in the main thread
MVMCoreDispatchUtility.performSyncBlock(onMainThread: {
// Play the video
self.videoViewController.player = item.player
if !model.halted && model.autoPlay && self.isVisibleInDelegate() {
item.player?.play()
UIAccessibility.post(notification: .screenChanged, argument: self)
}
})
case .failed:
if let errorObject = item.loadFailedError {
MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject)
}
default:
break
}
}
}
private func removeStateObserver() {
stateKVOToken?.invalidate()
stateKVOToken = nil
}
deinit {
removeStateObserver()
}
}

View File

@ -0,0 +1,141 @@
//
// VideoDataManager.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 1/26/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import Foundation
import AVFoundation
@objcMembers open class VideoDataManager: NSObject {
/// The state of the video.
@objc public enum VideoState: Int {
case none
case loading
case loaded
case failed
}
public let videoURLString: String
public var player: AVPlayer?
// Thread Safe video state handling.
private var _videoState = VideoState.none
private let videoStatueQueue = DispatchQueue(label: "com.vzw.mvmcoreui.videoDataManager.state", attributes: .concurrent)
/// The state of the video. Use KVO to listen for state changes.
@objc public var videoState: VideoState {
get {
var state = VideoState.none
videoStatueQueue.sync {
state = _videoState
}
return state
}
set {
willChangeValue(for: \.videoState)
videoStatueQueue.async(flags: .barrier) {
self._videoState = newValue
}
didChangeValue(for: \.videoState)
}
}
/// Set when the state is set to failed. Follows the same pattern as apple's AVPlayerItem
public var loadFailedError: MVMCoreErrorObject?
private var kvoToken: NSKeyValueObservation?
private var memoryWarningListener: Any?
public init(with videoURLString: String) {
self.videoURLString = videoURLString
super.init()
self.addMemoryWarningListener()
}
public func loadVideo() {
guard videoState != .loading else { return }
removeVideoObserver()
player = nil
videoState = .loading
//Asset loading needs time, calling async method. by tracking asset's propety "duration", if we get the value of duration, we can treat asset load successfully.
let tracksKey = "duration"
MVMCoreCache.shared()?.playerAsset(fromFileName: videoURLString, trackKeys: [tracksKey], onComplete: { [weak self] (asset, fileName, errorObject) in
guard let asset = asset else {
self?.loadFailedError = errorObject
self?.videoState = .failed
return
}
var error: NSError? = nil
let tracksStatus = asset.statusOfValue(forKey: tracksKey, error: &error)
switch tracksStatus {
case .loaded:
//When Assets load successfully, we create playerItem and add playerItem into AVPlayer
self?.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
self?.addObserverToPlayerItem()
case .failed:
//Asset load fail
//Since checking asset status here, no need to check player.currenItem.asset's media tracks when play button is clicked anymore.
if let error = error,
let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: (asset as? AVURLAsset)?.url.absoluteString ?? self?.videoURLString) {
self?.loadFailedError = errorObject
}
self?.videoState = .failed
default:
break
}
})
}
private func addObserverToPlayerItem() {
removeVideoObserver()
// To know when the video player item is done loading.
guard kvoToken == nil else { return }
kvoToken = player?.currentItem?.observe(\.status) { [weak self] (item, change) in
guard item == self?.player?.currentItem else { return }
switch item.status {
case .readyToPlay:
self?.videoState = .loaded
case .failed:
if let error = item.error,
let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: (item.asset as? AVURLAsset)?.url.absoluteString ?? self?.videoURLString) {
self?.loadFailedError = errorObject
}
self?.videoState = .failed
default:
break
}
}
}
private func removeVideoObserver() {
kvoToken?.invalidate()
kvoToken = nil
}
private func addMemoryWarningListener() {
memoryWarningListener = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
self?.removeVideoObserver()
self?.player = nil
self?.videoState = .none
}
}
private func removeMemoryWarningListener() {
guard let observer = memoryWarningListener else { return }
NotificationCenter.default.removeObserver(observer)
memoryWarningListener = nil
}
deinit {
removeVideoObserver()
removeMemoryWarningListener()
}
}

View File

@ -0,0 +1,167 @@
//
// VideoModel.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 1/26/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import Foundation
open class VideoModel: MoleculeModelProtocol {
public static var identifier = "video"
public var backgroundColor: Color?
public var video: String
public var showControls = false
public var autoPlay = true
public var alwaysReset = false
weak public var view: Video?
/// When the video is halted because it is no longer visible
public var halted: Bool = false {
didSet {
guard halted != oldValue,
videoDataManager.videoState == .loaded else { return }
if halted {
videoDataManager.player?.pause()
} else {
if alwaysReset {
// Always start video at the beginning.
videoDataManager.player?.seek(to: .zero)
}
if autoPlay {
videoDataManager.player?.play()
}
}
}
}
/// Keeps a reference to the video data.
public var videoDataManager: VideoDataManager
private weak var visibleBehavior: PageVisibilityClosureBehavior?
private weak var scrollBehavior: PageScrolledClosureBehavior?
private var activeListener: Any?
private var resignActiveListener: Any?
private enum CodingKeys: String, CodingKey {
case moleculeName
case video
case showControls
case autoPlay
case alwaysReset
}
public init(_ video: String) {
self.video = video
videoDataManager = VideoDataManager(with: video)
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
video = try typeContainer.decode(String.self, forKey:.video)
if let showControls = try typeContainer.decodeIfPresent(Bool.self, forKey: .showControls) {
self.showControls = showControls
}
if let autoPlay = try typeContainer.decodeIfPresent(Bool.self, forKey: .autoPlay) {
self.autoPlay = autoPlay
}
if let alwaysReset = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysReset) {
self.alwaysReset = alwaysReset
}
videoDataManager = VideoDataManager(with: video)
}
open func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(video, forKey: .video)
try container.encode(showControls, forKey: .showControls)
try container.encode(autoPlay, forKey: .autoPlay)
try container.encode(alwaysReset, forKey: .alwaysReset)
}
open func addVisibilityHalting(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
self.view = view
halted = false
addVisibleBehavior(for: view, delegateObject: delegateObject)
addScrollBehavior(for: view, delegateObject: delegateObject)
addActiveListener(for: view, delegateObject: delegateObject)
}
/// Adds a behavior to pause the video on page hidden behavior and unpause if necessary on page shown.
open func addVisibleBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
let onShow = { [weak self] in
guard let self = self,
let view = self.view,
view.isVisibleInDelegate() else { return }
self.halted = false
}
let onHide: () -> Void = { [weak self] in
self?.halted = true
}
guard visibleBehavior == nil else {
visibleBehavior?.pageShownHandler = onShow
visibleBehavior?.pageHiddenHandler = onHide
return
}
guard var delegate = delegateObject?.behaviorTemplateDelegate else { return }
let pauseBehavior = PageVisibilityClosureBehavior(with: onShow, onPageHiddenHandler: onHide)
delegate.add(behavior: pauseBehavior)
self.visibleBehavior = pauseBehavior
}
/// Adds a behavior to pause the video if scrolled off of the page and unpause if necessary if scrolled on.
open func addScrollBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
let onScroll = { [weak self] (scrollView: UIScrollView) in
// If visible to not visible, pause video.
// If not visible to visible, unpause if needed, add visible behavior
guard let self = self,
let view = self.view else { return }
self.halted = !view.isVisible(in: scrollView)
}
guard scrollBehavior == nil else {
scrollBehavior?.pageScrolledHandler = onScroll
return
}
guard var delegate = delegateObject?.behaviorTemplateDelegate else { return }
let scrollBehavior = PageScrolledClosureBehavior(with: onScroll)
delegate.add(behavior: scrollBehavior)
self.scrollBehavior = scrollBehavior
}
open func addActiveListener(for view: Video, delegateObject: MVMCoreUIDelegateObject?) {
removeActiveListener()
resignActiveListener = NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
self?.halted = true
}
activeListener = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
guard let self = self,
let view = self.view,
view.isVisibleInDelegate() else { return }
self.halted = false
}
}
private func removeActiveListener() {
if let observer = activeListener {
NotificationCenter.default.removeObserver(observer)
activeListener = nil
}
if let observer = resignActiveListener {
NotificationCenter.default.removeObserver(observer)
resignActiveListener = nil
}
}
deinit {
removeActiveListener()
}
}

View File

@ -79,6 +79,7 @@ import Foundation
MoleculeObjectMapping.shared()?.register(viewClass: DigitEntryField.self, viewModelClass: DigitEntryFieldModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ItemDropdownEntryField.self, viewModelClass: ItemDropdownEntryFieldModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: DateDropdownEntryField.self, viewModelClass: DateDropdownEntryFieldModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: MultiItemDropdownEntryField.self, viewModelClass: MultiItemDropdownEntryFieldModel.self)
// MARK:- Selectors
MoleculeObjectMapping.shared()?.register(viewClass: RadioButton.self, viewModelClass: RadioButtonModel.self)
@ -106,6 +107,7 @@ import Foundation
MoleculeObjectMapping.shared()?.register(viewClass: RadioButtonLabel.self, viewModelClass: RadioButtonLabelModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: WebView.self, viewModelClass: WebViewModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: LoadingSpinner.self, viewModelClass: LoadingSpinnerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Video.self, viewModelClass: VideoModel.self)
// MARK:- Horizontal Combination Molecules
MoleculeObjectMapping.shared()?.register(viewClass: StringAndMoleculeView.self, viewModelClass: StringAndMoleculeModel.self)
@ -151,6 +153,7 @@ import Foundation
MoleculeObjectMapping.shared()?.register(viewClass: Scroller.self, viewModelClass: ScrollerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ModuleMolecule.self, viewModelClass: ModuleMoleculeModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: BGImageMolecule.self, viewModelClass: BGImageMoleculeModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: BGVideoImageMolecule.self, viewModelClass: BGVideoImageMoleculeModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: MoleculeSectionHeader.self, viewModelClass: MoleculeSectionHeaderModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: MoleculeSectionFooter.self, viewModelClass: MoleculeSectionFooterModel.self)

View File

@ -6,7 +6,6 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class HeaderView: Container {
public let line = Line()
@ -15,7 +14,7 @@ open class HeaderView: Container {
open var molecule: MoleculeViewProtocol?
var headerModel: HeaderModel? {
get { return model as? HeaderModel }
get { model as? HeaderModel }
}
/// Convenience function to add a molecule to the view.
@ -58,6 +57,6 @@ open class HeaderView: Container {
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing
PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing
}
}

View File

@ -6,7 +6,6 @@
// Copyright © 2019 Suresh, Kamlesh. All rights reserved.
//
import Foundation
@objcMembers public class MoleculeHeaderModel: HeaderModel, MoleculeModelProtocol, MoleculeContainerModelProtocol {
public static var identifier: String = "header"

View File

@ -17,7 +17,7 @@ public class MoleculeHeaderView: MoleculeContainer {
var line = Line()
var headerModel: MoleculeHeaderModel? {
get { return model as? MoleculeHeaderModel }
get { model as? MoleculeHeaderModel }
}
//--------------------------------------------------

View File

@ -6,7 +6,6 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers open class TabBar: UITabBar, MoleculeViewProtocol, TabBarProtocol, UITabBarDelegate {
@ -15,9 +14,7 @@ import Foundation
public let line = Line()
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let model = model as? TabBarModel else {
fatalError("model is not TabBarModel")
}
guard let model = model as? TabBarModel else { fatalError("model is not TabBarModel") }
self.model = model
super.init(frame: .zero)
@ -32,7 +29,7 @@ import Foundation
fatalError("init(coder:) has not been implemented")
}
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let model = model as? TabBarModel else { return }
self.model = model
@ -57,8 +54,12 @@ import Foundation
var tabs: [UITabBarItem] = []
for (index, tab) in model.tabs.enumerated() {
let tabBarItem = UITabBarItem(title: tab.title, image: MVMCoreCache.shared()?.getImageFromRegisteredBundles(tab.image), tag: index)
tabBarItem.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -3)
tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font: MFFonts.mfFontTXRegular(8)], for: .normal)
tabBarItem.accessibilityLabel = tab.accessibilityText
if #available(iOS 13.0, *) {
} else {
tabBarItem.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -3)
tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font: MFFonts.mfFontTXRegular(8)], for: .normal)
}
tabs.append(tabBarItem)
}
setItems(tabs, animated: false)
@ -100,10 +101,7 @@ import Foundation
})
}
public func currentTabIndex() -> Int {
return model.selectedTab
}
public func currentTabIndex() -> Int { model.selectedTab }
}
extension UITabBarItem: MFButtonProtocol {
}
extension UITabBarItem: MFButtonProtocol { }

View File

@ -60,17 +60,19 @@ public class TabBarModel: MoleculeModelProtocol {
}
public class TabBarItemModel: Codable {
var title: String
var title: String?
var image: String
var action: ActionModelProtocol
var accessibilityText: String?
private enum CodingKeys: String, CodingKey {
case title
case image
case action
case accessibilityText
}
public init(with title: String, image: String, action: ActionModelProtocol) {
public init(with title: String?, image: String, action: ActionModelProtocol) {
self.title = title
self.image = image
self.action = action
@ -78,15 +80,17 @@ public class TabBarItemModel: Codable {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
title = try typeContainer.decode(String.self, forKey: .title)
title = try typeContainer.decodeIfPresent(String.self, forKey: .title)
image = try typeContainer.decode(String.self, forKey: .image)
action = try typeContainer.decodeModel(codingKey: .action)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encodeIfPresent(title, forKey: .title)
try container.encode(image, forKey: .image)
try container.encodeModel(action, forKey: .action)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
}
}

View File

@ -6,17 +6,15 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers open class ImageBarButtonItem: BarButtonItem {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public static func create(with image: UIImage?) -> Self {
let actionObject = ActionDelegate()
let button = self.init(image: image, style: .plain, target: actionObject, action: #selector(actionObject.callActionBlock(_:)))
let button = self.init(image: image, style: .plain, target: actionObject, action: #selector(actionObject.callActionBlock))
button.actionDelegate = actionObject
return button
}
@ -29,7 +27,7 @@ import Foundation
}
/// Creates the item with the passed in action map.
public static func create(with image: UIImage?, actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self {
public static func create(with image: UIImage?, actionMap: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self {
let button = create(with: image)
button.set(with: actionMap, delegateObject: delegateObject, additionalData: additionalData)
return button
@ -42,4 +40,3 @@ import Foundation
return button
}
}

View File

@ -6,10 +6,8 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers open class LabelBarButtonItem: BarButtonItem {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------

View File

@ -5,30 +5,47 @@
// Created by Scott Pfeil on 5/18/20.
//
import Foundation
public class NavigationImageButtonModel: NavigationButtonModelProtocol, MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public static var identifier: String = "navigationImageButton"
public var image: String
public var action: ActionModelProtocol
public var accessibilityText: String?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with image: String, action: ActionModelProtocol) {
self.image = image
self.action = action
}
//--------------------------------------------------
// MARK: - Coding Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case image
case action
case accessibilityIdentifier
case moleculeName
case accessibilityText
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
image = try typeContainer.decode(String.self, forKey: .image)
action = try typeContainer.decodeModel(codingKey: .action)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
@ -37,19 +54,26 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule
open func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(image, forKey: .image)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeModel(action, forKey: .action)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
}
//--------------------------------------------------
// MARK: - Method
//--------------------------------------------------
/// Convenience function that creates a BarButtonItem for the model.
public func createNavigationItemButton(delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> UIBarButtonItem {
let uiImage = MVMCoreCache.shared()?.getImageFromRegisteredBundles(image)
let navigationImageButton = ImageBarButtonItem.create(with: uiImage, actionModel: action, delegateObject: delegateObject, additionalData: additionalData)
let buttonItem = ImageBarButtonItem.create(with: uiImage, actionModel: action, delegateObject: delegateObject, additionalData: additionalData)
buttonItem.accessibilityIdentifier = accessibilityIdentifier ?? image
if let accessibilityString = accessibilityText {
navigationImageButton.accessibilityLabel = accessibilityString
navigationImageButton.isAccessibilityElement = true
buttonItem.accessibilityLabel = accessibilityString
buttonItem.isAccessibilityElement = true
}
return navigationImageButton
return buttonItem
}
}

View File

@ -6,28 +6,45 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
public class NavigationLabelButtonModel: NavigationButtonModelProtocol, MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var backgroundColor: Color?
public static var identifier: String = "navigationLabelButton"
public var accessibilityIdentifier: String?
public var title: String
public var action: ActionModelProtocol
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with title: String, action: ActionModelProtocol) {
self.title = title
self.action = action
}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case accessibilityIdentifier
case title
case action
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
title = try typeContainer.decode(String.self, forKey: .title)
action = try typeContainer.decodeModel(codingKey: .action)
}
@ -35,10 +52,15 @@ public class NavigationLabelButtonModel: NavigationButtonModelProtocol, Molecule
open func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(title, forKey: .title)
try container.encodeModel(action, forKey: .action)
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Convenience function that creates a BarButtonItem for the model.
public func createNavigationItemButton(delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> UIBarButtonItem {
return LabelBarButtonItem.create(with: title, actionModel: action, delegateObject: delegateObject, additionalData: additionalData)

View File

@ -6,12 +6,13 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtocol {
open class var identifier: String {
return "navigationBar"
}
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open class var identifier: String { "navigationBar" }
open var title: String?
open var hidden: Bool
@ -28,13 +29,21 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
open var additionalRightButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]?
open var titleView: MoleculeModelProtocol?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init() {
hidden = false
backgroundColor = Color(uiColor: .white)
tintColor = Color(uiColor: .black)
backgroundColor = Color(uiColor: .mvmWhite)
tintColor = Color(uiColor: .mvmBlack)
line = LineModel(type: .standard)
}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case title
@ -48,13 +57,17 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
case additionalRightButtons
case titleView
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
title = try typeContainer.decodeIfPresent(String.self, forKey: .title)
hidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidden) ?? false
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) ?? Color(uiColor: .white)
tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor) ?? Color(uiColor: .black)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) ?? Color(uiColor: .mvmWhite)
tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor) ?? Color(uiColor: .mvmBlack)
line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) ?? LineModel(type: .standard)
alwaysShowBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysShowBackButton)
backButton = try typeContainer.decodeModelIfPresent(codingKey: .backButton)

View File

@ -27,6 +27,12 @@ open class BGImageMoleculeModel: MoleculeContainerModel {
if bottomPadding == nil {
bottomPadding = PaddingDefaultVerticalSpacing3
}
if image.contentMode == nil {
image.contentMode = .scaleAspectFill
}
if image.imageFormat == nil {
image.imageFormat = "jpg"
}
}
private enum CodingKeys: String, CodingKey {

View File

@ -0,0 +1,98 @@
//
// BGVideoImageMolecule.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 1/26/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import UIKit
open class BGVideoImageMolecule: BGImageMolecule {
public let video = Video()
/// Used to hide video after loaded.
private var stateKVOToken: NSKeyValueObservation?
private var endObserver: NSObjectProtocol?
open override func setupView() {
super.setupView()
insertSubview(video, aboveSubview: image)
NSLayoutConstraint.constraintPinSubview(toSuperview: video)
}
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? BGVideoImageMoleculeModel else { return }
video.set(with: model.video, delegateObject, additionalData)
video.isHidden = shouldVideoBeHidden()
addStateObserver()
}
open func shouldVideoBeHidden() -> Bool {
guard let model = model as? BGVideoImageMoleculeModel,
model.video.videoDataManager.videoState != .failed else {
return true
}
guard model.video.videoDataManager.videoState == .loaded,
let player = model.video.videoDataManager.player,
let item = player.currentItem,
item.currentTime() == item.duration else {
return false
}
return true
}
/// Listens and responds to video loading state changes to add the end observer.
private func addStateObserver() {
removeStateObserver()
guard stateKVOToken == nil,
let model = model as? BGVideoImageMoleculeModel else { return }
// To know when the video player item is done loading.
stateKVOToken =
model.video.videoDataManager.observe(\.videoState) { [weak self] (item, change) in
guard let self = self,
let model = self.model as? BGVideoImageMoleculeModel,
item == model.video.videoDataManager else { return }
switch item.videoState {
case .loaded:
self.addEndObserver()
case .failed:
MVMCoreDispatchUtility.performBlock(onMainThread: {
self.video.isHidden = true
})
default:
break
}
}
}
private func removeStateObserver() {
stateKVOToken?.invalidate()
stateKVOToken = nil
}
private func addEndObserver() {
removeStateObserver()
guard let model = model as? BGVideoImageMoleculeModel,
let item = model.video.videoDataManager.player?.currentItem else { return }
endObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item, queue: OperationQueue.main) { [weak self] (notification) in
self?.video.isHidden = true
}
}
private func removeEndObserver() {
guard let endObserver = endObserver else { return }
NotificationCenter.default.removeObserver(endObserver)
self.endObserver = nil
}
deinit {
removeStateObserver()
removeEndObserver()
}
}

View File

@ -0,0 +1,38 @@
//
// BGVideoImageMoleculeModel.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 1/26/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import UIKit
open class BGVideoImageMoleculeModel: BGImageMoleculeModel {
open override class var identifier: String {
return "bgVideoImageContainer"
}
public var video: VideoModel
private enum CodingKeys: String, CodingKey {
case video
}
public init(_ video: VideoModel, image: ImageViewModel, molecule: MoleculeModelProtocol) {
self.video = video
super.init(image, molecule: molecule)
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
video = try typeContainer.decode(VideoModel.self, forKey:.video)
try super.init(from: decoder)
}
open override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(video, forKey: .video)
}
}

View File

@ -6,11 +6,10 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
extension MVMCoreUITopAlertExpandableView: MoleculeViewProtocol {
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
defaultSetup()
guard let model = model as? CollapsableNotificationModel else { return }
backgroundColor = model.backgroundColor?.uiColor ?? .mvmGreen
@ -30,6 +29,10 @@ extension MVMCoreUITopAlertExpandableView: MoleculeViewProtocol {
MVMCoreUITopAlertBaseView.addAction(to: button, actionMap: topActionMap, additionalData: nil)
shortView?.label?.accessibilityTraits = .button
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
}

View File

@ -6,17 +6,24 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
extension MVMCoreUITopAlertMainView: MoleculeViewProtocol {
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
defaultSetup()
guard let model = model as? NotificationModel else { return }
backgroundColor = model.backgroundColor?.uiColor ?? .mvmGreen
var actionMap = model.button?.action.toJSON()
if let title = model.button?.title {
actionMap?.updateValue(title, forKey: KeyTitle)
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
setupCloseButton(model.closeButton != nil, animationDelegate: MVMCoreUITopAlertView.sharedGlobal()?.animationDelegate)
setup(withMessage: model.headline.text, subMessage: model.body?.text, color: model.headline.textColor?.uiColor ?? .white, actionMap: actionMap, additionalData: nil)
}

View File

@ -6,52 +6,72 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class NotificationModel: MoleculeModelProtocol {
public class var identifier: String {
return "notification"
}
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String { "notification" }
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var headline: LabelModel
public var body: LabelModel?
public var button: ButtonModel?
public var closeButton: NotificationXButtonModel?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with headline: LabelModel) {
self.headline = headline
}
//--------------------------------------------------
// MARK: - Default
//--------------------------------------------------
open func setDefault() {
if backgroundColor == nil {
backgroundColor = Color(uiColor: .mvmGreen())
backgroundColor = Color(uiColor: .mvmGreen)
}
if headline.textColor == nil {
headline.textColor = Color(uiColor: .white)
headline.textColor = Color(uiColor: .mvmWhite)
}
if body?.textColor == nil {
body?.textColor = Color(uiColor: .white)
body?.textColor = Color(uiColor: .mvmWhite)
}
if button?.style == nil {
button?.style = .secondary
}
button?.size = .tiny
button?.enabledTextColor = Color(uiColor: .white)
button?.enabledBorderColor = Color(uiColor: .white)
button?.enabledTextColor = Color(uiColor: .mvmWhite)
button?.enabledBorderColor = Color(uiColor: .mvmWhite)
}
//--------------------------------------------------
// MARK: - Coding Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case accessibilityIdentifier
case headline
case body
case button
case closeButton
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
headline = try typeContainer.decode(LabelModel.self, forKey: .headline)
body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body)
button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button)
@ -63,6 +83,7 @@ open class NotificationModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(headline, forKey: .headline)
try container.encodeIfPresent(body, forKey: .body)
try container.encodeIfPresent(button, forKey: .button)

View File

@ -19,19 +19,15 @@ open class HeadlineBody: View {
// MARK: - Constraints
//--------------------------------------------------
var spaceBetweenLabelsConstant = PaddingOne
var spaceBetweenLabelsConstant = Padding.One
var spaceBetweenLabels: NSLayoutConstraint?
var leftConstraintTitle: NSLayoutConstraint?
var rightConstraintTitle: NSLayoutConstraint?
var leftConstraintMessage: NSLayoutConstraint?
var rightConstraintMessage: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
func hasText() -> Bool {
return headlineLabel.hasText || messageLabel.hasText
headlineLabel.hasText || messageLabel.hasText
}
// MARK: - Styling
@ -54,25 +50,25 @@ open class HeadlineBody: View {
}
}
func styleLandingPageHeader() {
public func styleLandingPageHeader() {
headlineLabel.setFontStyle(.Title2XLarge)
messageLabel.setFontStyle(.RegularBodySmall)
spaceBetweenLabelsConstant = PaddingTwo
spaceBetweenLabelsConstant = Padding.Two
}
func stylePageHeader() {
public func stylePageHeader() {
headlineLabel.setFontStyle(.BoldTitleLarge)
messageLabel.setFontStyle(.RegularBodySmall)
spaceBetweenLabelsConstant = PaddingOne
spaceBetweenLabelsConstant = Padding.One
}
func styleListItem() {
public func styleListItem() {
headlineLabel.setFontStyle(.BoldBodySmall)
messageLabel.setFontStyle(.RegularBodySmall)
spaceBetweenLabelsConstant = 0
}
func styleListItemDivider() {
public func styleListItemDivider() {
headlineLabel.setFontStyle(.BoldTitleMedium)
messageLabel.setFontStyle(.RegularBodySmall)
spaceBetweenLabelsConstant = 0
@ -86,48 +82,41 @@ open class HeadlineBody: View {
super.setupView()
backgroundColor = .clear
clipsToBounds = true
isAccessibilityElement = false
shouldGroupAccessibilityChildren = true
accessibilityElements = [headlineLabel, messageLabel]
let view = MVMCoreUICommonViewsUtility.commonView()
addSubview(view)
NSLayoutConstraint.constraintPinSubview(toSuperview: view)
view.isAccessibilityElement = false
view.shouldGroupAccessibilityChildren = true
view.accessibilityElements = [headlineLabel, messageLabel]
view.addSubview(headlineLabel)
view.addSubview(messageLabel)
addSubview(headlineLabel)
addSubview(messageLabel)
headlineLabel.setContentCompressionResistancePriority(.required, for: .vertical)
headlineLabel.setContentHuggingPriority(.required, for: .vertical)
messageLabel.setContentCompressionResistancePriority(.required, for: .vertical)
messageLabel.setContentHuggingPriority(.required, for: .vertical)
view.setContentHuggingPriority(.required, for: .vertical)
headlineLabel.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
headlineLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true
spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: spaceBetweenLabelsConstant)
spaceBetweenLabels?.isActive = true
leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: view.leftAnchor)
leftConstraintTitle?.isActive = true
rightConstraintTitle = view.rightAnchor.constraint(equalTo: headlineLabel.rightAnchor)
rightConstraintTitle?.isActive = true
leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: view.leftAnchor)
leftConstraintMessage?.isActive = true
rightConstraintMessage = view.rightAnchor.constraint(equalTo: messageLabel.rightAnchor)
rightConstraintMessage?.isActive = true
view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor).isActive = true
NSLayoutConstraint.activate([
headlineLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingAnchor.constraint(equalTo: headlineLabel.trailingAnchor),
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor),
bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor)
])
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
setSpacing()
headlineLabel.updateView(size)
messageLabel.updateView(size)
setSpacing()
// Provide the label additional size information to help calculate its intrinsic height.
let padding = MFStyler.defaultHorizontalPadding(forSize: size) * 2
messageLabel.preferredMaxLayoutWidth = size - padding
}
//--------------------------------------------------
@ -146,19 +135,18 @@ open class HeadlineBody: View {
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 58
}
public override class func estimatedHeight(with model: MoleculeModelProtocol,
_ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 58 }
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let headlineBodyModel = model as? HeadlineBodyModel else { return }
guard let model = model as? HeadlineBodyModel else { return }
style(with: headlineBodyModel.style)
style(with: model.style)
headlineLabel.setOptional(with: headlineBodyModel.headline, delegateObject, additionalData)
messageLabel.setOptional(with: headlineBodyModel.body, delegateObject, additionalData)
headlineLabel.setOptional(with: model.headline, delegateObject, additionalData)
messageLabel.setOptional(with: model.body, delegateObject, additionalData)
}
open override func reset() {

View File

@ -16,7 +16,7 @@ open class Stack<T>: Container where T: (StackModelProtocol & MoleculeModelProto
open var stackItems: [UIView] = []
open var stackModel: T? {
get { return model as? T }
get { model as? T }
}
//--------------------------------------------------

View File

@ -0,0 +1,23 @@
//
// AccessibilityModelProtocol.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 2/3/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import Foundation
public protocol AccessibilityModelProtocol {
var accessibilityIdentifier: String? { get set }
}
public extension AccessibilityModelProtocol {
var accessibilityIdentifier: String? {
get { nil }
set { }
}
}

View File

@ -7,22 +7,16 @@ public enum MolecularError: Swift.Error {
}
public protocol MoleculeModelProtocol: ModelProtocol {
public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol {
var moleculeName: String { get }
var backgroundColor: Color? { get set }
}
public extension MoleculeModelProtocol {
var moleculeName: String {
get { return Self.identifier }
}
var moleculeName: String { Self.identifier }
static var categoryName: String {
return "\(MoleculeModelProtocol.self)"
}
static var categoryName: String { "\(MoleculeModelProtocol.self)" }
static var categoryCodingKey: String {
return "moleculeName"
}
static var categoryCodingKey: String { "moleculeName" }
}

View File

@ -29,14 +29,10 @@ open class ModalMoleculeListTemplate: MoleculeListTemplate {
super.handleNewData()
closeButton = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: { [weak self] _ in
guard let self = self else {
return
}
guard let model = self.templateModel as? ModalListPageTemplateModel, let actionMap = model.closeAction else {
MVMCoreActionHandler.shared()?.handleAction(with: ActionBackModel().toJSON(), additionalData: nil, delegateObject: self.delegateObjectIVar)
return
}
MVMCoreActionHandler.shared()?.handleAction(with: actionMap.toJSON(), additionalData: nil, delegateObject: self.delegateObjectIVar)
guard let self = self else { return }
let closeAction = (self.templateModel as? ModalListPageTemplateModel)?.closeAction ??
ActionBackModel()
MVMCoreActionHandler.shared()?.asyncHandleAction(with: closeAction, additionalData: nil, delegateObject: self.delegateObjectIVar)
})
}

View File

@ -18,15 +18,10 @@ open class ModalMoleculeStackTemplate: MoleculeStackTemplate {
override open func handleNewData() {
super.handleNewData()
_ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: {[weak self] _ in
guard let self = self else {
return
}
guard let model = self.templateModel as? ModalStackPageTemplateModel, let actionMap = model.closeAction else {
MVMCoreActionHandler.shared()?.handleAction(with: ActionBackModel().toJSON(), additionalData: nil, delegateObject: self.delegateObjectIVar)
return
}
MVMCoreActionHandler.shared()?.handleAction(with: actionMap.toJSON(), additionalData: nil, delegateObject: self.delegateObjectIVar)
guard let self = self else { return }
let closeAction = (self.templateModel as? ModalStackPageTemplateModel)?.closeAction ??
ActionBackModel()
MVMCoreActionHandler.shared()?.asyncHandleAction(with: closeAction, additionalData: nil, delegateObject: self.delegateObjectIVar)
})
}
}

View File

@ -19,12 +19,9 @@ open class ModalSectionListTemplate: SectionListTemplate {
super.handleNewData()
_ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: {[weak self] _ in
guard let self = self else { return }
guard let model = self.templateModel as? ModalSectionListTemplateModel,
let actionMap = model.closeAction else {
MVMCoreActionHandler.shared()?.handleAction(with: ActionBackModel().toJSON(), additionalData: nil, delegateObject: self.delegateObjectIVar)
return
}
MVMCoreActionHandler.shared()?.handleAction(with: actionMap.toJSON(), additionalData: nil, delegateObject: self.delegateObjectIVar)
let closeAction = (self.templateModel as? ModalSectionListTemplateModel)?.closeAction ??
ActionBackModel()
MVMCoreActionHandler.shared()?.asyncHandleAction(with: closeAction, additionalData: nil, delegateObject: self.delegateObjectIVar)
})
}
}

View File

@ -14,6 +14,14 @@ import Foundation
return 0
}
open override func loadView() {
super.loadView()
// The height is used to keep the bottom view at the bottom.
if let contentView = contentView, let scrollView = scrollView {
contentView.heightAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.heightAnchor).isActive = true
}
}
open override func handleNewData() {
super.handleNewData()
heightConstraint?.isActive = true

View File

@ -8,6 +8,7 @@
public typealias BarButtonAction = (BarButtonItem) -> ()
@objc class ActionDelegate: NSObject {
var buttonAction: BarButtonAction?
@objc func callActionBlock(_ sender: BarButtonItem) {
@ -16,7 +17,6 @@ public typealias BarButtonAction = (BarButtonItem) -> ()
}
@objcMembers open class BarButtonItem: UIBarButtonItem, MFButtonProtocol {
//--------------------------------------------------
// MARK: - Delegate
//--------------------------------------------------
@ -43,4 +43,3 @@ public typealias BarButtonAction = (BarButtonItem) -> ()
}
}
}

View File

@ -8,6 +8,7 @@
public typealias ButtonAction = (Button) -> ()
@objcMembers open class Button: UIButton, MFButtonProtocol, MoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
@ -65,7 +66,7 @@ public typealias ButtonAction = (Button) -> ()
/// Adds a block to be performed for the given event.
open func addActionBlock(event: Event, _ buttonBlock: @escaping ButtonAction) {
self.buttonAction = buttonBlock
addTarget(self, action: #selector(callActionBlock(_:)), for: event)
addTarget(self, action: #selector(callActionBlock), for: event)
}
@objc func callActionBlock(_ sender: Button) {
@ -103,6 +104,10 @@ public typealias ButtonAction = (Button) -> ()
self.backgroundColor = backgroundColor.uiColor
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
if let model = model as? EnableableModelProtocol {
isEnabled = model.enabled
}
@ -119,25 +124,23 @@ public typealias ButtonAction = (Button) -> ()
// MARK: Overridables
// Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead.
open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
return model.moleculeName
model.moleculeName
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil }
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
return nil
}
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { nil }
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
open override func accessibilityActivate() -> Bool {
guard isEnabled else { return false }
buttonAction?(self)
return buttonAction != nil
}
}
// MARK: - MVMCoreViewProtocol
@ -160,6 +163,6 @@ extension Button: MVMCoreViewProtocol {
extension Button: AppleGuidelinesProtocol {
override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return Self.acceptablyOutsideBounds(point: point, bounds: bounds)
Self.acceptablyOutsideBounds(point: point, bounds: bounds)
}
}

View File

@ -51,9 +51,14 @@ import UIKit
// MARK:- MoleculeViewProtocol
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.model = model
if let backgroundColor = model.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
open func reset() {
@ -63,22 +68,18 @@ import UIKit
// MARK: Overridables
// Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead.
open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
return model.moleculeName
model.moleculeName
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil }
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
return nil
}
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { nil }
}
// MARK: - AppleGuidelinesProtocol
extension Control: AppleGuidelinesProtocol {
override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return Self.acceptablyOutsideBounds(point: point, bounds: bounds)
Self.acceptablyOutsideBounds(point: point, bounds: bounds)
}
}

View File

@ -53,33 +53,35 @@ open class ImageView: UIImageView, MoleculeViewProtocol {
}
public func reset() {
backgroundColor = .clear
}
backgroundColor = .clear
}
public func setAsMolecule() { }
//--------------------------------------------------
// MARK: - ModelMoleculeViewProtocol
//--------------------------------------------------
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.model = model
if let backgroundColor = model.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
return model?.moleculeName
model?.moleculeName
}
open class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil
}
open class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil }
open class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
return nil
}
open class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { nil }
}
// MARK:- MVMCoreViewProtocol
@ -94,4 +96,3 @@ extension ImageView: MVMCoreViewProtocol {
MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0)
}
}

View File

@ -6,7 +6,6 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers open class SectionHeaderFooterView: UITableViewHeaderFooterView, MoleculeViewProtocol {
//--------------------------------------------------
@ -43,10 +42,16 @@ import Foundation
//--------------------------------------------------
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.model = model
if let backgroundColor = model.backgroundColor {
contentView.backgroundColor = backgroundColor.uiColor
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
open func reset() {
@ -56,16 +61,12 @@ import Foundation
// MARK: Overridables
// Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead.
open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
return model.moleculeName
model.moleculeName
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil }
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
return nil
}
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { nil }
}
// MARK:- MVMCoreViewProtocol

View File

@ -11,7 +11,7 @@ import Foundation
@objcMembers open class TableView: UITableView {
/// A block that gets called on tableview frame changes
public var frameChangeAction: (() -> Void)?
public var frameChangeAction: (() -> ())?
private var previousFrame = CGRect.zero

View File

@ -93,6 +93,10 @@ extension TextField: MoleculeViewProtocol {
if let color = model.backgroundColor?.uiColor {
backgroundColor = color
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
open func reset() {

View File

@ -106,8 +106,8 @@ import UIKit
open func reset() {
fontStyle = Styler.Font.RegularBodyLarge
placeholderFontStyle = Styler.Font.RegularMicro
fontStyle = .RegularBodyLarge
placeholderFontStyle = .RegularMicro
placeholderTextColor = .mvmCoolGray3
textColor = .mvmBlack
isEnabled = true

View File

@ -58,6 +58,10 @@ import UIKit
if let backgroundColor = model.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
}
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
open func reset() {
@ -67,16 +71,12 @@ import UIKit
// MARK: Overridables
// Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead.
open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
return model.moleculeName
model.moleculeName
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil }
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
return nil
}
open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { nil }
}
// MARK:- MVMCoreViewProtocol

View File

@ -73,6 +73,12 @@ open class ScrollingViewController: ViewController {
scrollView.flashScrollIndicators()
}
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
executeBehaviors { (behavior: PageScrolledBehavior) in
behavior.pageScrolled(scrollView: scrollView)
}
}
//--------------------------------------------------
// MARK: - Keyboard Handling
//--------------------------------------------------

View File

@ -74,7 +74,7 @@ import UIKit
}
open func modulesToListenFor() -> [String]? {
loadObject?.requestParameters?.modules as? [String]
loadObject?.requestParameters?.allModules()
}
@objc open func responseJSONUpdated(notification: Notification) {
@ -348,8 +348,8 @@ import UIKit
// First update should be explicit (hence the zero check)
if needsUpdateUI || (previousScreenSize != .zero && screenSizeChanged()) {
updateViews()
needsUpdateUI = false
updateViews()
}
previousScreenSize = view.bounds.size;

View File

@ -10,23 +10,22 @@ import Foundation
public protocol PageBehaviorProtocol: ModelProtocol {
// The type of rule
/// The type of rule
var behaviorName: String { get }
}
public extension PageBehaviorProtocol {
var behaviorName: String {
get { return Self.identifier }
get { Self.identifier }
}
static var categoryCodingKey: String {
return "behaviorName"
"behaviorName"
}
static var categoryName: String {
return "\(PageBehaviorProtocol.self)"
"\(PageBehaviorProtocol.self)"
}
}
@ -34,11 +33,31 @@ public protocol PageVisibilityBehavior: PageBehaviorProtocol {
func onPageShown()
func onPageHidden()
}
public protocol PageScrolledBehavior: PageBehaviorProtocol {
func pageScrolled(scrollView: UIScrollView)
}
public protocol PageBehaviorsTemplateProtocol {
var behaviors: [PageBehaviorProtocol]? { get }
var behaviors: [PageBehaviorProtocol]? { get set }
}
public extension PageBehaviorsTemplateProtocol {
mutating func add(behavior: PageBehaviorProtocol) {
var newBehaviors = behaviors ?? []
newBehaviors.append(behavior)
self.behaviors = newBehaviors
}
}
public extension MVMCoreUIDelegateObject {
weak var behaviorTemplateDelegate: (PageBehaviorsTemplateProtocol & NSObjectProtocol)? {
get {
return (moleculeDelegate as? PageProtocol)?.pageModel as? (PageBehaviorsTemplateProtocol & NSObjectProtocol)
}
}
}

View File

@ -0,0 +1,33 @@
//
// PageScrolledClosureBehavior.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 2/11/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import Foundation
public class PageScrolledClosureBehavior: PageScrolledBehavior {
public static var identifier = "pageScrolledClosureBehavior"
public var pageScrolledHandler: (_ scrollView: UIScrollView) -> Void
public init(with onPageScrolledHandler: @escaping (_ scrollView: UIScrollView) -> Void) {
self.pageScrolledHandler = onPageScrolledHandler
}
// This class is not meant to be decoded and encoded really.
public required init(from decoder: Decoder) throws {
throw ModelRegistry.Error.decoderOther(message: "PageScrolledClosureBehavior does not decode.")
}
public func encode(to encoder: Encoder) throws {
throw ModelRegistry.Error.decoderOther(message: "PageScrolledClosureBehavior does not encode.")
}
public func pageScrolled(scrollView: UIScrollView) {
pageScrolledHandler(scrollView)
}
}

View File

@ -0,0 +1,40 @@
//
// PageVisibilityClosureBehavior.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 2/11/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
import Foundation
public class PageVisibilityClosureBehavior: PageVisibilityBehavior {
public static var identifier = "pageVisibilityClosureBehavior"
public var pageShownHandler: () -> Void
public var pageHiddenHandler: () -> Void
public init(with onPageShownHandler: @escaping () -> Void, onPageHiddenHandler: @escaping () -> Void) {
self.pageShownHandler = onPageShownHandler
self.pageHiddenHandler = onPageHiddenHandler
}
// This class is not meant to be decoded and encoded really.
public required init(from decoder: Decoder) throws {
throw ModelRegistry.Error.decoderOther(message: "PageVisibilityClosureBehavior does not decode.")
}
public func encode(to encoder: Encoder) throws {
throw ModelRegistry.Error.decoderOther(message: "PageVisibilityClosureBehavior does not encode.")
}
//MARK:- PageVisibilityBehavior
public func onPageShown() {
pageShownHandler()
}
public func onPageHidden() {
pageHiddenHandler()
}
}

Some files were not shown because too many files have changed in this diff Show More