Merge branch 'develop' into feature/label_accessibility
This commit is contained in:
commit
1c5b2e448e
@ -18,8 +18,9 @@
|
||||
01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */; };
|
||||
01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; };
|
||||
01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; };
|
||||
B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; };
|
||||
B8200E192281DC1A007245F4 /* ProgressBarWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */; };
|
||||
B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; };
|
||||
D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */; };
|
||||
D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; };
|
||||
@ -31,6 +32,9 @@
|
||||
D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */; };
|
||||
D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */; };
|
||||
D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; };
|
||||
D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; };
|
||||
D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */; };
|
||||
D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */; };
|
||||
@ -162,8 +166,13 @@
|
||||
D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* StandardHeaderView.swift */; };
|
||||
D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; };
|
||||
D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A638FC22CA98280052ED1F /* HeadlineBody.swift */; };
|
||||
D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390022CBB1820052ED1F /* Carousel.swift */; };
|
||||
D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */; };
|
||||
D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */; };
|
||||
D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */; };
|
||||
D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */; };
|
||||
D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */; };
|
||||
D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; };
|
||||
D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */; };
|
||||
D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; };
|
||||
@ -187,8 +196,9 @@
|
||||
01CA51B4229716F60071A6EE /* Switch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = "<group>"; };
|
||||
01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = "<group>"; };
|
||||
01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = "<group>"; };
|
||||
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; };
|
||||
B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = "<group>"; };
|
||||
B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBarWithLabel.swift; sourceTree = "<group>"; };
|
||||
B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = "<group>"; };
|
||||
D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = "<group>"; };
|
||||
D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUINavigationController.m; sourceTree = "<group>"; };
|
||||
D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = "<group>"; };
|
||||
@ -200,6 +210,9 @@
|
||||
D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUISwitch.m; sourceTree = "<group>"; };
|
||||
D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIStackableViewController.h; sourceTree = "<group>"; };
|
||||
D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIStackableViewController.m; sourceTree = "<group>"; };
|
||||
D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPageControl.h; sourceTree = "<group>"; };
|
||||
D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = "<group>"; };
|
||||
D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = "<group>"; };
|
||||
D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = "<group>"; };
|
||||
D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFLoadImageView.swift; sourceTree = "<group>"; };
|
||||
D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFTransparentGIFView.swift; sourceTree = "<group>"; };
|
||||
@ -335,8 +348,13 @@
|
||||
D2A514662213885800345BFB /* StandardHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardHeaderView.swift; sourceTree = "<group>"; };
|
||||
D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerViewController.swift; sourceTree = "<group>"; };
|
||||
D2A638FC22CA98280052ED1F /* HeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBody.swift; sourceTree = "<group>"; };
|
||||
D2A6390022CBB1820052ED1F /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = "<group>"; };
|
||||
D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeadlineBody.swift; sourceTree = "<group>"; };
|
||||
D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewControllerMappingObject.h; sourceTree = "<group>"; };
|
||||
D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIViewControllerMappingObject.m; sourceTree = "<group>"; };
|
||||
D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroller.swift; sourceTree = "<group>"; };
|
||||
D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplate.swift; sourceTree = "<group>"; };
|
||||
D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = "<group>"; };
|
||||
D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTableViewController.swift; sourceTree = "<group>"; };
|
||||
@ -433,6 +451,7 @@
|
||||
D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */,
|
||||
D296E13B2295969C0051EBE7 /* MoleculeListCellProtocol.h */,
|
||||
D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */,
|
||||
D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */,
|
||||
);
|
||||
path = Templates;
|
||||
sourceTree = "<group>";
|
||||
@ -459,6 +478,7 @@
|
||||
D29DF10E21E67A77003B2FB9 /* Molecules */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */,
|
||||
01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */,
|
||||
D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */,
|
||||
D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */,
|
||||
@ -472,9 +492,16 @@
|
||||
D274CA322236A78900B01B62 /* StandardFooterView.swift */,
|
||||
D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */,
|
||||
B8200E142280C4CF007245F4 /* ProgressBar.swift */,
|
||||
B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */,
|
||||
B8200E182281DC1A007245F4 /* CornerLabels.swift */,
|
||||
D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */,
|
||||
D2A638FC22CA98280052ED1F /* HeadlineBody.swift */,
|
||||
D2A6390022CBB1820052ED1F /* Carousel.swift */,
|
||||
D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */,
|
||||
D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */,
|
||||
D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */,
|
||||
D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */,
|
||||
D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */,
|
||||
D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */,
|
||||
);
|
||||
path = Molecules;
|
||||
sourceTree = "<group>";
|
||||
@ -751,6 +778,7 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */,
|
||||
D29DF18021E69E49003B2FB9 /* MFView.h in Headers */,
|
||||
D29DF27921E7A533003B2FB9 /* MVMCoreUISession.h in Headers */,
|
||||
D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */,
|
||||
@ -809,6 +837,7 @@
|
||||
D29770FD21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h in Headers */,
|
||||
D29DF17421E69E1F003B2FB9 /* MFCustomButton.h in Headers */,
|
||||
D29DF29721E7ADB8003B2FB9 /* MFScrollingViewController.h in Headers */,
|
||||
D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */,
|
||||
D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */,
|
||||
D29DF2A121E7AF4E003B2FB9 /* MVMCoreUIUtility.h in Headers */,
|
||||
D29DF17621E69E1F003B2FB9 /* PrimaryButton.h in Headers */,
|
||||
@ -932,11 +961,14 @@
|
||||
D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */,
|
||||
D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */,
|
||||
DBEFFA04225A829700230692 /* Label.swift in Sources */,
|
||||
D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */,
|
||||
D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */,
|
||||
D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */,
|
||||
D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */,
|
||||
D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */,
|
||||
D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */,
|
||||
D282AACB2243C61700C46919 /* ButtonView.swift in Sources */,
|
||||
D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */,
|
||||
0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */,
|
||||
D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */,
|
||||
D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */,
|
||||
@ -951,6 +983,8 @@
|
||||
01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */,
|
||||
D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */,
|
||||
D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */,
|
||||
D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */,
|
||||
D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */,
|
||||
D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */,
|
||||
016A1071228122180009D605 /* SwitchLineItem.swift in Sources */,
|
||||
D29DF29521E7ADB8003B2FB9 /* ProgrammaticScrollViewController.m in Sources */,
|
||||
@ -967,6 +1001,7 @@
|
||||
D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */,
|
||||
D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */,
|
||||
D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */,
|
||||
D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */,
|
||||
D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */,
|
||||
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,
|
||||
D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */,
|
||||
@ -979,11 +1014,12 @@
|
||||
D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */,
|
||||
D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */,
|
||||
0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */,
|
||||
0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */,
|
||||
D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */,
|
||||
D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */,
|
||||
D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */,
|
||||
D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */,
|
||||
B8200E192281DC1A007245F4 /* ProgressBarWithLabel.swift in Sources */,
|
||||
B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */,
|
||||
D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */,
|
||||
0105618D224BBE7700E1557D /* FormValidator.swift in Sources */,
|
||||
D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */,
|
||||
|
||||
@ -178,6 +178,17 @@ public typealias ActionBlock = () -> Void
|
||||
|
||||
setLabel(label, withHTML: json?.optionalStringForKey("html"))
|
||||
|
||||
if let alignment = json?.optionalStringForKey("textAlignment") {
|
||||
switch alignment {
|
||||
case "center":
|
||||
label.textAlignment = .center
|
||||
case "right":
|
||||
label.textAlignment = .right
|
||||
default:
|
||||
label.textAlignment = .left
|
||||
}
|
||||
}
|
||||
|
||||
if let backgroundColorHex = json?.optionalStringForKey(KeyBackgroundColor), !backgroundColorHex.isEmpty {
|
||||
label.backgroundColor = UIColor.mfGet(forHex: backgroundColorHex)
|
||||
}
|
||||
@ -217,7 +228,7 @@ public typealias ActionBlock = () -> Void
|
||||
|
||||
case "strikethrough":
|
||||
attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range)
|
||||
|
||||
attributedString.addAttribute(.baselineOffset, value: 0, range: range)
|
||||
case "color":
|
||||
if let colorHex = attribute.optionalStringForKey(KeyTextColor), !colorHex.isEmpty {
|
||||
attributedString.removeAttribute(.foregroundColor, range: range)
|
||||
@ -406,6 +417,7 @@ extension Label {
|
||||
public func reset() {
|
||||
text = nil
|
||||
attributedText = nil
|
||||
textAlignment = .left
|
||||
originalAttributedString = nil
|
||||
hasAttachmentImage = false
|
||||
styleB2(true)
|
||||
|
||||
@ -22,9 +22,11 @@ import UIKit
|
||||
var imageWidth: CGFloat?
|
||||
var imageHeight: CGFloat?
|
||||
|
||||
var delegateObject: MVMCoreUIDelegateObject?
|
||||
|
||||
// For keeping track of current state.
|
||||
private var edges: UIRectEdge?
|
||||
private var spinnerHeight: CGFloat?
|
||||
private var edges = UIRectEdge(rawValue: 0)
|
||||
private var spinnerHeight: CGFloat = 0
|
||||
private var currentImageWidth: CGFloat?
|
||||
private var currentImageHeight: CGFloat?
|
||||
private var currentImageName: String?
|
||||
@ -37,7 +39,6 @@ import UIKit
|
||||
|
||||
// The default is an image that is centered with no edges pinned. So it will take the size of the content and fill as needed.
|
||||
public init() {
|
||||
edges = UIRectEdge(rawValue: 0)
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
@ -91,10 +92,7 @@ import UIKit
|
||||
pinView(toSuperView: imageView)
|
||||
|
||||
// Setup edges constraints
|
||||
if edges == nil {
|
||||
edges = UIRectEdge(rawValue: 0)
|
||||
}
|
||||
pinEdges(edges!)
|
||||
pinEdges(edges)
|
||||
|
||||
// Setup spinner.
|
||||
loadingSpinner.clipsToBounds = true
|
||||
@ -153,6 +151,40 @@ import UIKit
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Constraints
|
||||
|
||||
func setHeight(_ height: CGFloat) {
|
||||
if let heightConstraint = heightConstraint, MVMCoreGetterUtility.cgfequal(heightConstraint.multiplier, 1) {
|
||||
heightConstraint.constant = height
|
||||
} else {
|
||||
heightConstraint?.isActive = false
|
||||
heightConstraint = imageView.heightAnchor.constraint(equalToConstant: height)
|
||||
heightConstraint?.priority = UILayoutPriority(rawValue: 900)
|
||||
}
|
||||
heightConstraint?.isActive = true
|
||||
}
|
||||
|
||||
func setWidth(_ width: CGFloat) {
|
||||
if let widthConstraint = widthConstraint, MVMCoreGetterUtility.cgfequal(widthConstraint.multiplier, 1) {
|
||||
widthConstraint.constant = width
|
||||
} else {
|
||||
widthConstraint = imageView.widthAnchor.constraint(equalToConstant: width)
|
||||
widthConstraint?.priority = UILayoutPriority(rawValue: 900)
|
||||
}
|
||||
widthConstraint?.isActive = true
|
||||
}
|
||||
|
||||
func layoutWillChange(width: CGFloat?, height: CGFloat?, size: CGSize?) -> Bool {
|
||||
guard addSizeConstraintsForAspectRatio else {
|
||||
return false
|
||||
}
|
||||
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 heightChangeFromSpinner = ((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?) {
|
||||
widthConstraint?.isActive = false
|
||||
@ -162,26 +194,25 @@ import UIKit
|
||||
}
|
||||
|
||||
if let width = width, let height = height {
|
||||
heightConstraint = imageView.heightAnchor.constraint(equalToConstant: height.cgfloat())
|
||||
widthConstraint = imageView.widthAnchor.constraint(equalToConstant: width.cgfloat())
|
||||
setHeight(height.cgfloat())
|
||||
setWidth(width.cgfloat())
|
||||
} else if let width = width, let size = size {
|
||||
widthConstraint = imageView.widthAnchor.constraint(equalToConstant: width.cgfloat())
|
||||
setWidth(width.cgfloat())
|
||||
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 {
|
||||
heightConstraint = imageView.heightAnchor.constraint(equalToConstant: height.cgfloat())
|
||||
setHeight(height.cgfloat())
|
||||
widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: size.width/size.height)
|
||||
widthConstraint?.priority = UILayoutPriority(rawValue: 900)
|
||||
widthConstraint?.isActive = true
|
||||
}
|
||||
widthConstraint?.priority = UILayoutPriority(rawValue: 900)
|
||||
heightConstraint?.priority = UILayoutPriority(rawValue: 900)
|
||||
heightConstraint?.isActive = true
|
||||
widthConstraint?.isActive = true
|
||||
imageView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal)
|
||||
imageView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical)
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol functions
|
||||
open override func setAsMolecule() {
|
||||
addSizeConstraintsForAspectRatio = true
|
||||
pinEdges(.all)
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
@ -190,6 +221,7 @@ import UIKit
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
self.delegateObject = delegateObject
|
||||
if let accessibilityString = json?.optionalStringForKey("accessibilityText") {
|
||||
imageView.accessibilityLabel = accessibilityString
|
||||
imageView.accessibilityTraits = .staticText
|
||||
@ -197,6 +229,13 @@ import UIKit
|
||||
}
|
||||
let width = json?.optionalCGFloatForKey("width") ?? imageWidth
|
||||
let height = json?.optionalCGFloatForKey("height") ?? imageHeight
|
||||
// For smoother transitions, set constraints that we know immediately.
|
||||
if let width = width, addSizeConstraintsForAspectRatio {
|
||||
setWidth(width)
|
||||
}
|
||||
if let height = height, addSizeConstraintsForAspectRatio {
|
||||
setHeight(height)
|
||||
}
|
||||
if let imageName = json?.optionalStringForKey("image"), shouldLoadImage(withName: imageName, width: width, height: height) {
|
||||
imageView.image = nil
|
||||
imageView.animatedImage = nil
|
||||
@ -210,9 +249,9 @@ import UIKit
|
||||
self.currentImageName = imageName
|
||||
self.currentImageWidth = width?.cgfloat()
|
||||
self.currentImageHeight = height?.cgfloat()
|
||||
self.loadingSpinner.resumeSpinnerAfterDelay()
|
||||
if let height = self.spinnerHeight {
|
||||
self.loadingSpinnerHeightConstraint?.constant = height
|
||||
if MVMCoreCache.isHostedImage(imageName) {
|
||||
self.loadingSpinner.resumeSpinnerAfterDelay()
|
||||
self.loadingSpinnerHeightConstraint?.constant = self.spinnerHeight
|
||||
}
|
||||
|
||||
let finishedLoadingBlock: MVMCoreGetImageBlock = {[weak self] (image, data, isFallbackImage) in MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
|
||||
@ -220,9 +259,13 @@ import UIKit
|
||||
return
|
||||
}
|
||||
self?.isFallbackImage = isFallbackImage
|
||||
self?.loadingSpinnerHeightConstraint?.constant = 0
|
||||
self?.loadingSpinner.pause()
|
||||
let layoutWillChange = self?.layoutWillChange(width: self?.currentImageWidth, height: self?.currentImageHeight, size: image?.size) ?? false
|
||||
self?.addConstraints(width: width, height: height, size: image?.size)
|
||||
self?.loadingSpinnerHeightConstraint?.constant = 0
|
||||
if layoutWillChange {
|
||||
self?.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self!)
|
||||
}
|
||||
completionHandler(image,data,isFallbackImage)
|
||||
})}
|
||||
|
||||
@ -241,9 +284,8 @@ import UIKit
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: { [unowned self] in
|
||||
self.currentImageName = imageName
|
||||
self.loadingSpinner.resumeSpinnerAfterDelay()
|
||||
if let height = self.spinnerHeight {
|
||||
self.loadingSpinnerHeightConstraint?.constant = height
|
||||
}
|
||||
self.loadingSpinnerHeightConstraint?.constant = self.spinnerHeight
|
||||
|
||||
MVMCoreCache.shared()?.getCroppedImage(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, finalRect: cropRect, flipImage: flipImage, localFallbackImageName: customFallbackImage ?? MVMCoreUIUtility.localizedImageName("fallback"), completionHandler: { [weak self] (image, data, isFallBackImage) in
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let image = image, let loadingImageName = self?.currentImageName, loadingImageName == imageName else {
|
||||
|
||||
@ -30,7 +30,6 @@ typedef void (^PerformActionForRadioButton)(void);
|
||||
@property (nullable, copy, nonatomic) PerformActionForRadioButton performActionForCheck;
|
||||
typedef void (^CircleSelectedClosure)(_Nonnull id sender);
|
||||
|
||||
|
||||
@property (nonatomic, strong, nullable) UIColor* fillColor;
|
||||
@property (nonatomic, strong, nullable) UIColor* borderColor;
|
||||
@property (nonatomic) CGFloat circleBorderWidth;
|
||||
@ -38,11 +37,16 @@ typedef void (^CircleSelectedClosure)(_Nonnull id sender);
|
||||
@property (nonatomic, copy, nullable) CircleSelectedClosure handleSelectionBlock;
|
||||
@property (nonatomic) CGFloat circleDiameter;
|
||||
@property (nonatomic, nullable, strong, readonly) CAShapeLayer * myCircle;
|
||||
|
||||
@property (nullable, weak, nonatomic) UIView *outerCircleView;
|
||||
@property (nullable, weak, nonatomic) UIView *innerCircleView;
|
||||
@property (nonatomic, strong, nullable) NSLayoutConstraint *heightConstraint;
|
||||
@property (nonatomic, strong, nullable) NSLayoutConstraint *widthConstraint;
|
||||
@property (nonatomic) BOOL respondsToTapGesture;
|
||||
@property (nonatomic, getter = isSelected) BOOL selected;
|
||||
@property (strong, nonatomic) NSLayoutConstraint *innerHeightConstarint;
|
||||
@property (strong, nonatomic) NSLayoutConstraint *innerWidthConstarint;
|
||||
@property (strong, nonatomic) NSLayoutConstraint *outerHeightConstarint;
|
||||
@property (strong, nonatomic) NSLayoutConstraint *outerWidthConstarint;
|
||||
|
||||
// Set line width manually
|
||||
- (void)setCheckMarkLineWidth:(CGFloat)lineWidth;
|
||||
|
||||
@ -12,11 +12,6 @@
|
||||
static CGFloat const DefaultOuterCircleSize = 31;
|
||||
static CGFloat const DefaultInnerCircleSize = 19;
|
||||
|
||||
@interface MFRadioButton ()
|
||||
@property (nullable, weak, nonatomic) UIView *outerCircleView;
|
||||
@property (nullable, weak, nonatomic) UIView *innerCircleView;
|
||||
@end
|
||||
|
||||
|
||||
@implementation MFRadioButton
|
||||
|
||||
@ -64,9 +59,11 @@ static CGFloat const DefaultInnerCircleSize = 19;
|
||||
[outerCircleView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
|
||||
[outerCircleView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
|
||||
[outerCircleView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
|
||||
|
||||
[outerCircleView.widthAnchor constraintEqualToConstant:DefaultOuterCircleSize].active = YES;
|
||||
[outerCircleView.heightAnchor constraintEqualToConstant:DefaultOuterCircleSize].active = YES;
|
||||
|
||||
self.outerHeightConstarint = [outerCircleView.widthAnchor constraintEqualToConstant:DefaultOuterCircleSize];
|
||||
self.outerHeightConstarint.active = YES;
|
||||
self.outerWidthConstarint = [outerCircleView.heightAnchor constraintEqualToConstant:DefaultOuterCircleSize];
|
||||
self.outerWidthConstarint.active = YES;
|
||||
|
||||
UIView *innerCircleView = [[UIView alloc] init];
|
||||
[outerCircleView addSubview:innerCircleView];
|
||||
@ -76,8 +73,11 @@ static CGFloat const DefaultInnerCircleSize = 19;
|
||||
|
||||
[innerCircleView.centerXAnchor constraintEqualToAnchor:outerCircleView.centerXAnchor].active = YES;
|
||||
[innerCircleView.centerYAnchor constraintEqualToAnchor:outerCircleView.centerYAnchor].active = YES;
|
||||
[innerCircleView.widthAnchor constraintEqualToConstant:DefaultInnerCircleSize].active = YES;
|
||||
[innerCircleView.heightAnchor constraintEqualToConstant:DefaultInnerCircleSize].active = YES;
|
||||
|
||||
self.innerHeightConstarint = [innerCircleView.heightAnchor constraintEqualToConstant:DefaultInnerCircleSize];
|
||||
self.innerHeightConstarint.active = YES;
|
||||
self.innerWidthConstarint = [innerCircleView.widthAnchor constraintEqualToConstant:DefaultInnerCircleSize];
|
||||
self.innerWidthConstarint.active = YES;
|
||||
|
||||
outerCircleView.userInteractionEnabled = NO;
|
||||
innerCircleView.userInteractionEnabled = NO;
|
||||
|
||||
@ -39,7 +39,6 @@
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
self.preservesSuperviewLayoutMargins = YES;
|
||||
}
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
@ -47,6 +46,13 @@
|
||||
|
||||
#pragma mark - MVMCoreUIMoleculeViewProtocol
|
||||
|
||||
- (void)setAsMolecule {
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
self.insetsLayoutMarginsFromSafeArea = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
@ -114,6 +114,11 @@
|
||||
} else {
|
||||
self.hidden = YES;
|
||||
}
|
||||
|
||||
NSString *colorString = [json string:KeyBackgroundColor];
|
||||
if (colorString) {
|
||||
self.backgroundColor = [UIColor mfGetColorForHex:colorString];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - helper
|
||||
|
||||
@ -30,8 +30,9 @@
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *topPinLow;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *bottomPinLow;
|
||||
|
||||
/// In updateView, will set horizontal padding to default if set to YES.
|
||||
/// In updateView, will set horizontal padding to default.
|
||||
@property (nonatomic) BOOL updateViewHorizontalDefaults;
|
||||
@property (nonatomic) BOOL updateViewVerticalDefaults;
|
||||
|
||||
/// A molecule if we constrain one.
|
||||
@property (weak, nullable, nonatomic) UIView <MVMCoreUIMoleculeViewProtocol>*molecule;
|
||||
@ -72,6 +73,9 @@
|
||||
// Add a view to be constrained in this view.
|
||||
- (void)addConstrainedView:(nonnull UIView *)view;
|
||||
|
||||
/// Can override to change how the molecule is added when shouldSetupMoleculeFromJSON = true. Inserts the molecule at 0 and calls pinToSuperView.
|
||||
- (void)addMolecule:(nonnull UIView *)molecule;
|
||||
|
||||
// Change the alignment of the label
|
||||
- (void)alignLeft;
|
||||
- (void)alignCenterHorizontal;
|
||||
@ -85,4 +89,7 @@
|
||||
/// Convenience function for getting the alignment from a map.
|
||||
+ (UIStackViewAlignment)getAlignmentForString:(nullable NSString *)alignmentString defaultAlignment:(UIStackViewAlignment)defaultAlignment;
|
||||
|
||||
/// Makes the view isAccessibilityElement false and adds molecule elements to accessbilityElements.
|
||||
- (void)setMoleculeAccessibility;
|
||||
|
||||
@end
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
#import "MFStyler.h"
|
||||
#import "MVMCoreUIConstants.h"
|
||||
#import "MVMCoreUIMoleculeMappingObject.h"
|
||||
#import "MVMCoreUIUtility.h"
|
||||
#import "MVMCoreUIViewConstrainingProtocol.h"
|
||||
|
||||
@interface ViewConstrainingView ()
|
||||
@property (weak, nullable, nonatomic) UIView *constrainedView;
|
||||
@ -28,6 +30,7 @@
|
||||
[self setAsMolecule];
|
||||
}
|
||||
self.molecule = molecule;
|
||||
[self setMoleculeAccessibility];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -238,6 +241,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addMolecule:(nonnull UIView *)molecule {
|
||||
[self insertSubview:molecule atIndex:0];
|
||||
[self pinViewToSuperView:molecule];
|
||||
}
|
||||
|
||||
#pragma mark - MVMCoreUIViewConstrainingProtocol
|
||||
|
||||
- (void)alignHorizontal:(UIStackViewAlignment)alignment {
|
||||
@ -279,21 +287,14 @@
|
||||
}
|
||||
|
||||
- (void)shouldSetHorizontalMargins:(BOOL)shouldSet {
|
||||
self.updateViewHorizontalDefaults = shouldSet;
|
||||
if (![self.json optionalNumberForKey:@"useHorizontalMargins"]) {
|
||||
self.updateViewHorizontalDefaults = shouldSet;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)shouldSetVerticalMargins:(BOOL)shouldSet {
|
||||
BOOL useStandardSpacing = shouldSet;
|
||||
if (shouldSet && [self.molecule respondsToSelector:@selector(useStandardConstraints)]) {
|
||||
useStandardSpacing = [((UIView <MVMCoreUIViewConstrainingProtocol>*)self.molecule) useStandardConstraints];
|
||||
}
|
||||
|
||||
if (useStandardSpacing) {
|
||||
[self setTopPinConstant:PaddingDefaultVerticalSpacing];
|
||||
[self setBottomPinConstant:PaddingDefaultVerticalSpacing];
|
||||
} else {
|
||||
[self setTopPinConstant:0];
|
||||
[self setBottomPinConstant:0];
|
||||
if (![self.json optionalNumberForKey:@"useVerticalMargins"]) {
|
||||
self.updateViewVerticalDefaults = shouldSet;
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,11 +304,7 @@
|
||||
[super setupView];
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
if (@available(iOS 11.0, *)) {
|
||||
self.directionalLayoutMargins = NSDirectionalEdgeInsetsZero;
|
||||
} else {
|
||||
self.layoutMargins = UIEdgeInsetsZero;
|
||||
}
|
||||
[MVMCoreUIUtility setMarginsForView:self leading:0 top:0 trailing:0 bottom:0];
|
||||
}
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
@ -315,47 +312,88 @@
|
||||
if ([self.constrainedView respondsToSelector:@selector(updateView:)]) {
|
||||
[((id<MVMCoreViewProtocol>)self.constrainedView) updateView:size];
|
||||
}
|
||||
if (self.molecule != self.constrainedView) {
|
||||
[self.molecule updateView:size];
|
||||
}
|
||||
[MFStyler setDefaultMarginsForView:self size:size horizontal:self.updateViewHorizontalDefaults vertical:self.updateViewVerticalDefaults];
|
||||
UIEdgeInsets margins = [MVMCoreUIUtility getMarginsForView:self];
|
||||
if (self.updateViewHorizontalDefaults) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
CGFloat padding = [MFStyler defaultHorizontalPaddingForSize:size];
|
||||
[self setLeftPinConstant:padding];
|
||||
[self setRightPinConstant:padding];
|
||||
[MFStyler setDefaultMarginsForView:self size:size];
|
||||
}];
|
||||
[self setLeftPinConstant:margins.left];
|
||||
[self setRightPinConstant:margins.right];
|
||||
}
|
||||
if (self.updateViewVerticalDefaults) {
|
||||
[self setTopPinConstant:margins.top];
|
||||
[self setBottomPinConstant:margins.bottom];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MVMCoreUIMoleculeViewProtocol
|
||||
|
||||
- (void)reset {
|
||||
if ([self.molecule respondsToSelector:@selector(reset)]) {
|
||||
[self.molecule performSelector:@selector(reset)];
|
||||
- (void)setMoleculeAccessibility {
|
||||
if (self.molecule) {
|
||||
self.isAccessibilityElement = NO;
|
||||
if (self.molecule.accessibilityElements) {
|
||||
self.accessibilityElements = self.molecule.accessibilityElements;
|
||||
} else {
|
||||
self.accessibilityElements = @[self.molecule];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAsMolecule {
|
||||
self.updateViewHorizontalDefaults = YES;
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
self.updateViewHorizontalDefaults = NO;
|
||||
self.updateViewVerticalDefaults = NO;
|
||||
if ([self.molecule respondsToSelector:@selector(alignment)]) {
|
||||
[self alignHorizontal:[(UIView <MVMCoreUIViewConstrainingProtocol> *)self.molecule alignment]];
|
||||
}
|
||||
[self alignVertical:UIStackViewAlignmentFill];
|
||||
if ([self.molecule respondsToSelector:@selector(reset)]) {
|
||||
[self.molecule performSelector:@selector(reset)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData {
|
||||
[super setWithJSON:json delegateObject:delegateObject additionalData:additionalData];
|
||||
|
||||
[self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData];
|
||||
if (self.shouldSetupMoleculeFromJSON && !self.molecule) {
|
||||
if (self.shouldSetupMoleculeFromJSON) {
|
||||
NSDictionary *moleculeJSON = [json dict:KeyMolecule];
|
||||
if (moleculeJSON) {
|
||||
if (self.molecule) {
|
||||
[self.molecule setWithJSON:moleculeJSON delegateObject:delegateObject additionalData:additionalData];
|
||||
} else if (moleculeJSON) {
|
||||
UIView <MVMCoreUIMoleculeViewProtocol>*molecule = [[MVMCoreUIMoleculeMappingObject sharedMappingObject] createMoleculeForJSON:moleculeJSON delegateObject:delegateObject constrainIfNeeded:true];
|
||||
if (molecule) {
|
||||
[self insertSubview:molecule atIndex:0];
|
||||
[self pinViewToSuperView:molecule];
|
||||
[self addMolecule:molecule];
|
||||
}
|
||||
self.molecule = molecule;
|
||||
[self setMoleculeAccessibility];
|
||||
}
|
||||
} else {
|
||||
[self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData];
|
||||
}
|
||||
if (self.molecule) {
|
||||
if ([self.molecule respondsToSelector:@selector(copyBackgroundColor)] && [self.molecule performSelector:@selector(copyBackgroundColor)]) {
|
||||
self.backgroundColor = self.molecule.backgroundColor;
|
||||
}
|
||||
|
||||
NSNumber *useHorizontalMargins = [json optionalNumberForKey:@"useHorizontalMargins"];
|
||||
if (useHorizontalMargins) {
|
||||
self.updateViewHorizontalDefaults = [useHorizontalMargins boolValue];
|
||||
}
|
||||
NSNumber *useVerticalMargins = [json optionalNumberForKey:@"useVerticalMargins"];
|
||||
if (useVerticalMargins) {
|
||||
self.updateViewVerticalDefaults = [useVerticalMargins boolValue];
|
||||
}
|
||||
|
||||
// Set the alignment for the stack in the containing view. The json driven value is for the axis direction alignment.
|
||||
NSString *alignment = [json string:@"horizontalAlignment"];
|
||||
if (alignment) {
|
||||
[self alignHorizontal:[ViewConstrainingView getAlignmentForString:alignment defaultAlignment:UIStackViewAlignmentFill]];
|
||||
}
|
||||
alignment = [json string:@"verticalAlignment"];
|
||||
if (alignment) {
|
||||
[self alignVertical:[ViewConstrainingView getAlignmentForString:alignment defaultAlignment:UIStackViewAlignmentFill]];
|
||||
}
|
||||
|
||||
if ([self.molecule respondsToSelector:@selector(copyBackgroundColor)] && [self.molecule performSelector:@selector(copyBackgroundColor)]) {
|
||||
self.backgroundColor = self.molecule.backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -61,6 +61,9 @@
|
||||
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
tableView.delegate = self;
|
||||
tableView.dataSource = self;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
tableView.insetsContentViewsToSafeArea = NO;
|
||||
}
|
||||
if ([tableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)]) {
|
||||
tableView.cellLayoutMarginsFollowReadableWidth = NO;
|
||||
}
|
||||
|
||||
@ -21,23 +21,21 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController {
|
||||
private var topViewBottomConstraint: NSLayoutConstraint?
|
||||
private var bottomViewTopConstraint: NSLayoutConstraint?
|
||||
|
||||
//MARK: - MVMCoreViewProtocol
|
||||
//MARK: - MFViewController
|
||||
open override func updateViews() {
|
||||
super.updateViews()
|
||||
let width = view.bounds.width
|
||||
MFStyler.setDefaultMarginsFor(contentView, size: width)
|
||||
if let topView = topView as? MVMCoreViewProtocol {
|
||||
topView.updateView(width)
|
||||
showHeader()
|
||||
showHeader(width)
|
||||
}
|
||||
if let bottomView = bottomView as? MVMCoreViewProtocol {
|
||||
bottomView.updateView(width)
|
||||
showFooter()
|
||||
showFooter(width)
|
||||
}
|
||||
self.tableView?.reloadData()
|
||||
}
|
||||
|
||||
//MARK: - MFViewController
|
||||
open override func newDataBuildScreen() {
|
||||
super.newDataBuildScreen()
|
||||
createViewForTableHeader()
|
||||
@ -103,14 +101,15 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController {
|
||||
currentSpaceForCompare = currentSpace * 2;
|
||||
}
|
||||
|
||||
let width = view.bounds.width
|
||||
if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 0.1) {
|
||||
if fillTop && fillBottom {
|
||||
// space both
|
||||
let half = newSpace / 2
|
||||
topViewBottomConstraint?.constant = half
|
||||
bottomViewTopConstraint?.constant = half
|
||||
showHeader()
|
||||
showFooter()
|
||||
showHeader(width)
|
||||
showFooter(width)
|
||||
} else if fillTop {
|
||||
// Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one.
|
||||
if bottomViewOutsideOfScrollArea {
|
||||
@ -118,11 +117,11 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController {
|
||||
} else {
|
||||
topViewBottomConstraint?.constant = newSpace
|
||||
}
|
||||
showHeader()
|
||||
showHeader(width)
|
||||
} else if fillBottom {
|
||||
// Only bottom is spaced.
|
||||
bottomViewTopConstraint?.constant = newSpace
|
||||
showFooter()
|
||||
showFooter(width)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,7 +140,7 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController {
|
||||
topViewBottomConstraint = headerView.bottomAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceBelowTopView() ?? 0)
|
||||
topViewBottomConstraint?.isActive = true
|
||||
self.headerView = headerView
|
||||
showHeader()
|
||||
showHeader(nil)
|
||||
}
|
||||
|
||||
/// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter.
|
||||
@ -157,11 +156,11 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController {
|
||||
footerView.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true
|
||||
footerView.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true
|
||||
self.footerView = footerView
|
||||
showFooter()
|
||||
showFooter(nil)
|
||||
}
|
||||
|
||||
/// Takes the current headerView and adds it to the tableHeaderView
|
||||
func showHeader() {
|
||||
func showHeader(_ sizingWidth: CGFloat?) {
|
||||
headerView?.removeFromSuperview()
|
||||
tableView?.tableHeaderView = nil
|
||||
guard let headerView = headerView else {
|
||||
@ -177,7 +176,7 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController {
|
||||
}
|
||||
|
||||
/// Takes the current footerView and adds it to the tableFooterView
|
||||
func showFooter() {
|
||||
func showFooter(_ sizingWidth: CGFloat?) {
|
||||
footerView?.removeFromSuperview()
|
||||
safeAreaView?.removeFromSuperview()
|
||||
guard let footerView = footerView, let tableView = tableView else {
|
||||
|
||||
@ -22,7 +22,7 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController {
|
||||
var bottomViewOutsideOfScroll = false
|
||||
|
||||
private var safeAreaView: UIView?
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
var heightConstraint: NSLayoutConstraint?
|
||||
|
||||
open override func updateViews() {
|
||||
super.updateViews()
|
||||
|
||||
@ -303,8 +303,9 @@
|
||||
}
|
||||
|
||||
- (void)addViewToContentView:(UIView *)bottomView {
|
||||
|
||||
self.bottomConstraint.active = YES;
|
||||
if (![self bottomViewOutsideOfScroll]) {
|
||||
self.bottomConstraint.active = YES;
|
||||
}
|
||||
|
||||
// Buttons will be at the bottom of the content view.
|
||||
[self.contentView addSubview:bottomView];
|
||||
|
||||
@ -108,5 +108,7 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[];
|
||||
#pragma mark - Molecules
|
||||
#import <MVMCoreUI/TopLabelsView.h>
|
||||
#import <MVMCoreUI/MVMCoreUIMoleculeViewProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreUIPagingProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreUIPageControl.h>
|
||||
|
||||
#pragma mark - Templates
|
||||
|
||||
186
MVMCoreUI/Molecules/ActionDetailWithImage.swift
Normal file
186
MVMCoreUI/Molecules/ActionDetailWithImage.swift
Normal file
@ -0,0 +1,186 @@
|
||||
//
|
||||
// ActionDetailWithImage.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Christiano, Kevin on 6/20/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@objcMembers open class ActionDetailWithImage: ViewConstrainingView {
|
||||
//------------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
//------------------------------------------------------
|
||||
|
||||
let title = Label.commonLabelH3(true)
|
||||
let message = Label.commonLabelB3(true)
|
||||
let button = PrimaryButton.primaryTinyButton(false)!
|
||||
let imageLoader = MFLoadImageView(pinnedEdges: .all)
|
||||
let leftContainer = ViewConstrainingView.empty()
|
||||
|
||||
//------------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//------------------------------------------------------
|
||||
|
||||
var bottomTitlePadding: CGFloat = PaddingOne
|
||||
|
||||
//------------------------------------------------------
|
||||
// MARK: - Constraints
|
||||
//------------------------------------------------------
|
||||
|
||||
var imageLeadingConstraint: NSLayoutConstraint?
|
||||
var buttonTopConstraint: NSLayoutConstraint?
|
||||
var messageTopConstraint: NSLayoutConstraint?
|
||||
|
||||
//------------------------------------------------------
|
||||
// MARK: - Initialization
|
||||
//------------------------------------------------------
|
||||
|
||||
public init() {
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
public convenience init(json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
self.init()
|
||||
setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
// MARK: - View Lifecycle
|
||||
//------------------------------------------------------
|
||||
|
||||
override open func setupView() {
|
||||
super.setupView()
|
||||
|
||||
guard subviews.isEmpty else { return }
|
||||
|
||||
setDefaultState()
|
||||
|
||||
addSubview(leftContainer)
|
||||
addSubview(imageLoader)
|
||||
leftContainer.addSubview(title)
|
||||
leftContainer.addSubview(message)
|
||||
leftContainer.addSubview(button)
|
||||
|
||||
leftContainer.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
|
||||
leftContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
|
||||
|
||||
layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: leftContainer.bottomAnchor).isActive = true
|
||||
let leftContainerBottom = leftContainer.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
leftContainerBottom.priority = UILayoutPriority(249)
|
||||
leftContainerBottom.isActive = true
|
||||
|
||||
title.topAnchor.constraint(equalTo: leftContainer.topAnchor).isActive = true
|
||||
title.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true
|
||||
leftContainer.trailingAnchor.constraint(equalTo: title.trailingAnchor).isActive = true
|
||||
|
||||
messageTopConstraint = message.topAnchor.constraint(equalTo: title.bottomAnchor, constant: PaddingOne)
|
||||
messageTopConstraint?.isActive = true
|
||||
|
||||
message.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true
|
||||
leftContainer.trailingAnchor.constraint(equalTo: message.trailingAnchor).isActive = true
|
||||
|
||||
buttonTopConstraint = button.topAnchor.constraint(equalTo: message.bottomAnchor, constant: PaddingTwo)
|
||||
buttonTopConstraint?.isActive = true
|
||||
|
||||
button.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true
|
||||
leftContainer.bottomAnchor.constraint(equalTo: button.bottomAnchor).isActive = true
|
||||
leftContainer.trailingAnchor.constraint(greaterThanOrEqualTo: button.trailingAnchor).isActive = true
|
||||
|
||||
imageLoader.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
layoutMarginsGuide.trailingAnchor.constraint(equalTo: imageLoader.trailingAnchor).isActive = true
|
||||
imageLeadingConstraint = imageLoader.leadingAnchor.constraint(greaterThanOrEqualTo: leftContainer.trailingAnchor, constant: 16)
|
||||
imageLeadingConstraint?.isActive = true
|
||||
imageLoader.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true
|
||||
|
||||
layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: imageLoader.bottomAnchor).isActive = true
|
||||
let imageloaderBottom = imageLoader.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
|
||||
imageloaderBottom.priority = UILayoutPriority(249)
|
||||
imageloaderBottom.isActive = true
|
||||
}
|
||||
|
||||
override open func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
|
||||
title.updateView(size)
|
||||
message.updateView(size)
|
||||
button.updateView(size)
|
||||
imageLoader.updateView(size)
|
||||
leftContainer.updateView(size)
|
||||
|
||||
|
||||
messageTopConstraint?.constant = title.hasText && message.hasText ? bottomTitlePadding : 0
|
||||
buttonTopConstraint?.constant = message.hasText || title.hasText ? PaddingTwo : 0
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return 197
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//------------------------------------------------------
|
||||
|
||||
private func setDefaultState() {
|
||||
|
||||
title.font = MFStyler.fontH3()
|
||||
message.font = MFStyler.fontB3()
|
||||
button.setAsSecondaryCustom()
|
||||
button.isHidden = false
|
||||
imageLoader.imageView.contentMode = .scaleAspectFit
|
||||
imageLoader.addSizeConstraintsForAspectRatio = true
|
||||
bottomTitlePadding = PaddingOne
|
||||
}
|
||||
|
||||
override open func reset() {
|
||||
super.reset()
|
||||
|
||||
title.reset()
|
||||
message.reset()
|
||||
button.reset()
|
||||
imageLeadingConstraint?.constant = 16
|
||||
imageLoader.reset()
|
||||
|
||||
setDefaultState()
|
||||
}
|
||||
|
||||
open override func setAsMolecule() {
|
||||
super.setAsMolecule()
|
||||
|
||||
title.setAsMolecule()
|
||||
message.setAsMolecule()
|
||||
button.setAsMolecule()
|
||||
imageLoader.setAsMolecule()
|
||||
|
||||
setDefaultState()
|
||||
}
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
guard let dictionary = json else { return }
|
||||
|
||||
if let padding = dictionary.optionalCGFloatForKey("bottomTitlePadding") {
|
||||
bottomTitlePadding = padding
|
||||
}
|
||||
|
||||
title.setWithJSON(dictionary.optionalDictionaryForKey("title"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
message.setWithJSON(dictionary.optionalDictionaryForKey("message"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
imageLoader.setWithJSON(dictionary.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
if let buttonDictionary = dictionary.optionalDictionaryForKey("button") {
|
||||
button.setWithJSON(buttonDictionary, delegateObject: delegateObject, additionalData: additionalData)
|
||||
} else {
|
||||
button.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
385
MVMCoreUI/Molecules/Carousel.swift
Normal file
385
MVMCoreUI/Molecules/Carousel.swift
Normal file
@ -0,0 +1,385 @@
|
||||
//
|
||||
// Carousel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 7/2/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class Carousel: ViewConstrainingView {
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
|
||||
|
||||
/// The current index of the collection view. Includes dummy cells when looping.
|
||||
var currentIndex = 0
|
||||
|
||||
/// The index of the page, does not include dummy cells.
|
||||
var pageIndex: Int {
|
||||
get {
|
||||
return loop ? currentIndex - 2 : currentIndex
|
||||
}
|
||||
set(newIndex) {
|
||||
currentIndex = loop ? newIndex + 2 : newIndex
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of pages that there are. Used for the page control and for calculations. Should not include the looping dummy cells. Be sure to set this if subclassing and not using the molecules.
|
||||
var numberOfPages = 0
|
||||
|
||||
/// The json for the molecules.
|
||||
var molecules: [[AnyHashable: Any]]?
|
||||
|
||||
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
|
||||
var itemAlignment = UICollectionView.ScrollPosition.left
|
||||
|
||||
/// From 0-1. The item width as a percent of the carousel width.
|
||||
var itemWidthPercent: CGFloat = 1
|
||||
|
||||
/// The height of the carousel. Default is 300.
|
||||
var collectionViewHeight: NSLayoutConstraint?
|
||||
|
||||
/// The view that we use for paging
|
||||
var pagingView: (UIView & MVMCoreUIPagingProtocol)?
|
||||
|
||||
/// If the carousel should loop after scrolling past the first and final cells.
|
||||
var loop = false
|
||||
private var dragging = false
|
||||
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
guard collectionView.superview == nil else {
|
||||
return
|
||||
}
|
||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
collectionView.dataSource = self
|
||||
collectionView.delegate = self
|
||||
collectionView.showsHorizontalScrollIndicator = false
|
||||
collectionView.backgroundColor = .clear
|
||||
collectionView.isAccessibilityElement = false
|
||||
addSubview(collectionView)
|
||||
pinView(toSuperView: collectionView)
|
||||
|
||||
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
||||
collectionViewHeight?.isActive = false
|
||||
}
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
collectionView.collectionViewLayout.invalidateLayout()
|
||||
showPeaking(false)
|
||||
|
||||
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
|
||||
DispatchQueue.main.async {
|
||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false)
|
||||
self.collectionView.layoutIfNeeded()
|
||||
self.showPeaking(true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
collectionView.backgroundColor = backgroundColor
|
||||
collectionView.layer.borderColor = backgroundColor?.cgColor
|
||||
collectionView.layer.borderWidth = (json?.boolForKey("border") ?? false) ? 1 : 0
|
||||
backgroundColor = .white
|
||||
registerCells(with: json, delegateObject: delegateObject)
|
||||
setupLayout(with: json)
|
||||
prepareMolecules(with: json)
|
||||
itemWidthPercent = (json?.optionalCGFloatForKey("itemWidthPercent") ?? 100) / 100
|
||||
setAlignment(with: json?.optionalStringForKey("itemAlignment"))
|
||||
if let height = json?.optionalCGFloatForKey("height") {
|
||||
collectionViewHeight?.constant = height
|
||||
collectionViewHeight?.isActive = true
|
||||
}
|
||||
setupPagingMolecule(json: json?.optionalDictionaryForKey("pagingMolecule"), delegateObject: delegateObject)
|
||||
collectionView.reloadData()
|
||||
}
|
||||
|
||||
open override func shouldSetHorizontalMargins(_ shouldSet: Bool) {
|
||||
super.shouldSetHorizontalMargins(shouldSet)
|
||||
updateViewHorizontalDefaults = false
|
||||
}
|
||||
|
||||
// MARK: - JSON Setters
|
||||
/// Updates the layout being used
|
||||
func setupLayout(with json:[AnyHashable: Any]?) {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
layout.minimumLineSpacing = json?["spacing"] as? CGFloat ?? 1
|
||||
layout.minimumInteritemSpacing = 0
|
||||
collectionView.collectionViewLayout = layout
|
||||
}
|
||||
|
||||
func prepareMolecules(with json: [AnyHashable: Any]?) {
|
||||
guard let newMolecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] else {
|
||||
numberOfPages = 0
|
||||
molecules = nil
|
||||
return
|
||||
}
|
||||
|
||||
numberOfPages = newMolecules.count
|
||||
molecules = newMolecules
|
||||
if json?.boolForKey("loop") ?? false && newMolecules.count > 2 {
|
||||
// Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell).
|
||||
loop = true
|
||||
molecules?.insert(newMolecules.last!, at: 0)
|
||||
molecules?.insert(newMolecules[(newMolecules.count - 2)], at: 0)
|
||||
molecules?.append(newMolecules.first!)
|
||||
molecules?.append(newMolecules[1])
|
||||
}
|
||||
pageIndex = 0
|
||||
}
|
||||
|
||||
/// Registers the cells with the collection view
|
||||
func registerCells(with json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
if let molecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] {
|
||||
for molecule in molecules {
|
||||
if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) {
|
||||
collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the paging molecule
|
||||
open func setupPagingMolecule(json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) {
|
||||
var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil
|
||||
if let json = json {
|
||||
pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: json, delegateObject: delegateObject, constrainIfNeeded: true) as? (UIView & MVMCoreUIPagingProtocol)
|
||||
}
|
||||
addPaging(view: pagingView, position: (json?.optionalCGFloatForKey("position") ?? 20))
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
/// Returns the (identifier, class) of the molecule for the given map.
|
||||
func getMoleculeInfo(with molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: [AnyHashable: Any])? {
|
||||
guard let molecule = molecule,
|
||||
let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule),
|
||||
let moleculeName = moleculeClass.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) else {
|
||||
return nil
|
||||
}
|
||||
return (moleculeName, moleculeClass, molecule)
|
||||
}
|
||||
|
||||
/// Sets the alignment from the string.
|
||||
open func setAlignment(with string: String?) {
|
||||
switch string {
|
||||
case "leading":
|
||||
itemAlignment = .left
|
||||
case "trailing":
|
||||
itemAlignment = .right
|
||||
case "center":
|
||||
itemAlignment = .centeredHorizontally
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a paging view. Centers it horizontally with the collection view. The position is the vertical distance from the center of the page view to the bottom of the collection view.
|
||||
open func addPaging(view: (UIView & MVMCoreUIPagingProtocol)?, position: CGFloat) {
|
||||
pagingView?.removeFromSuperview()
|
||||
guard let pagingView = view else {
|
||||
bottomPin?.isActive = false
|
||||
bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor)
|
||||
bottomPin?.isActive = true
|
||||
return
|
||||
}
|
||||
pagingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(pagingView)
|
||||
pagingView.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true
|
||||
collectionView.bottomAnchor.constraint(equalTo: pagingView.centerYAnchor, constant: position).isActive = true
|
||||
bottomAnchor.constraint(greaterThanOrEqualTo: pagingView.bottomAnchor).isActive = true
|
||||
bottomPin?.isActive = false
|
||||
bottomPin = bottomAnchor.constraint(equalTo: collectionView.bottomAnchor)
|
||||
bottomPin?.priority = .defaultLow
|
||||
bottomPin?.isActive = true
|
||||
|
||||
pagingView.setNumberOfPages(numberOfPages)
|
||||
(pagingView as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(.fill)
|
||||
pagingView.setPagingTouch { [weak self] (pager) in
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let localSelf = self else {
|
||||
return
|
||||
}
|
||||
let currentPage = pager.currentPage()
|
||||
localSelf.pageIndex = currentPage
|
||||
localSelf.goTo(localSelf.currentIndex, animated: !UIAccessibility.isVoiceOverRunning)
|
||||
})
|
||||
}
|
||||
self.pagingView = pagingView
|
||||
}
|
||||
|
||||
open func showPeaking(_ peaking: Bool) {
|
||||
if peaking && !UIAccessibility.isVoiceOverRunning {
|
||||
// Show overlay and arrow in peaking Cell
|
||||
let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row }
|
||||
if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex {
|
||||
(collectionView.cellForItem(at: firstItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true)
|
||||
}
|
||||
if let lastItem = visibleItemsPaths.last, lastItem.row != currentIndex {
|
||||
(collectionView.cellForItem(at: lastItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true)
|
||||
}
|
||||
} else {
|
||||
// Hide peaking.
|
||||
for item in collectionView.visibleCells {
|
||||
(item as? MoleculeCollectionViewCell)?.setPeaking(false, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) {
|
||||
guard let cell = cell else {
|
||||
return
|
||||
}
|
||||
if index == currentIndex {
|
||||
cell.accessibilityElementsHidden = false
|
||||
var array = cell.accessibilityElements
|
||||
|
||||
if let acc = pagingView?.accessibilityElements {
|
||||
array?.append(contentsOf: acc)
|
||||
} else {
|
||||
array?.append(pagingView!)
|
||||
}
|
||||
|
||||
self.accessibilityElements = array
|
||||
} else {
|
||||
cell.accessibilityElementsHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Carousel: UICollectionViewDelegateFlowLayout {
|
||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||
return CGSize(width: itemWidth, height: collectionView.bounds.height)
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
(cell as? MoleculeCollectionViewCell)?.setPeaking(false, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension Carousel: UICollectionViewDataSource {
|
||||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return molecules?.count ?? 0
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
guard let molecule = molecules?[indexPath.row],
|
||||
let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil) else {
|
||||
return UICollectionViewCell()
|
||||
}
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath)
|
||||
if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol {
|
||||
protocolCell.reset?()
|
||||
protocolCell.setWithJSON(moleculeInfo.molecule, delegateObject: nil, additionalData: nil)
|
||||
protocolCell.updateView(collectionView.bounds.width)
|
||||
}
|
||||
setAccessiblity(cell, index: indexPath.row)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension Carousel: UIScrollViewDelegate {
|
||||
|
||||
func goTo(_ index: Int, animated: Bool) {
|
||||
showPeaking(false)
|
||||
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index)
|
||||
self.currentIndex = index
|
||||
self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: animated)
|
||||
if let cell = collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) {
|
||||
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index)
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: cell)
|
||||
}
|
||||
}
|
||||
|
||||
func handleUserOnBufferCell() {
|
||||
guard loop else {
|
||||
return
|
||||
}
|
||||
|
||||
let lastPageIndex = numberOfPages + 1
|
||||
let goToIndex = {(index: Int) in
|
||||
self.goTo(index, animated: false)
|
||||
self.collectionView.layoutIfNeeded()
|
||||
self.pagingView?.setPage(self.pageIndex)
|
||||
}
|
||||
|
||||
if currentIndex < 2 {
|
||||
// If on a "buffer" last row (which is the first index), go to the real last row secretly. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking.
|
||||
goToIndex(lastPageIndex)
|
||||
} else if currentIndex > lastPageIndex {
|
||||
// If on the "buffer" first row (which is the index after the real last row), go to the real first row secretly.
|
||||
goToIndex(2)
|
||||
}
|
||||
}
|
||||
|
||||
func checkForDraggingOutOfBounds(_ scrollView: UIScrollView) {
|
||||
guard loop, dragging else {
|
||||
return
|
||||
}
|
||||
// Checks if the user is not paging but attempting to drag endlessly and goes out of bounds. Caps the index.
|
||||
if let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing {
|
||||
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||
let index = scrollView.contentOffset.x / (itemWidth + separatorWidth)
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
if index < 1 {
|
||||
self.currentIndex = 0
|
||||
} else if index > CGFloat(lastCellIndex - 1) {
|
||||
self.currentIndex = lastCellIndex
|
||||
}
|
||||
}
|
||||
|
||||
handleUserOnBufferCell()
|
||||
}
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
||||
// Check if the user is dragging the card even further past the next card.
|
||||
checkForDraggingOutOfBounds(scrollView)
|
||||
|
||||
// Let the pager know our progress if needed.
|
||||
pagingView?.scrollViewDidScroll?(collectionView)
|
||||
}
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
dragging = true
|
||||
showPeaking(false)
|
||||
}
|
||||
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
dragging = false
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
|
||||
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size).
|
||||
guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else {
|
||||
return
|
||||
}
|
||||
|
||||
// We switch cards if we pass the velocity threshold or position threshold (currently 50%).
|
||||
let itemWidth = collectionView.bounds.width * itemWidthPercent
|
||||
var cellToSwipeTo = Int(scrollView.contentOffset.x/(itemWidth + separatorWidth) + 0.5)
|
||||
let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1
|
||||
let velocityThreshold: CGFloat = 1.1
|
||||
if velocity.x > velocityThreshold {
|
||||
cellToSwipeTo = currentIndex + 1
|
||||
} else if velocity.x < -velocityThreshold {
|
||||
cellToSwipeTo = currentIndex - 1
|
||||
}
|
||||
|
||||
// Cap the index.
|
||||
goTo(min(max(cellToSwipeTo, 0), lastCellIndex), animated: true)
|
||||
}
|
||||
|
||||
// To give the illusion of endless scrolling. Since we are always calling scrollToItem we can assume finished paging in here.
|
||||
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||
// Cycle to other end if on buffer cell.
|
||||
handleUserOnBufferCell()
|
||||
|
||||
pagingView?.setPage(pageIndex)
|
||||
|
||||
showPeaking(true)
|
||||
}
|
||||
}
|
||||
180
MVMCoreUI/Molecules/CornerLabels.swift
Normal file
180
MVMCoreUI/Molecules/CornerLabels.swift
Normal file
@ -0,0 +1,180 @@
|
||||
//
|
||||
// ProgressBarView.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Panth Patel on 5/3/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objcMembers public class CornerLabels: ViewConstrainingView {
|
||||
|
||||
let topLeftLabel = Label.commonLabelB1(true)
|
||||
let topRightLabel = Label.commonLabelB1(true)
|
||||
let bottomLeftLabel = Label.commonLabelB3(true)
|
||||
let bottomRightLabel = Label.commonLabelB3(true)
|
||||
let topLabelsView = MVMCoreUICommonViewsUtility.commonView()
|
||||
let bottomLabelsView = MVMCoreUICommonViewsUtility.commonView()
|
||||
|
||||
/// The space between the molecule and top labels. Set to 0 if the top labels or molecule are not set.
|
||||
var spaceAboveMolecule: CGFloat = 6.0 {
|
||||
didSet {
|
||||
if spaceAboveMolecule != oldValue {
|
||||
topLabelToMoleculeConstraint?.constant = spaceAboveMolecule
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The space between the molecule and bottom labels. Set to 0 if the bottom labels or molecule are not set.
|
||||
var spaceBelowMolecule: CGFloat = 6.0 {
|
||||
didSet {
|
||||
if spaceBelowMolecule != oldValue {
|
||||
bottomLabelToMoleculeConstraint?.constant = spaceBelowMolecule
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var topLabelToMoleculeConstraint: NSLayoutConstraint?
|
||||
var bottomLabelToMoleculeConstraint: NSLayoutConstraint?
|
||||
|
||||
public override func addMolecule(_ molecule: UIView) {
|
||||
insertSubview(molecule, at: 0)
|
||||
topLabelToMoleculeConstraint?.isActive = false
|
||||
bottomLabelToMoleculeConstraint?.isActive = false
|
||||
|
||||
molecule.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true
|
||||
layoutMarginsGuide.rightAnchor.constraint(equalTo: molecule.rightAnchor).isActive = true
|
||||
|
||||
topLabelToMoleculeConstraint = molecule.topAnchor.constraint(equalTo: topLabelsView.bottomAnchor, constant: spaceAboveMolecule)
|
||||
topLabelToMoleculeConstraint?.isActive = true
|
||||
bottomLabelToMoleculeConstraint = bottomLabelsView.topAnchor.constraint(equalTo: molecule.bottomAnchor, constant: spaceBelowMolecule)
|
||||
bottomLabelToMoleculeConstraint?.isActive = true
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
topLeftLabel.updateView(size)
|
||||
topRightLabel.updateView(size)
|
||||
bottomLeftLabel.updateView(size)
|
||||
bottomRightLabel.updateView(size)
|
||||
}
|
||||
|
||||
public override func setupView() {
|
||||
super.setupView()
|
||||
shouldSetupMoleculeFromJSON = true
|
||||
|
||||
guard topLeftLabel.superview == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
topRightLabel.textAlignment = .right
|
||||
bottomRightLabel.textAlignment = .right
|
||||
|
||||
addSubview(topLabelsView)
|
||||
addSubview(bottomLabelsView)
|
||||
topLabelsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
|
||||
topLabelsView.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true
|
||||
layoutMarginsGuide.rightAnchor.constraint(equalTo: topLabelsView.rightAnchor).isActive = true
|
||||
|
||||
topLabelToMoleculeConstraint = bottomLabelsView.topAnchor.constraint(equalTo: topLabelsView.bottomAnchor, constant: 0)
|
||||
topLabelToMoleculeConstraint?.isActive = true
|
||||
bottomLabelToMoleculeConstraint = topLabelToMoleculeConstraint
|
||||
|
||||
bottomLabelsView.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true
|
||||
layoutMarginsGuide.rightAnchor.constraint(equalTo: bottomLabelsView.rightAnchor).isActive = true
|
||||
layoutMarginsGuide.bottomAnchor.constraint(equalTo: bottomLabelsView.bottomAnchor).isActive = true
|
||||
|
||||
topLabelsView.addSubview(topLeftLabel)
|
||||
topLabelsView.addSubview(topRightLabel)
|
||||
setCompetitionConstraints(topLeftLabel,topRightLabel)
|
||||
|
||||
bottomLabelsView.addSubview(bottomLeftLabel)
|
||||
bottomLabelsView.addSubview(bottomRightLabel)
|
||||
setCompetitionConstraints(bottomLeftLabel,bottomRightLabel)
|
||||
|
||||
topLeftLabel.topAnchor.constraint(equalTo: topLabelsView.topAnchor).isActive = true
|
||||
topLeftLabel.leftAnchor.constraint(equalTo: topLabelsView.leftAnchor).isActive = true
|
||||
topLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: topLeftLabel.bottomAnchor).isActive = true
|
||||
topRightLabel.topAnchor.constraint(equalTo: topLabelsView.topAnchor).isActive = true
|
||||
topLabelsView.rightAnchor.constraint(equalTo: topRightLabel.rightAnchor).isActive = true
|
||||
topLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: topRightLabel.bottomAnchor).isActive = true
|
||||
|
||||
var constraint = topLabelsView.bottomAnchor.constraint(equalTo: topLeftLabel.bottomAnchor)
|
||||
constraint.priority = topLeftLabel.contentHuggingPriority(for: .vertical) - 10
|
||||
constraint.isActive = true
|
||||
|
||||
constraint = topLabelsView.bottomAnchor.constraint(equalTo: topRightLabel.bottomAnchor)
|
||||
constraint.priority = topRightLabel.contentHuggingPriority(for: .vertical) - 10
|
||||
constraint.isActive = true
|
||||
|
||||
topRightLabel.leftAnchor.constraint(equalTo: topLeftLabel.rightAnchor, constant: 16).isActive = true
|
||||
|
||||
bottomLeftLabel.topAnchor.constraint(equalTo: bottomLabelsView.topAnchor).isActive = true
|
||||
bottomLeftLabel.leftAnchor.constraint(equalTo: bottomLabelsView.leftAnchor).isActive = true
|
||||
bottomLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: bottomLeftLabel.bottomAnchor).isActive = true
|
||||
bottomRightLabel.topAnchor.constraint(equalTo: bottomLabelsView.topAnchor).isActive = true
|
||||
bottomRightLabel.rightAnchor.constraint(equalTo: bottomLabelsView.rightAnchor).isActive = true
|
||||
bottomLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: bottomRightLabel.bottomAnchor).isActive = true
|
||||
|
||||
constraint = bottomLabelsView.bottomAnchor.constraint(equalTo: bottomLeftLabel.bottomAnchor)
|
||||
constraint.priority = bottomLeftLabel.contentHuggingPriority(for: .vertical) - 10
|
||||
constraint.isActive = true
|
||||
|
||||
constraint = bottomLabelsView.bottomAnchor.constraint(equalTo: bottomRightLabel.bottomAnchor)
|
||||
constraint.priority = bottomRightLabel.contentHuggingPriority(for: .vertical) - 10
|
||||
constraint.isActive = true
|
||||
|
||||
bottomRightLabel.leftAnchor.constraint(greaterThanOrEqualTo: bottomLeftLabel.rightAnchor, constant: 16).isActive = true
|
||||
}
|
||||
|
||||
/// Sets the rules for the labels if they were to collide. The first view will take priority, but the second view has a minimum width when competing.
|
||||
func setCompetitionConstraints(_ view1: UIView,_ view2: UIView) {
|
||||
view1.setContentHuggingPriority(UILayoutPriority(rawValue: 700), for: .horizontal)
|
||||
view2.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal)
|
||||
view1.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 350), for: .horizontal)
|
||||
view2.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 300), for: .horizontal)
|
||||
let width = view2.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.40)
|
||||
width.priority = UILayoutPriority(rawValue: 400)
|
||||
width.isActive = true
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
topLeftLabel.setWithJSON(json?.optionalDictionaryForKey("topLeftLabel"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
topRightLabel.setWithJSON(json?.optionalDictionaryForKey("topRightLabel"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
bottomLeftLabel.setWithJSON(json?.optionalDictionaryForKey("bottomLeftLabel"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
bottomRightLabel.setWithJSON(json?.optionalDictionaryForKey("bottomRightLabel"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
topLabelToMoleculeConstraint?.constant = (molecule != nil && (topLeftLabel.hasText || topRightLabel.hasText)) ? spaceAboveMolecule : 0
|
||||
bottomLabelToMoleculeConstraint?.constant = (molecule != nil && (bottomLeftLabel.hasText || bottomRightLabel.hasText)) ? spaceBelowMolecule : 0
|
||||
}
|
||||
|
||||
public override func setAsMolecule() {
|
||||
super.setAsMolecule()
|
||||
styleDefault()
|
||||
}
|
||||
|
||||
public override func reset() {
|
||||
super.reset()
|
||||
|
||||
styleDefault()
|
||||
spaceAboveMolecule = 6.0
|
||||
spaceBelowMolecule = 6.0
|
||||
|
||||
molecule?.reset?()
|
||||
}
|
||||
|
||||
func styleDefault() {
|
||||
topLeftLabel.styleB1(true)
|
||||
topRightLabel.styleB1(true)
|
||||
bottomLeftLabel.styleB3(true)
|
||||
bottomRightLabel.styleB3(true)
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return 34
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ import UIKit
|
||||
open class HeadlineBody: ViewConstrainingView {
|
||||
let headlineLabel = Label.commonLabelH2(true)
|
||||
let messageLabel = Label.commonLabelB2(true)
|
||||
var spaceBetweenLabelsConstant = PaddingTwo
|
||||
var spaceBetweenLabels: NSLayoutConstraint?
|
||||
var leftConstraintTitle: NSLayoutConstraint?
|
||||
var rightConstraintTitle: NSLayoutConstraint?
|
||||
@ -44,7 +45,7 @@ open class HeadlineBody: ViewConstrainingView {
|
||||
topPin = headlineLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0)
|
||||
topPin?.isActive = true
|
||||
|
||||
spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: PaddingTwo)
|
||||
spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: spaceBetweenLabelsConstant)
|
||||
spaceBetweenLabels?.isActive = true
|
||||
|
||||
leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
@ -66,7 +67,7 @@ open class HeadlineBody: ViewConstrainingView {
|
||||
// MARK: - Constraining
|
||||
public func setSpacing() {
|
||||
if headlineLabel.hasText && messageLabel.hasText {
|
||||
spaceBetweenLabels?.constant = PaddingTwo
|
||||
spaceBetweenLabels?.constant = spaceBetweenLabelsConstant
|
||||
} else {
|
||||
spaceBetweenLabels?.constant = 0
|
||||
}
|
||||
@ -96,6 +97,7 @@ open class HeadlineBody: ViewConstrainingView {
|
||||
super.reset()
|
||||
headlineLabel.styleH2(true)
|
||||
messageLabel.styleB2(true)
|
||||
spaceBetweenLabelsConstant = PaddingTwo
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
|
||||
85
MVMCoreUI/Molecules/ImageHeadlineBody.swift
Normal file
85
MVMCoreUI/Molecules/ImageHeadlineBody.swift
Normal file
@ -0,0 +1,85 @@
|
||||
//
|
||||
// ImageHeadlineBody.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 8/1/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objcMembers open class ImageHeadlineBody: ViewConstrainingView {
|
||||
let headlineBody = HeadlineBody(frame: .zero)
|
||||
let imageView = MFLoadImageView()
|
||||
var constraintBetweenImageLabelsConstant: CGFloat = 16
|
||||
var constraintBetweenImageLabels: NSLayoutConstraint?
|
||||
|
||||
// MARK: - MFViewProtocol
|
||||
|
||||
open override func setupView() {
|
||||
guard subviews.count == 0 else {
|
||||
return
|
||||
}
|
||||
MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0)
|
||||
headlineBody.headlineLabel.styleB1(true)
|
||||
headlineBody.spaceBetweenLabelsConstant = 0
|
||||
addSubview(headlineBody)
|
||||
addSubview(imageView)
|
||||
|
||||
NSLayoutConstraint.pinViewTop(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true
|
||||
NSLayoutConstraint.pinViewRight(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true
|
||||
layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true
|
||||
var constraint = NSLayoutConstraint.pinViewBottom(toSuperview: headlineBody, useMargins: true, constant: 0)
|
||||
constraint.priority = .defaultLow
|
||||
constraint.isActive = true
|
||||
|
||||
imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||
NSLayoutConstraint.pinViewLeft(toSuperview: imageView, useMargins: true, constant: 0).isActive = true
|
||||
imageView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true
|
||||
layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true
|
||||
constraint = NSLayoutConstraint.pinViewBottom(toSuperview: imageView, useMargins: true, constant: 0)
|
||||
constraint.priority = UILayoutPriority(rawValue: 200)
|
||||
constraint.isActive = true
|
||||
constraint = NSLayoutConstraint.pinViewTop(toSuperview: imageView, useMargins: true, constant: 0)
|
||||
constraint.priority = UILayoutPriority(rawValue: 200)
|
||||
constraint.isActive = true
|
||||
|
||||
constraintBetweenImageLabels = headlineBody.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: constraintBetweenImageLabelsConstant)
|
||||
constraintBetweenImageLabels?.isActive = true
|
||||
}
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
headlineBody.updateView(size)
|
||||
imageView.updateView(size)
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
let imageJSON = json?.optionalDictionaryForKey("image")
|
||||
imageView.setWithJSON(imageJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
constraintBetweenImageLabels?.constant = imageJSON != nil ? constraintBetweenImageLabelsConstant : 0
|
||||
}
|
||||
|
||||
open override func setAsMolecule() {
|
||||
super.setAsMolecule()
|
||||
headlineBody.setAsMolecule()
|
||||
imageView.setAsMolecule()
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
headlineBody.reset()
|
||||
headlineBody.headlineLabel.styleB1(true)
|
||||
headlineBody.spaceBetweenLabelsConstant = 0
|
||||
imageView.reset()
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return 95
|
||||
}
|
||||
}
|
||||
60
MVMCoreUI/Molecules/MVMCoreUIPageControl.h
Normal file
60
MVMCoreUI/Molecules/MVMCoreUIPageControl.h
Normal file
@ -0,0 +1,60 @@
|
||||
//
|
||||
// MVMCoreUIPageControl.h
|
||||
// MobileFirstFramework
|
||||
//
|
||||
// Created by Seshamani, Shreyas on 1/5/18.
|
||||
// Copyright © 2018 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MVMCore/MVMCoreViewProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreUIMoleculeViewProtocol.h>
|
||||
#import <MVMCoreUI/MVMCoreUIPagingProtocol.h>
|
||||
|
||||
@interface MVMCoreUIPageControl : UIControl <MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIPagingProtocol>
|
||||
|
||||
// These properties effectively do what their corresponding namesakes do in UIPageControl
|
||||
@property (nonatomic) NSInteger currentPage;
|
||||
@property (nonatomic) NSInteger numberOfPages;
|
||||
@property (nonatomic, getter=isAnimated) BOOL animated;
|
||||
@property (nullable, strong, nonatomic) UIColor *pageIndicatorTintColor;
|
||||
@property (nullable, strong, nonatomic) UIColor *currentPageIndicatorTintColor;
|
||||
@property (nullable, strong, nonatomic, readonly) NSArray *rectangles;
|
||||
@property (nullable, weak, nonatomic) UIView *containerView;
|
||||
@property (nullable, weak, nonatomic) UIView *indicatorRectangle;
|
||||
@property (nullable, copy, nonatomic) PagingTouchBlock pagingTouchBlock;
|
||||
|
||||
///set YES to make the accessibility value as "Slide #currentPage of #totalPage", otherwise will be "Page #currentPage of #totalPage", default is NO
|
||||
@property (nonatomic) BOOL isSlidesAcc;
|
||||
|
||||
/// This property may be used for indicating the user's selected option (not the currentPage). For instance, in Plan Explore Sizes, it indicates what plan the user is currently on.
|
||||
@property (nonatomic) NSInteger persistentPreselectedPage;
|
||||
|
||||
//customize pagecontrol properties
|
||||
@property (nonatomic) CGFloat rectangleWidth;
|
||||
|
||||
/// Indicates the color of the persistentPreselectedPage
|
||||
@property (nullable, strong, nonatomic) UIColor *persisitentPreselectedPageTintColor;
|
||||
|
||||
//top bottom constraints
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *topConstraint;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *bottomConstraint;
|
||||
|
||||
//a flag to allow to send UIControlEventValueChanged actions all the time
|
||||
//e.g. going to previous element at first place and going to next at last place
|
||||
//While current rectangle won't change, need update current page
|
||||
@property (nonatomic) BOOL alwaysSendingControlEvent;
|
||||
|
||||
- (nullable instancetype)initWithAnimation:(BOOL)animated;
|
||||
- (void)setCurrentPage:(NSInteger)currentPage animated:(BOOL)animated;
|
||||
|
||||
// Sets up the horizontal constraint to have the pages centered horizontally.
|
||||
- (void)setupHorizontalConstraints;
|
||||
|
||||
// For subclassing only.
|
||||
- (void)setupView;
|
||||
- (void)setupRectangles;
|
||||
|
||||
- (void)setTopBottomSpace:(CGFloat)constant;
|
||||
|
||||
@end
|
||||
402
MVMCoreUI/Molecules/MVMCoreUIPageControl.m
Normal file
402
MVMCoreUI/Molecules/MVMCoreUIPageControl.m
Normal file
@ -0,0 +1,402 @@
|
||||
//
|
||||
// MVMCoreUIPageControl.m
|
||||
// MobileFirstFramework
|
||||
//
|
||||
// Created by Seshamani, Shreyas on 1/5/18.
|
||||
// Copyright © 2018 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MVMCoreUIPageControl.h"
|
||||
#import "MVMCoreUICommonViewsUtility.h"
|
||||
#import <MVMCoreUI/StackableViewController.h>
|
||||
#import <MVMCoreUI/UIColor+MFConvenience.h>
|
||||
#import "MVMCoreUIUtility.h"
|
||||
#import "MVMCoreUIConstants.h"
|
||||
@interface MVMCoreUIPageControl ()
|
||||
|
||||
@property (nullable, weak, nonatomic) UIView *animationRectangle;
|
||||
@property (nullable, strong, nonatomic, readwrite) NSArray *rectangles;
|
||||
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *indicatorRectangleLeadingConstraint;
|
||||
|
||||
@property (nonatomic) CGFloat interRectangleSpacing;
|
||||
@property (nonatomic) BOOL isDoAnimating;
|
||||
@property (nonatomic) BOOL isDisAnimating;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MVMCoreUIPageControl
|
||||
|
||||
static CGFloat const DefaultInterRectangleSpacing = 6;
|
||||
static CGFloat const DefaultRectangleWidth = 24;
|
||||
static CGFloat const RectangleHeight = 1;
|
||||
static CGFloat const IndicatorRectangleHeight = 4;
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initValues];
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initValues];
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self initValues];
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAnimation:(BOOL)animated {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initValues];
|
||||
[self setupView];
|
||||
self.animated = animated;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initValues {
|
||||
self.interRectangleSpacing = DefaultInterRectangleSpacing;
|
||||
self.rectangleWidth = DefaultRectangleWidth;
|
||||
self.animated = YES;
|
||||
self.isSlidesAcc = NO;
|
||||
}
|
||||
|
||||
- (void)setCurrentPage:(NSInteger)currentPage {
|
||||
[self setCurrentPage:currentPage animated:self.animated];
|
||||
}
|
||||
|
||||
- (void)setCurrentPage:(NSInteger)currentPage animated:(BOOL)animated {
|
||||
if (_currentPage!=currentPage) {
|
||||
_currentPage = currentPage;
|
||||
if (currentPage >= 0 && currentPage < self.numberOfPages) {
|
||||
self.animationRectangle.frame = self.indicatorRectangle.frame;
|
||||
self.indicatorRectangleLeadingConstraint.constant = (_currentPage * (self.rectangleWidth + self.interRectangleSpacing) + self.interRectangleSpacing);
|
||||
[self displayIndicator:animated];
|
||||
[self dismissAnimationIndicator:animated];
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setNumberOfPages:(NSInteger)numberOfPages {
|
||||
if (_numberOfPages != numberOfPages) {
|
||||
_numberOfPages = numberOfPages;
|
||||
[self updateRectangleWidthAndSpacing];
|
||||
[self setupRectangles];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPersistentPreselectedPage:(NSInteger)persistentPreselectedPage {
|
||||
_persistentPreselectedPage = persistentPreselectedPage;
|
||||
|
||||
if (!self.rectangles) {
|
||||
[self setupRectangles];
|
||||
}
|
||||
|
||||
if (persistentPreselectedPage >= 0 && persistentPreselectedPage < self.numberOfPages) {
|
||||
UIView *persistentRectangle = self.rectangles[persistentPreselectedPage];
|
||||
persistentRectangle.backgroundColor = self.persisitentPreselectedPageTintColor;
|
||||
}
|
||||
}
|
||||
|
||||
@synthesize currentPageIndicatorTintColor = _currentPageIndicatorTintColor;
|
||||
|
||||
- (UIColor *)currentPageIndicatorTintColor {
|
||||
if (!_currentPageIndicatorTintColor) {
|
||||
_currentPageIndicatorTintColor = [UIColor blackColor];
|
||||
}
|
||||
return _currentPageIndicatorTintColor;
|
||||
}
|
||||
|
||||
- (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColor {
|
||||
|
||||
_currentPageIndicatorTintColor = currentPageIndicatorTintColor;
|
||||
if (!self.indicatorRectangle) {
|
||||
[self setupRectangles];
|
||||
}
|
||||
self.indicatorRectangle.backgroundColor = currentPageIndicatorTintColor;
|
||||
self.animationRectangle.backgroundColor = currentPageIndicatorTintColor;
|
||||
}
|
||||
|
||||
@synthesize pageIndicatorTintColor = _pageIndicatorTintColor;
|
||||
|
||||
- (UIColor *)pageIndicatorTintColor {
|
||||
if (!_pageIndicatorTintColor) {
|
||||
return [UIColor mfBattleshipGrey];
|
||||
}
|
||||
return _pageIndicatorTintColor;
|
||||
}
|
||||
|
||||
- (void)setPageIndicatorTintColor:(UIColor *)pageIndicatorTintColor {
|
||||
_pageIndicatorTintColor = pageIndicatorTintColor;
|
||||
if (!self.rectangles) {
|
||||
[self setupRectangles];
|
||||
}
|
||||
for (UIView *rectangle in self.rectangles) {
|
||||
rectangle.backgroundColor = pageIndicatorTintColor;
|
||||
}
|
||||
}
|
||||
|
||||
@synthesize persisitentPreselectedPageTintColor = _persisitentPreselectedPageTintColor;
|
||||
|
||||
- (UIColor *)persisitentPreselectedPageTintColor {
|
||||
if (!_persisitentPreselectedPageTintColor) {
|
||||
_persisitentPreselectedPageTintColor = [UIColor mfCerulean];
|
||||
}
|
||||
return _persisitentPreselectedPageTintColor;
|
||||
}
|
||||
|
||||
- (void)setPersisitentPreselectedPageTintColor:(UIColor *)persisitentPreselectedPageTintColor {
|
||||
_persisitentPreselectedPageTintColor = persisitentPreselectedPageTintColor;
|
||||
|
||||
if (!self.rectangles) {
|
||||
[self setupRectangles];
|
||||
}
|
||||
|
||||
if (self.persistentPreselectedPage >= 0 && self.persistentPreselectedPage < self.numberOfPages) {
|
||||
UIView *persistentRectangle = self.rectangles[self.persistentPreselectedPage];
|
||||
persistentRectangle.backgroundColor = persisitentPreselectedPageTintColor;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
- (void)updateRectangleWidthAndSpacing {
|
||||
|
||||
CGFloat screenWidth = [MVMCoreUIUtility getWidth];
|
||||
CGFloat pageControlRequiredWidth = self.numberOfPages * (self.rectangleWidth + self.interRectangleSpacing) + DefaultInterRectangleSpacing;
|
||||
|
||||
if (pageControlRequiredWidth > screenWidth) {
|
||||
// the Inter rectangle spacing is a quarter of the rectangle width
|
||||
self.interRectangleSpacing = screenWidth / ((self.numberOfPages + 1) + (self.numberOfPages * 4));
|
||||
self.rectangleWidth = self.interRectangleSpacing * 4;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupRectangles {
|
||||
|
||||
// Create the rectangles for all the indexes
|
||||
[self removeRectangles];
|
||||
NSMutableArray *rectangles = [[NSMutableArray alloc] init];
|
||||
for (int index = 0; index < self.numberOfPages; index++) {
|
||||
|
||||
UIView *rectangle = [MVMCoreUICommonViewsUtility commonView];
|
||||
[rectangles addObject:rectangle];
|
||||
rectangle.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
rectangle.frame = CGRectMake((self.interRectangleSpacing * (index + 1)) + (self.rectangleWidth * index), IndicatorRectangleHeight- RectangleHeight, self.rectangleWidth, RectangleHeight);
|
||||
[rectangle.heightAnchor constraintEqualToConstant:RectangleHeight].active = YES;
|
||||
[rectangle.widthAnchor constraintEqualToConstant:self.rectangleWidth].active = YES;
|
||||
rectangle.backgroundColor = self.pageIndicatorTintColor;
|
||||
}
|
||||
|
||||
[StackableViewController populateViewHorizontally:self.containerView withUIArray:rectangles withSpacingBlock:^UIEdgeInsets(id _Nullable object) {
|
||||
|
||||
if (object == [rectangles lastObject]) {
|
||||
return UIEdgeInsetsMake(RectangleHeight, self.interRectangleSpacing, 0, self.interRectangleSpacing);
|
||||
}
|
||||
return UIEdgeInsetsMake(RectangleHeight, self.interRectangleSpacing, 0, 0);
|
||||
}];
|
||||
self.rectangles = rectangles;
|
||||
|
||||
// Create the indicator rectangle
|
||||
UIView *indicatorRectangle = [MVMCoreUICommonViewsUtility commonView];
|
||||
[self.containerView addSubview:indicatorRectangle];
|
||||
indicatorRectangle.backgroundColor = self.currentPageIndicatorTintColor;
|
||||
[indicatorRectangle.heightAnchor constraintEqualToConstant:IndicatorRectangleHeight].active = YES;
|
||||
[indicatorRectangle.widthAnchor constraintEqualToConstant:self.rectangleWidth].active = YES;
|
||||
[indicatorRectangle.topAnchor constraintGreaterThanOrEqualToAnchor:self.containerView.topAnchor].active = YES;
|
||||
[indicatorRectangle.bottomAnchor constraintEqualToAnchor:self.containerView.bottomAnchor].active = YES;
|
||||
self.indicatorRectangleLeadingConstraint = [indicatorRectangle.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor constant:self.interRectangleSpacing];
|
||||
self.indicatorRectangleLeadingConstraint.active = YES;
|
||||
self.indicatorRectangle = indicatorRectangle;
|
||||
indicatorRectangle.frame = CGRectMake(self.interRectangleSpacing, 0, self.rectangleWidth, IndicatorRectangleHeight);
|
||||
|
||||
// Create shadow indicator rectangle for animaiton
|
||||
UIView *animatedIndicatorRectangle = [MVMCoreUICommonViewsUtility commonView];
|
||||
[self.containerView addSubview:animatedIndicatorRectangle];
|
||||
animatedIndicatorRectangle.backgroundColor = self.currentPageIndicatorTintColor;
|
||||
self.animationRectangle = animatedIndicatorRectangle;
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
|
||||
if (!self.containerView) {
|
||||
self.isAccessibilityElement = YES;
|
||||
|
||||
// Create a container view that keeps everything centered
|
||||
UIView *containerView = [MVMCoreUICommonViewsUtility commonView];
|
||||
[self addSubview:containerView];
|
||||
self.containerView = containerView;
|
||||
[self setupHorizontalConstraints];
|
||||
|
||||
NSLayoutConstraint *topConstraint = [containerView.topAnchor constraintEqualToAnchor:self.topAnchor constant:PaddingThree];
|
||||
topConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
topConstraint.active = YES;
|
||||
self.topConstraint = topConstraint;
|
||||
|
||||
NSLayoutConstraint *bottomConstraint = [self.bottomAnchor constraintEqualToAnchor:containerView.bottomAnchor constant:PaddingThree];
|
||||
bottomConstraint.priority = UILayoutPriorityDefaultHigh;
|
||||
bottomConstraint.active = YES;
|
||||
self.bottomConstraint = bottomConstraint;
|
||||
|
||||
[self updateRectangleWidthAndSpacing];
|
||||
|
||||
// Create the rectangles for all the indexes
|
||||
[self setupRectangles];
|
||||
|
||||
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] init];
|
||||
[tapGesture addTarget:self action:@selector(rectangleTapped:)];
|
||||
[self addGestureRecognizer:tapGesture];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupHorizontalConstraints {
|
||||
[self.containerView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor].active = YES;
|
||||
[self.containerView.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor].active = YES;
|
||||
[self.containerView.trailingAnchor constraintLessThanOrEqualToAnchor:self.trailingAnchor].active = YES;
|
||||
}
|
||||
|
||||
- (void)removeRectangles {
|
||||
for (UIView *subview in self.containerView.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)rectangleTapped:(UITapGestureRecognizer *)tapGesture {
|
||||
if (self.userInteractionEnabled) {
|
||||
CGPoint touchPoint = [tapGesture locationInView:self.containerView];
|
||||
NSInteger selectedIndex = [self.rectangles indexOfObjectPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
UIView *subview = self.rectangles[idx];
|
||||
if (CGRectGetMaxX(subview.frame) >= touchPoint.x && CGRectGetMinX(subview.frame) <= touchPoint.x) {
|
||||
*stop = YES;
|
||||
}
|
||||
return *stop;
|
||||
}];
|
||||
if (selectedIndex != NSNotFound) {
|
||||
self.currentPage = selectedIndex;
|
||||
[self sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
if (self.pagingTouchBlock) {
|
||||
self.pagingTouchBlock(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MVMCoreUIPagingProtocol
|
||||
|
||||
- (void)setPage:(NSInteger)page {
|
||||
self.currentPage = page;
|
||||
}
|
||||
|
||||
#pragma mark - MoleculeViewProtocol
|
||||
|
||||
- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData {
|
||||
NSString *colorString = [json string:KeyBackgroundColor];
|
||||
if (colorString) {
|
||||
self.backgroundColor = [UIColor mfGetColorForHex:colorString];
|
||||
}
|
||||
colorString = [json string:@"barsColor"];
|
||||
if (colorString) {
|
||||
UIColor *color = [UIColor mfGetColorForHex:colorString];
|
||||
self.pageIndicatorTintColor = color;
|
||||
self.currentPageIndicatorTintColor = color;
|
||||
}
|
||||
colorString = [json string:@"currentBarColor"];
|
||||
if (colorString) {
|
||||
self.currentPageIndicatorTintColor = [UIColor mfGetColorForHex:colorString];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
- (UIAccessibilityTraits)accessibilityTraits {
|
||||
return UIAccessibilityTraitAdjustable;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityValue {
|
||||
NSString *stringKey = self.isSlidesAcc ? @"MVMCoreUIPageControlslides_currentpage_index" : @"MVMCoreUIPageControl_currentpage_index";
|
||||
return [NSString stringWithFormat:[MVMCoreUIUtility hardcodedStringWithKey:stringKey],self.currentPage+1, self.numberOfPages];
|
||||
}
|
||||
|
||||
- (void)accessibilityIncrement {
|
||||
[self accessibilityAdjustToPage:self.currentPage + 1];
|
||||
}
|
||||
|
||||
- (void)accessibilityDecrement {
|
||||
[self accessibilityAdjustToPage:self.currentPage -1];
|
||||
}
|
||||
|
||||
//when self.awlaysSenfingControlEven it NO, and user is already at first or final index, if user try to increment or decrement, won't do action
|
||||
//while self.awlaysSenfingControlEven is YES, it still send control event, while the rectangle won't change, need set currentPage again.
|
||||
- (void)accessibilityAdjustToPage:(NSInteger)index {
|
||||
if ((index < self.numberOfPages && index >= 0) || self.alwaysSendingControlEvent) {
|
||||
[self setCurrentPage:index animated:NO];
|
||||
[self sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
if (self.pagingTouchBlock) {
|
||||
self.pagingTouchBlock(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Animate pagecontrol indicator
|
||||
|
||||
- (void)displayIndicator:(BOOL)animated {
|
||||
if (!animated) {
|
||||
return;
|
||||
}
|
||||
if (!self.isDoAnimating) {
|
||||
self.indicatorRectangle.frame = CGRectMake(CGRectGetMinX(self.indicatorRectangle.frame), CGRectGetMinY(self.indicatorRectangle.frame) + IndicatorRectangleHeight, CGRectGetWidth(self.indicatorRectangle.frame), 0.f);
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.isDoAnimating = YES;
|
||||
self.indicatorRectangle.frame = CGRectMake(CGRectGetMinX(self.indicatorRectangle.frame), CGRectGetMinY(self.indicatorRectangle.frame)-IndicatorRectangleHeight, CGRectGetWidth(self.indicatorRectangle.frame), IndicatorRectangleHeight);
|
||||
} completion:^(BOOL finished) {
|
||||
self.isDoAnimating = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismissAnimationIndicator:(BOOL)animated {
|
||||
if (animated) {
|
||||
if (!self.isDisAnimating) {
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.isDisAnimating = YES;
|
||||
self.animationRectangle.frame = CGRectMake(CGRectGetMinX(self.animationRectangle.frame), CGRectGetMinY(self.animationRectangle.frame) + IndicatorRectangleHeight, CGRectGetWidth(self.animationRectangle.frame), 0.f);
|
||||
} completion:^(BOOL finished) {
|
||||
[self layoutIfNeeded];
|
||||
self.animationRectangle.frame = self.indicatorRectangle.frame;
|
||||
self.isDisAnimating = NO;
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
[self layoutIfNeeded];
|
||||
self.animationRectangle.frame = self.indicatorRectangle.frame;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTopBottomSpace:(CGFloat)constant {
|
||||
self.bottomConstraint.constant = constant;
|
||||
self.topConstraint.constant = constant;
|
||||
}
|
||||
|
||||
@end
|
||||
25
MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h
Normal file
25
MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// MVMCoreUIPagingProtocol.h
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 7/10/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol MVMCoreUIPagingProtocol <NSObject>
|
||||
|
||||
typedef void (^PagingTouchBlock)(NSObject<MVMCoreUIPagingProtocol>* _Nonnull sender);
|
||||
|
||||
- (NSInteger)currentPage;
|
||||
|
||||
- (void)setNumberOfPages:(NSInteger)numberOfPages;
|
||||
|
||||
- (void)setPage:(NSInteger)page;
|
||||
|
||||
- (void)setPagingTouchBlock:(nullable PagingTouchBlock)pagingTouchBlock;
|
||||
|
||||
@optional
|
||||
- (void)scrollViewDidScroll:(nonnull UICollectionView *)collectionView;
|
||||
|
||||
@end
|
||||
@ -31,6 +31,13 @@ open class ModuleMolecule: ViewConstrainingView {
|
||||
addSubview(moleculeView)
|
||||
NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: false).values))
|
||||
moduleMolecule = moleculeView
|
||||
|
||||
isAccessibilityElement = false
|
||||
if moleculeView.accessibilityElements != nil {
|
||||
accessibilityElements = moleculeView.accessibilityElements
|
||||
} else {
|
||||
accessibilityElements = [moleculeView]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
moduleMolecule?.setWithJSON(module, delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
147
MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift
Normal file
147
MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift
Normal file
@ -0,0 +1,147 @@
|
||||
//
|
||||
// MoleculeCollectionViewCell.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 7/2/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol {
|
||||
open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)?
|
||||
open var json: [AnyHashable: Any]?
|
||||
|
||||
// In updateView, will set padding to default.
|
||||
open var updateViewHorizontalDefaults = true
|
||||
open var updateViewVerticalDefaults = true
|
||||
|
||||
open var allowsPeaking = false
|
||||
var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate))
|
||||
var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate))
|
||||
var peakingCover = MVMCoreUICommonViewsUtility.commonView()
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
setupView()
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setupView()
|
||||
}
|
||||
|
||||
public func setupView() {
|
||||
guard peakingCover.superview == nil else {
|
||||
return
|
||||
}
|
||||
isAccessibilityElement = false
|
||||
contentView.isAccessibilityElement = false
|
||||
if #available(iOS 11.0, *) {
|
||||
insetsLayoutMarginsFromSafeArea = false
|
||||
contentView.insetsLayoutMarginsFromSafeArea = false
|
||||
contentView.preservesSuperviewLayoutMargins = false
|
||||
}
|
||||
|
||||
// Covers the card when peaking.
|
||||
peakingCover.backgroundColor = .white
|
||||
peakingCover.alpha = 0
|
||||
contentView.addSubview(peakingCover)
|
||||
NSLayoutConstraint.constraintPinSubview(toSuperview: peakingCover)
|
||||
|
||||
// A small arrow on the next card for when peaking.
|
||||
let ratio: CGFloat = 0.015
|
||||
peakingLeftArrow.translatesAutoresizingMaskIntoConstraints = false
|
||||
peakingLeftArrow.alpha = 0
|
||||
peakingLeftArrow.tintColor = .black
|
||||
contentView.addSubview(peakingLeftArrow)
|
||||
peakingLeftArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
|
||||
NSLayoutConstraint.scalingPinViewLeft(toSuper: peakingLeftArrow, ratio: ratio, anchor: contentView.widthAnchor)
|
||||
|
||||
peakingRightArrow.translatesAutoresizingMaskIntoConstraints = false
|
||||
peakingRightArrow.transform = CGAffineTransform(scaleX: -1, y: 1) // Flip
|
||||
peakingRightArrow.alpha = 0
|
||||
peakingRightArrow.tintColor = .black
|
||||
contentView.addSubview(peakingRightArrow)
|
||||
peakingRightArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
|
||||
NSLayoutConstraint.scalingPinViewRight(toSuper: peakingRightArrow, ratio: ratio, anchor: contentView.widthAnchor)
|
||||
}
|
||||
|
||||
public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
self.json = json
|
||||
|
||||
if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") {
|
||||
updateViewHorizontalDefaults = useHorizontalMargins
|
||||
}
|
||||
if let useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") {
|
||||
updateViewVerticalDefaults = useVerticalMargins
|
||||
}
|
||||
|
||||
// Handles peaking.
|
||||
allowsPeaking = json?.optionalBoolForKey("peakingUI") ?? true
|
||||
if let peakingArrowColor = json?.optionalStringForKey("peakingArrowColor") {
|
||||
let color = UIColor.mfGet(forHex: peakingArrowColor)
|
||||
peakingLeftArrow.tintColor = color
|
||||
peakingRightArrow.tintColor = color
|
||||
}
|
||||
|
||||
if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorString)
|
||||
}
|
||||
|
||||
guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else {
|
||||
return
|
||||
}
|
||||
if molecule == nil {
|
||||
if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) {
|
||||
contentView.insertSubview(moleculeView, at: 0)
|
||||
NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: true).values))
|
||||
molecule = moleculeView
|
||||
}
|
||||
} else {
|
||||
molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
// This molecule will handle spacing by default.
|
||||
if let castView = molecule as? MVMCoreUIViewConstrainingProtocol {
|
||||
castView.shouldSetHorizontalMargins?(false)
|
||||
castView.shouldSetVerticalMargins?(false)
|
||||
}
|
||||
|
||||
accessibilityElements = molecule?.subviews
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
molecule?.reset?()
|
||||
updateViewVerticalDefaults = true
|
||||
updateViewHorizontalDefaults = true
|
||||
backgroundColor = .white
|
||||
}
|
||||
|
||||
public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? {
|
||||
guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else {
|
||||
return nil
|
||||
}
|
||||
return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName)
|
||||
}
|
||||
|
||||
public func updateView(_ size: CGFloat) {
|
||||
molecule?.updateView(size)
|
||||
MFStyler.setDefaultMarginsFor(contentView, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults)
|
||||
}
|
||||
|
||||
public func setPeaking(_ peaking: Bool, animated: Bool) {
|
||||
guard allowsPeaking else {
|
||||
return
|
||||
}
|
||||
let animation = {() in
|
||||
self.peakingRightArrow.alpha = peaking ? 1 : 0
|
||||
self.peakingLeftArrow.alpha = peaking ? 1 : 0
|
||||
self.peakingCover.alpha = peaking ? 0.5 : 0
|
||||
}
|
||||
if animated {
|
||||
UIView.animate(withDuration: 0.4, animations: animation)
|
||||
} else {
|
||||
animation()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,13 +41,12 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
var items: [StackItem] = []
|
||||
var useStackSpacingBeforeFirstItem = false
|
||||
|
||||
private var moleculesShouldSetHorizontalMargins = true
|
||||
private var moleculesShouldSetHorizontalMargins = false
|
||||
private var moleculesShouldSetVerticalMargins = false
|
||||
|
||||
/// For setting the direction of the stack
|
||||
var axis: NSLayoutConstraint.Axis = .vertical {
|
||||
didSet {
|
||||
moleculesShouldSetHorizontalMargins = (moleculesShouldSetHorizontalMargins && axis == .vertical)
|
||||
if axis != oldValue {
|
||||
restack()
|
||||
}
|
||||
@ -109,6 +108,8 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
guard contentView.superview == nil else {
|
||||
return
|
||||
}
|
||||
MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0)
|
||||
updateViewHorizontalDefaults = true
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
backgroundColor = .clear
|
||||
addSubview(contentView)
|
||||
@ -125,12 +126,10 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
public override func setAsMolecule() {
|
||||
updateViewHorizontalDefaults = false
|
||||
}
|
||||
|
||||
public override func reset() {
|
||||
super.reset()
|
||||
backgroundColor = .clear
|
||||
updateViewHorizontalDefaults = true
|
||||
for item in items {
|
||||
if let view = item.view as? MVMCoreUIMoleculeViewProtocol {
|
||||
view.reset?()
|
||||
@ -138,22 +137,6 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
}
|
||||
}
|
||||
|
||||
public override func shouldSetHorizontalMargins(_ shouldSet: Bool) {
|
||||
super.shouldSetHorizontalMargins(shouldSet)
|
||||
moleculesShouldSetHorizontalMargins = (shouldSet && axis == .vertical)
|
||||
for item in items {
|
||||
(item.view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(moleculesShouldSetHorizontalMargins)
|
||||
}
|
||||
}
|
||||
|
||||
public override func shouldSetVerticalMargins(_ shouldSet: Bool) {
|
||||
super.shouldSetVerticalMargins(shouldSet)
|
||||
moleculesShouldSetVerticalMargins = false
|
||||
for item in items {
|
||||
(item.view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(moleculesShouldSetVerticalMargins)
|
||||
}
|
||||
}
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
let previousJSON = self.json
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
@ -174,15 +157,6 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
setAxisWithJSON(json)
|
||||
spacing = json?.optionalCGFloatForKey("spacing") ?? 16
|
||||
|
||||
// Set the alignment for the stack in the containing view. The json driven value is for the axis direction alignment.
|
||||
if axis == .vertical {
|
||||
alignHorizontal(.fill)
|
||||
alignVertical(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: .fill))
|
||||
} else {
|
||||
alignHorizontal(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: .fill))
|
||||
alignVertical(.fill)
|
||||
}
|
||||
|
||||
// Adds the molecules and sets the json.
|
||||
for (index, map) in molecules.enumerated() {
|
||||
if let moleculeJSON = map.optionalDictionaryForKey(KeyMolecule) {
|
||||
|
||||
@ -13,6 +13,10 @@ import UIKit
|
||||
open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)?
|
||||
open var json: [AnyHashable: Any]?
|
||||
|
||||
// In updateView, will set padding to default.
|
||||
open var updateViewHorizontalDefaults = true
|
||||
open var updateViewVerticalDefaults = true
|
||||
|
||||
// For the accessory view convenience.
|
||||
public var caretView: CaretView?
|
||||
private var caretViewWidthSizeObject: MFSizeObject?
|
||||
@ -41,7 +45,7 @@ import UIKit
|
||||
|
||||
// MARK: - MFViewProtocol
|
||||
public func updateView(_ size: CGFloat) {
|
||||
MFStyler.setDefaultMarginsFor(self, size: size, horizontal: true, vertical: true)
|
||||
MFStyler.setDefaultMarginsFor(self, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults)
|
||||
if #available(iOS 11.0, *) {
|
||||
if accessoryView != nil {
|
||||
// Smaller left margin if accessory view.
|
||||
@ -75,14 +79,42 @@ import UIKit
|
||||
}
|
||||
|
||||
public func setupView() {
|
||||
preservesSuperviewLayoutMargins = false
|
||||
contentView.preservesSuperviewLayoutMargins = false
|
||||
selectionStyle = .none
|
||||
if #available(iOS 11.0, *) {
|
||||
insetsLayoutMarginsFromSafeArea = false
|
||||
contentView.insetsLayoutMarginsFromSafeArea = false
|
||||
contentView.preservesSuperviewLayoutMargins = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
self.json = json;
|
||||
|
||||
if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") {
|
||||
updateViewHorizontalDefaults = useHorizontalMargins
|
||||
}
|
||||
if let useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") {
|
||||
updateViewVerticalDefaults = useVerticalMargins
|
||||
}
|
||||
|
||||
if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorString)
|
||||
}
|
||||
|
||||
// Add the caret if there is an action and it's not declared hidden.
|
||||
if let _ = json?.optionalDictionaryForKey("actionMap"), !json!.boolForKey("hideArrow") {
|
||||
addCaretViewAccessory()
|
||||
} else {
|
||||
accessoryView = nil
|
||||
}
|
||||
|
||||
// override the separator
|
||||
if let separator = json?.optionalDictionaryForKey("separator") {
|
||||
addSeparatorsIfNeeded()
|
||||
bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else {
|
||||
return
|
||||
}
|
||||
@ -91,47 +123,31 @@ import UIKit
|
||||
contentView.addSubview(moleculeView)
|
||||
let standardConstraints = (moleculeView as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true
|
||||
NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: standardConstraints).values))
|
||||
if standardConstraints {
|
||||
let constraint = contentView.heightAnchor.constraint(equalToConstant: 80)
|
||||
constraint.priority = .defaultLow
|
||||
constraint.isActive = true
|
||||
}
|
||||
molecule = moleculeView
|
||||
}
|
||||
} else {
|
||||
molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
// This molecule will by default handle margins.
|
||||
if let castView = molecule as? MVMCoreUIViewConstrainingProtocol {
|
||||
let standardConstraints = castView.useStandardConstraints?() ?? true
|
||||
castView.shouldSetHorizontalMargins?(!standardConstraints)
|
||||
castView.shouldSetVerticalMargins?(!standardConstraints)
|
||||
}
|
||||
|
||||
backgroundColor = molecule?.backgroundColor
|
||||
|
||||
// Add the caret if there is an action and it's not declared hidden.
|
||||
if let _ = json.optionalDictionaryForKey("actionMap"), !json.boolForKey("hideArrow") {
|
||||
addCaretViewAccessory()
|
||||
} else {
|
||||
accessoryView = nil
|
||||
}
|
||||
|
||||
// override the separator
|
||||
if let separator = json.optionalDictionaryForKey("separator") {
|
||||
addSeparatorsIfNeeded()
|
||||
bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData)
|
||||
castView.shouldSetHorizontalMargins?(false)
|
||||
castView.shouldSetVerticalMargins?(false)
|
||||
}
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
molecule?.reset?()
|
||||
updateViewVerticalDefaults = true
|
||||
updateViewHorizontalDefaults = true
|
||||
backgroundColor = .white
|
||||
}
|
||||
|
||||
public static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) else {
|
||||
return 80
|
||||
}
|
||||
return max(80, height)
|
||||
return max(2 * PaddingDefaultVerticalSpacing3, height)
|
||||
}
|
||||
|
||||
public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? {
|
||||
|
||||
@ -8,41 +8,73 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public class ProgressBar: UIProgressView {
|
||||
|
||||
var isRounded = Bool()
|
||||
var thickness : Float = 0.0 {
|
||||
@objcMembers open class ProgressBar: UIProgressView, MVMCoreUIMoleculeViewProtocol, MVMCoreViewProtocol {
|
||||
var isRounded = false
|
||||
var thickness: CGFloat = 8.0 {
|
||||
willSet(newValue) {
|
||||
heightAnchor.constraint(equalToConstant: CGFloat(newValue)).isActive = true
|
||||
switch isRounded {
|
||||
case true:
|
||||
layer.cornerRadius = CGFloat(newValue/2)
|
||||
clipsToBounds = true
|
||||
default:
|
||||
heightAnchor.constraint(equalToConstant: newValue).isActive = true
|
||||
if isRounded {
|
||||
layer.cornerRadius = newValue/2.0
|
||||
} else {
|
||||
progressViewStyle = .bar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupView()
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setupView()
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
setupView()
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
public func setupView() {
|
||||
clipsToBounds = true
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
reset()
|
||||
}
|
||||
|
||||
public func updateView(_ size: CGFloat) {
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
isRounded = json?.optionalBoolForKey("roundedRect") ?? false
|
||||
thickness = json?.floatFromStringForKey("thickness") ?? Float(0.0)
|
||||
let percentage = json?.floatFromStringForKey("percent")
|
||||
progress = (percentage ?? Float(0.0))/100
|
||||
progressTintColor = UIColor.mfBattleshipGrey()
|
||||
trackTintColor = UIColor.mfLighterGray()
|
||||
|
||||
if let progresscolor = json?.optionalStringForKey("progressColor") {
|
||||
if !progresscolor.isEmpty {
|
||||
progressTintColor = UIColor.mfGet(forHex: progresscolor)
|
||||
}
|
||||
if let isRounded = json?.optionalBoolForKey("roundedRect") {
|
||||
self.isRounded = isRounded
|
||||
}
|
||||
|
||||
if let backgroundcolor = json?.optionalStringForKey("backgroundColor") {
|
||||
if !backgroundcolor.isEmpty {
|
||||
trackTintColor = UIColor.mfGet(forHex: backgroundcolor)
|
||||
}
|
||||
if let thickness = json?.optionalCGFloatForKey("thickness") {
|
||||
self.thickness = thickness
|
||||
}
|
||||
if let percentage = json?["percent"] as? Float {
|
||||
progress = percentage/100.0
|
||||
}
|
||||
if let progressColor = json?.optionalStringForKey("progressColor") {
|
||||
progressTintColor = UIColor.mfGet(forHex: progressColor)
|
||||
}
|
||||
if let backgroundColor = json?.optionalStringForKey("backgroundColor") {
|
||||
trackTintColor = UIColor.mfGet(forHex: backgroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
isRounded = false
|
||||
thickness = 8
|
||||
progress = 0
|
||||
progressTintColor = UIColor.mfCerulean()
|
||||
trackTintColor = UIColor.mfSilver()
|
||||
}
|
||||
|
||||
public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return 8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
//
|
||||
// ProgressBarView.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Panth Patel on 5/3/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objcMembers public class ProgressBarWithLabel: ViewConstrainingView {
|
||||
|
||||
var progress = ProgressBar()
|
||||
var topleftlabel = Label()
|
||||
var toprightlabel = Label()
|
||||
var bottomleftlabel = Label()
|
||||
var bottomrightlabel = Label()
|
||||
|
||||
open override func needsToBeConstrained() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
let progressbarjson = json?.optionalDictionaryForKey("progressbar")
|
||||
progress.setWithJSON(progressbarjson, delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
let topleftlabeljson = json?.optionalDictionaryForKey("label")
|
||||
let toprightlabeljson = json?.optionalDictionaryForKey("toprightlabel")
|
||||
let bottomleftlabeljson = json?.optionalDictionaryForKey("bottomleftlabel")
|
||||
let bottomrightlabeljson = json?.optionalDictionaryForKey("bottomrightlabel")
|
||||
|
||||
topleftlabel.setWithJSON(topleftlabeljson, delegateObject: delegateObject, additionalData: additionalData)
|
||||
toprightlabel.setWithJSON(toprightlabeljson, delegateObject: delegateObject, additionalData: additionalData)
|
||||
bottomleftlabel.setWithJSON(bottomleftlabeljson, delegateObject: delegateObject, additionalData: additionalData)
|
||||
bottomrightlabel.setWithJSON(bottomrightlabeljson, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
override open func setupView() {
|
||||
super.setupView()
|
||||
addSubview(topleftlabel)
|
||||
addSubview(toprightlabel)
|
||||
addSubview(bottomleftlabel)
|
||||
addSubview(bottomrightlabel)
|
||||
addSubview(progress)
|
||||
|
||||
progress.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
topleftlabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
|
||||
topleftlabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
|
||||
let topleftwidthconstraint = NSLayoutConstraint(item: topleftlabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.5, constant: 0.0)
|
||||
topleftwidthconstraint.priority = UILayoutPriority(100)
|
||||
topleftwidthconstraint.isActive = true
|
||||
topleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .horizontal)
|
||||
topleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .vertical)
|
||||
|
||||
NSLayoutConstraint(item: toprightlabel, attribute: .leading, relatedBy: .equal, toItem: topleftlabel, attribute: .trailing, multiplier: 1.0, constant: PaddingTwo).isActive = true
|
||||
toprightlabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
|
||||
toprightlabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
|
||||
toprightlabel.textAlignment = .right
|
||||
toprightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .horizontal)
|
||||
toprightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .vertical)
|
||||
|
||||
var topconstraint = progress.topAnchor.constraint(equalTo: topleftlabel.bottomAnchor, constant: PaddingTwo)
|
||||
topconstraint.priority = UILayoutPriority(249)
|
||||
topconstraint.isActive = true
|
||||
progress.topAnchor.constraint(greaterThanOrEqualTo: topleftlabel.bottomAnchor, constant: PaddingTwo).isActive = true
|
||||
topconstraint = progress.topAnchor.constraint(equalTo: toprightlabel.bottomAnchor, constant: PaddingTwo)
|
||||
topconstraint.priority = UILayoutPriority(249)
|
||||
topconstraint.isActive = true
|
||||
progress.topAnchor.constraint(greaterThanOrEqualTo: toprightlabel.bottomAnchor, constant: PaddingTwo).isActive = true
|
||||
|
||||
progress.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
|
||||
progress.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
|
||||
|
||||
bottomleftlabel.topAnchor.constraint(equalTo: progress.bottomAnchor, constant: PaddingTwo).isActive = true
|
||||
bottomleftlabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
|
||||
let bottomleftwidthconstraint = NSLayoutConstraint(item: bottomleftlabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.5, constant: 0.0)
|
||||
bottomleftwidthconstraint.priority = UILayoutPriority(100)
|
||||
bottomleftwidthconstraint.isActive = true
|
||||
bottomleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .horizontal)
|
||||
bottomleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .vertical)
|
||||
|
||||
NSLayoutConstraint(item: bottomrightlabel, attribute: .leading, relatedBy: .equal, toItem: bottomleftlabel, attribute: .trailing, multiplier: 1.0, constant: PaddingTwo).isActive = true
|
||||
bottomrightlabel.topAnchor.constraint(equalTo: progress.bottomAnchor, constant: PaddingTwo).isActive = true
|
||||
bottomrightlabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
|
||||
bottomrightlabel.textAlignment = .right
|
||||
bottomrightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .horizontal)
|
||||
bottomrightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .vertical)
|
||||
|
||||
var bottomconstraint = bottomAnchor.constraint(equalTo: bottomleftlabel.bottomAnchor, constant: PaddingTwo)
|
||||
bottomconstraint.priority = UILayoutPriority(249)
|
||||
bottomconstraint.isActive = true
|
||||
bottomAnchor.constraint(greaterThanOrEqualTo: bottomleftlabel.bottomAnchor, constant: PaddingTwo).isActive = true
|
||||
bottomconstraint = bottomAnchor.constraint(equalTo: bottomrightlabel.bottomAnchor, constant: PaddingTwo)
|
||||
bottomconstraint.priority = UILayoutPriority(249)
|
||||
bottomconstraint.isActive = true
|
||||
bottomAnchor.constraint(greaterThanOrEqualTo: bottomrightlabel.bottomAnchor, constant: PaddingTwo).isActive = true
|
||||
}
|
||||
}
|
||||
44
MVMCoreUI/Molecules/Scroller.swift
Normal file
44
MVMCoreUI/Molecules/Scroller.swift
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// Scroller.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 7/23/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objcMembers open class Scroller: ViewConstrainingView {
|
||||
public let scrollView = UIScrollView(frame: .zero)
|
||||
public let contentView = MVMCoreUICommonViewsUtility.commonView()
|
||||
|
||||
override open func setupView() {
|
||||
super.setupView()
|
||||
guard scrollView.superview == nil else {
|
||||
return
|
||||
}
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addConstrainedView(scrollView)
|
||||
scrollView.addSubview(contentView)
|
||||
NSLayoutConstraint.constraintPinSubview(toSuperview: contentView)
|
||||
let constraint = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0)
|
||||
constraint.priority = UILayoutPriority(rawValue: 999)
|
||||
constraint.isActive = true
|
||||
}
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else {
|
||||
return
|
||||
}
|
||||
if molecule == nil {
|
||||
if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) {
|
||||
contentView.addSubview(moleculeView)
|
||||
pinView(toSuperView: moleculeView)
|
||||
molecule = moleculeView
|
||||
}
|
||||
} else {
|
||||
molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,14 +12,16 @@ open class StandardFooterView: ViewConstrainingView {
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
shouldSetupMoleculeFromJSON = true
|
||||
updateViewVerticalDefaults = true
|
||||
updateViewHorizontalDefaults = true
|
||||
}
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
// This molecule will by default handle margins.
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false)
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false)
|
||||
shouldSetVerticalMargins(true)
|
||||
shouldSetHorizontalMargins(true)
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
|
||||
@ -20,6 +20,8 @@ public class StandardHeaderView: ViewConstrainingView {
|
||||
public override func setupView() {
|
||||
super.setupView()
|
||||
shouldSetupMoleculeFromJSON = true
|
||||
updateViewVerticalDefaults = true
|
||||
updateViewHorizontalDefaults = true
|
||||
if separatorView == nil, let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) {
|
||||
separatorView.setAsHeavy()
|
||||
addSubview(separatorView)
|
||||
@ -40,19 +42,14 @@ public class StandardHeaderView: ViewConstrainingView {
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
// This molecule will by default handle margins.
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false)
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false)
|
||||
shouldSetVerticalMargins(true)
|
||||
shouldSetHorizontalMargins(true)
|
||||
|
||||
if let separatorJSON = json?.optionalDictionaryForKey("separator") {
|
||||
separatorView?.setWithJSON(separatorJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
if separatorView?.isHidden ?? true {
|
||||
bottomPin?.constant = 0
|
||||
} else {
|
||||
bottomPin?.constant = PaddingDefaultVerticalSpacing
|
||||
}
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#import "MVMCoreUIObject.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
#import "MFTextField.h"
|
||||
#import "MVMCoreUIPageControl.h"
|
||||
#import "MVMCoreUIViewConstrainingProtocol.h"
|
||||
|
||||
@implementation MVMCoreUIMoleculeMappingObject
|
||||
@ -38,17 +39,23 @@
|
||||
@"textField" : MFTextField.class,
|
||||
@"digitTextField" : MFDigitTextField.class,
|
||||
@"checkbox" : MVMCoreUICheckBox.class,
|
||||
@"progressBarWithLabel" : ProgressBarWithLabel.class,
|
||||
@"cornerLabels" : CornerLabels.class,
|
||||
@"progressBar": ProgressBar.class,
|
||||
@"textField": MFTextField.class,
|
||||
@"checkbox": MVMCoreUICheckBox.class,
|
||||
@"listItem": MoleculeTableViewCell.class,
|
||||
@"switchLineItem": SwitchLineItem.class,
|
||||
@"switch": Switch.class,
|
||||
@"image": MFLoadImageView.class,
|
||||
@"leftRightLabelView": LeftRightLabelView.class,
|
||||
@"actionDetailWithImage": ActionDetailWithImage.class,
|
||||
@"image": MFLoadImageView.class,
|
||||
@"moduleMolecule": ModuleMolecule.class,
|
||||
@"headlineBody": HeadlineBody.class
|
||||
@"headlineBody": HeadlineBody.class,
|
||||
@"carousel": Carousel.class,
|
||||
@"carouselItem": MoleculeCollectionViewCell.class,
|
||||
@"barsPager": MVMCoreUIPageControl.class,
|
||||
@"scroller": Scroller.class,
|
||||
@"imageHeadlineBody": ImageHeadlineBody.class
|
||||
} mutableCopy];
|
||||
});
|
||||
return mapping;
|
||||
|
||||
@ -22,7 +22,9 @@
|
||||
@"textFieldListForm" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[TextFieldListFormViewController class]],
|
||||
@"moleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackTemplate class]],
|
||||
@"centerMoleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackCenteredTemplate class]],
|
||||
@"moleculeList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]]
|
||||
@"moleculeList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]],
|
||||
@"threeLayer" :
|
||||
[[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ThreeLayerTemplate class]]
|
||||
} mutableCopy];
|
||||
});
|
||||
return viewControllerMapping;
|
||||
|
||||
@ -12,4 +12,8 @@
|
||||
/// returns a module for the corresponding module name.
|
||||
- (nullable NSDictionary *)getModuleWithName:(nullable NSString *)name;
|
||||
|
||||
@optional
|
||||
/// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method.
|
||||
- (void)moleculeLayoutUpdated:(nonnull UIView <MVMCoreUIMoleculeViewProtocol>*)molecule;
|
||||
|
||||
@end
|
||||
|
||||
@ -29,6 +29,7 @@ typedef NS_ENUM(NSUInteger, MFTimeFormatUnit) {
|
||||
extern CGFloat const PaddingDefault;
|
||||
extern CGFloat const PaddingDefaultHorizontalSpacing;
|
||||
extern CGFloat const PaddingDefaultVerticalSpacing;
|
||||
extern CGFloat const PaddingDefaultVerticalSpacing3;
|
||||
extern CGFloat const PaddingBetweenFields;
|
||||
extern CGFloat const PaddingHorizontalHeadlineWhiteView;
|
||||
extern CGFloat const PaddingHorizontalLarge;
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
CGFloat const PaddingDefault = 24;
|
||||
CGFloat const PaddingDefaultHorizontalSpacing = 32;
|
||||
CGFloat const PaddingDefaultVerticalSpacing = 32;
|
||||
CGFloat const PaddingDefaultVerticalSpacing3 = 24;
|
||||
CGFloat const PaddingBetweenFields = 24;
|
||||
CGFloat const PaddingHorizontalHeadlineWhiteView = 60;
|
||||
CGFloat const PaddingHorizontalLarge = 72;
|
||||
@ -97,12 +98,8 @@ CGFloat const LabelWithInternalButtonLineSpace = 2;
|
||||
+ (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size horizontal:(BOOL)horizontal vertical:(BOOL)vertical {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0;
|
||||
CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing : 0;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(verticalPadding, horizontalPadding, verticalPadding, horizontalPadding);
|
||||
} else {
|
||||
view.layoutMargins = UIEdgeInsetsMake(verticalPadding, horizontalPadding, verticalPadding, horizontalPadding);
|
||||
}
|
||||
CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing3 : 0;
|
||||
[MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:verticalPadding trailing:horizontalPadding bottom:verticalPadding];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
23
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/Contents.json
vendored
Normal file
23
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "closeXBlack.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "closeXBlack@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "closeXBlack@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 B |
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@2x.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 374 B |
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@3x.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 535 B |
23
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/Contents.json
vendored
Normal file
23
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "E_UBI_003_G.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "E_UBI_003_G@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "E_UBI_003_G@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 B |
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@2x.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 B |
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@3x.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/peakingRightArrow.imageset/E_UBI_003_G@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 379 B |
@ -41,6 +41,9 @@
|
||||
"AccOn" = "on";
|
||||
"AccOff" = "off";
|
||||
"AccToggleHint" = "double tap to toggle";
|
||||
// Carousel
|
||||
"MVMCoreUIPageControl_currentpage_index" = "page %ld of %ld";
|
||||
"MVMCoreUIPageControlslides_currentpage_index" = "slide %ld of %ld";
|
||||
//Styler
|
||||
"CountDownDay" = " day";
|
||||
"CountDownHour" = " hour";
|
||||
|
||||
@ -39,6 +39,9 @@
|
||||
"AccOn" = "encendido";
|
||||
"AccOff" = "apagado";
|
||||
"AccToggleHint" = "toca dos veces para alternar";
|
||||
// Carousel
|
||||
"MVMCoreUIPageControl_currentpage_index" = "página %ld de %ld";
|
||||
"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %ld of %ld";
|
||||
//Styler
|
||||
"CountDownDay" = " día";
|
||||
"CountDownHour" = " hora";
|
||||
|
||||
@ -39,6 +39,9 @@
|
||||
"AccOn" = "encendido";
|
||||
"AccOff" = "apagado";
|
||||
"AccToggleHint" = "toca dos veces para alternar";
|
||||
// Carousel
|
||||
"MVMCoreUIPageControl_currentpage_index" = "página %ld de %ld";
|
||||
"MVMCoreUIPageControlslides_currentpage_index" = "diapositiva %ld of %ld";
|
||||
//Styler
|
||||
"CountDownDay" = " día";
|
||||
"CountDownHour" = " hora";
|
||||
|
||||
@ -106,6 +106,17 @@ open class MoleculeListTemplate: ThreeLayerTableViewController {
|
||||
return loadObject?.requestParameters?.modules
|
||||
}
|
||||
|
||||
// MARK: - MoleculeDelegateProtocol
|
||||
open override func moleculeLayoutUpdated(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) {
|
||||
if let tableView = tableView {
|
||||
let point = molecule.convert(molecule.bounds.origin, to: tableView)
|
||||
if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false {
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
/// Returns the (identifier, class) of the molecule for the given map.
|
||||
func getMoleculeInfo(with molecule: [AnyHashable: Any]?) -> (identifier: String, class: AnyClass, molecule: [AnyHashable: Any])? {
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class MoleculeStackTemplate: ThreeLayerViewController {
|
||||
open class MoleculeStackTemplate: ThreeLayerViewController {
|
||||
var observer: NSKeyValueObservation?
|
||||
|
||||
open override var loadObject: MVMCoreLoadObject? {
|
||||
@ -25,18 +25,18 @@ public class MoleculeStackTemplate: ThreeLayerViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public override func spaceBetweenTopAndMiddle() -> CGFloat? {
|
||||
open override func spaceBetweenTopAndMiddle() -> CGFloat? {
|
||||
return 0
|
||||
}
|
||||
|
||||
public override func viewForTop() -> UIView? {
|
||||
open override func viewForTop() -> UIView? {
|
||||
guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else {
|
||||
return nil
|
||||
}
|
||||
return molecule
|
||||
}
|
||||
|
||||
public override func viewForMiddle() -> UIView? {
|
||||
open override func viewForMiddle() -> UIView? {
|
||||
guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("moleculeStack") else {
|
||||
return nil
|
||||
}
|
||||
@ -46,7 +46,7 @@ public class MoleculeStackTemplate: ThreeLayerViewController {
|
||||
return stack
|
||||
}
|
||||
|
||||
override public func viewForBottom() -> UIView? {
|
||||
override open func viewForBottom() -> UIView? {
|
||||
guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else {
|
||||
return nil
|
||||
}
|
||||
@ -54,14 +54,14 @@ public class MoleculeStackTemplate: ThreeLayerViewController {
|
||||
}
|
||||
|
||||
// MARK: - cache handling
|
||||
public override func pageTypesToListenFor() -> [Any]? {
|
||||
open override func pageTypesToListenFor() -> [Any]? {
|
||||
guard let pageType = self.pageType else {
|
||||
return super.pageTypesToListenFor()
|
||||
}
|
||||
return [pageType]
|
||||
}
|
||||
|
||||
public override func modulesToListenFor() -> [Any]? {
|
||||
open override func modulesToListenFor() -> [Any]? {
|
||||
return loadObject?.requestParameters?.modules
|
||||
}
|
||||
|
||||
|
||||
52
MVMCoreUI/Templates/ThreeLayerTemplate.swift
Normal file
52
MVMCoreUI/Templates/ThreeLayerTemplate.swift
Normal file
@ -0,0 +1,52 @@
|
||||
//
|
||||
// ThreeLayerTemplate.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 7/23/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController {
|
||||
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
bottomViewOutsideOfScroll = true
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
open override func newDataBuildScreen() {
|
||||
super.newDataBuildScreen()
|
||||
heightConstraint?.isActive = true
|
||||
}
|
||||
|
||||
open override func viewForTop() -> UIView? {
|
||||
guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else {
|
||||
return nil
|
||||
}
|
||||
return molecule
|
||||
}
|
||||
|
||||
open override func viewForMiddle() -> UIView? {
|
||||
guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("middle"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else {
|
||||
return nil
|
||||
}
|
||||
return molecule
|
||||
}
|
||||
|
||||
override open func viewForBottom() -> UIView? {
|
||||
guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else {
|
||||
return nil
|
||||
}
|
||||
return molecule
|
||||
}
|
||||
|
||||
open override func spaceBetweenMiddleAndBottom() -> CGFloat? {
|
||||
return 0
|
||||
}
|
||||
|
||||
open override func spaceBetweenTopAndMiddle() -> CGFloat? {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@ -50,4 +50,7 @@
|
||||
// Get the background color based on the type
|
||||
- (nonnull UIColor *)getBackgroundColorForType:(nullable NSString *)type;
|
||||
|
||||
// Set the status bar color. Used for updating the status bar when the view changes.
|
||||
- (void)setStatusBarColor:(nullable UIColor *)statusBarColor statusBarStyle:(UIStatusBarStyle)style;
|
||||
|
||||
@end
|
||||
|
||||
@ -181,7 +181,7 @@ static const CGFloat VertialShadowOffset = 6;
|
||||
+ (nonnull MFCustomButton *)addCloseButtonToView:(UIView *)view action:(ButtonTapBlock)actionBlock verticalCentered:(BOOL)verticalCentered {
|
||||
MFCustomButton *button = [[MFCustomButton alloc] initWithFrame:CGRectZero];
|
||||
button.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[button setTitle:@"✕" forState:UIControlStateNormal];
|
||||
[button setImage:[MVMCoreUIUtility imageNamed:@"closeXBlack"] forState:UIControlStateNormal];
|
||||
button.titleLabel.font = [MFStyler fontForHeadlineAlternative];
|
||||
|
||||
//accessibility
|
||||
|
||||
@ -28,6 +28,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// Returns an image from this framework's bundle
|
||||
+ (nullable UIImage *)imageNamed:(nullable NSString *)imageName;
|
||||
|
||||
// Returns the margins for a view.
|
||||
+ (UIEdgeInsets)getMarginsForView:(nullable UIView *)view;
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
+ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom;
|
||||
|
||||
#pragma mark - Formatting
|
||||
|
||||
// Formats the given mdn.
|
||||
@ -64,8 +71,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// Gets the constraint height to be whatever space is left in the scroll view.
|
||||
+ (CGFloat)getVariableConstraintHeight:(CGFloat)currentConstant inScrollView:(nonnull UIScrollView *)scrollView minimumHeight:(CGFloat)minimumHeight;
|
||||
|
||||
// Sets the view's frame according to constraint.
|
||||
/// Sets the view's frame according to constraints.
|
||||
+ (void)sizeViewToFit:(nullable UIView *)view;
|
||||
+ (void)sizeViewToFit:(UIView *)view forWidth:(nullable NSNumber *)width;
|
||||
|
||||
#pragma mark - Keyboard
|
||||
|
||||
|
||||
@ -57,6 +57,24 @@
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (UIEdgeInsets)getMarginsForView:(nullable UIView *)view {
|
||||
if (@available(iOS 11.0, *)) {
|
||||
return UIEdgeInsetsMake(view.directionalLayoutMargins.top, view.directionalLayoutMargins.leading, view.directionalLayoutMargins.bottom, view.directionalLayoutMargins.trailing);
|
||||
} else {
|
||||
return view.layoutMargins;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
+ (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom {
|
||||
if (@available(iOS 11.0, *)) {
|
||||
view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(top, leading, bottom, trailing);
|
||||
} else {
|
||||
view.layoutMargins = UIEdgeInsetsMake(top, leading, bottom, trailing);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Formatting
|
||||
|
||||
// As per business confirmation format should be xxx.xxx.xxxx same across application
|
||||
@ -101,7 +119,7 @@
|
||||
#pragma mark - Sizing
|
||||
|
||||
+ (CGFloat)getWidth {
|
||||
UIViewController *controller = [MVMCoreUISession sharedGlobal].splitViewController ?: [MVMCoreUISession sharedGlobal].navigationController ?: [UIApplication sharedApplication].keyWindow.rootViewController;
|
||||
UIViewController *controller = [MVMCoreUISession sharedGlobal].splitViewController ?: [MVMCoreUISession sharedGlobal].navigationController;
|
||||
if (controller) {
|
||||
return CGRectGetWidth(controller.view.bounds);
|
||||
} else {
|
||||
@ -120,7 +138,7 @@
|
||||
}
|
||||
|
||||
+ (CGFloat)getHeightOfView:(nonnull UIView *)view forWidth:(nullable NSNumber *)width {
|
||||
CGFloat floatWidth = (width ? width.floatValue : CGRectGetWidth([MVMCoreNavigationHandler sharedNavigationHandler].navigationController.view.bounds));
|
||||
CGFloat floatWidth = (width ? width.floatValue : [MVMCoreUIUtility getWidth]);
|
||||
return [view systemLayoutSizeFittingSize:CGSizeMake(floatWidth, UILayoutFittingCompressedSize.height) withHorizontalFittingPriority:1000 verticalFittingPriority:250].height;
|
||||
}
|
||||
|
||||
@ -174,6 +192,13 @@
|
||||
view.frame = frame;
|
||||
}
|
||||
|
||||
+ (void)sizeViewToFit:(UIView *)view forWidth:(nullable NSNumber *)width {
|
||||
CGFloat height = [MVMCoreUIUtility getHeightOfView:view forWidth:width];
|
||||
CGRect frame = view.frame;
|
||||
frame.size.height = height;
|
||||
view.frame = frame;
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard
|
||||
|
||||
+ (void)setScrollViewInsetForKeyboardShow:(nonnull NSNotification *)notification scrollView:(nonnull UIScrollView *)scrollView viewController:(nonnull UIViewController *)viewController rectToScrollTo:(nonnull CGRect (^)(void))rectToScrollTo {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user