merge develop
This commit is contained in:
commit
ceae7e4f80
@ -33,6 +33,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 */; };
|
||||
@ -41,7 +44,6 @@
|
||||
D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D28B4F8921FF967C00712C7A /* MVMCoreUIObject.m */; };
|
||||
D296E13C229598BF0051EBE7 /* MoleculeListCellProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E13B2295969C0051EBE7 /* MoleculeListCellProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D296E1412295EBBA0051EBE7 /* MoleculeDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E1402295EBBA0051EBE7 /* MoleculeDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D296E143229729C30051EBE7 /* MoleculeMappingObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D296E142229729C30051EBE7 /* MoleculeMappingObject+Extension.swift */; };
|
||||
D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */; };
|
||||
D29770C921F7C4AE00B2F0D0 /* TopLabelsView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -51,6 +53,7 @@
|
||||
D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770F121F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m */; };
|
||||
D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770FA21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m */; };
|
||||
D29770FD21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29770FB21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */; };
|
||||
D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */; };
|
||||
D29DF11521E6805F003B2FB9 /* UIColor+MFConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF11121E6805F003B2FB9 /* UIColor+MFConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@ -163,6 +166,9 @@
|
||||
D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; };
|
||||
@ -203,6 +209,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>"; };
|
||||
@ -211,7 +220,6 @@
|
||||
D28B4F8921FF967C00712C7A /* MVMCoreUIObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIObject.m; sourceTree = "<group>"; };
|
||||
D296E13B2295969C0051EBE7 /* MoleculeListCellProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoleculeListCellProtocol.h; sourceTree = "<group>"; };
|
||||
D296E1402295EBBA0051EBE7 /* MoleculeDelegateProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoleculeDelegateProtocol.h; sourceTree = "<group>"; };
|
||||
D296E142229729C30051EBE7 /* MoleculeMappingObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoleculeMappingObject+Extension.swift"; sourceTree = "<group>"; };
|
||||
D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewConstrainingProtocol.h; sourceTree = "<group>"; };
|
||||
D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopLabelsView.m; sourceTree = "<group>"; };
|
||||
D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TopLabelsView.h; sourceTree = "<group>"; };
|
||||
@ -221,6 +229,7 @@
|
||||
D29770F121F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopLabelsAndBottomButtonsViewController.m; sourceTree = "<group>"; };
|
||||
D29770FA21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUITextFieldView.m; sourceTree = "<group>"; };
|
||||
D29770FB21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUITextFieldView.h; sourceTree = "<group>"; };
|
||||
D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleMolecule.swift; sourceTree = "<group>"; };
|
||||
D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUI.h; sourceTree = "<group>"; };
|
||||
D29DF0D021E404D4003B2FB9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -337,6 +346,9 @@
|
||||
D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackCenteredTemplate.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = "<group>"; };
|
||||
@ -478,6 +490,13 @@
|
||||
B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */,
|
||||
B8200E182281DC1A007245F4 /* ProgressBarView.swift */,
|
||||
0116A4E4228B19640094F3ED /* RadioButtonModel.swift */,
|
||||
D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */,
|
||||
D2A638FC22CA98280052ED1F /* HeadlineBody.swift */,
|
||||
D2A6390022CBB1820052ED1F /* Carousel.swift */,
|
||||
D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */,
|
||||
D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */,
|
||||
D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */,
|
||||
D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */,
|
||||
);
|
||||
path = Molecules;
|
||||
sourceTree = "<group>";
|
||||
@ -686,7 +705,6 @@
|
||||
D296E1402295EBBA0051EBE7 /* MoleculeDelegateProtocol.h */,
|
||||
D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */,
|
||||
D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */,
|
||||
D296E142229729C30051EBE7 /* MoleculeMappingObject+Extension.swift */,
|
||||
);
|
||||
path = OtherHandlers;
|
||||
sourceTree = "<group>";
|
||||
@ -755,6 +773,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 */,
|
||||
@ -813,6 +832,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 */,
|
||||
@ -908,6 +928,7 @@
|
||||
D29DF32121ED0CBA003B2FB9 /* LabelView.m in Sources */,
|
||||
DBC4391822442197001AB423 /* CaretView.swift in Sources */,
|
||||
D29770F221F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m in Sources */,
|
||||
D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */,
|
||||
DBC4391922442197001AB423 /* DashLine.swift in Sources */,
|
||||
D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */,
|
||||
0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */,
|
||||
@ -939,6 +960,7 @@
|
||||
DBEFFA04225A829700230692 /* Label.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 */,
|
||||
@ -953,13 +975,15 @@
|
||||
D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */,
|
||||
D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */,
|
||||
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */,
|
||||
D296E143229729C30051EBE7 /* MoleculeMappingObject+Extension.swift in Sources */,
|
||||
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 */,
|
||||
D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */,
|
||||
D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */,
|
||||
D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */,
|
||||
DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */,
|
||||
|
||||
@ -139,4 +139,8 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI
|
||||
open func alignment() -> UIStackView.Alignment {
|
||||
return UIStackView.Alignment.leading;
|
||||
}
|
||||
|
||||
public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,6 +146,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject {
|
||||
return 31;
|
||||
}
|
||||
|
||||
#pragma mark - MVMCoreUIViewConstrainingProtocol
|
||||
|
||||
- (BOOL)needsToBeConstrained {
|
||||
|
||||
@ -669,6 +669,13 @@
|
||||
[FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol];
|
||||
|
||||
self.primaryButtonType = PrimaryButtonTypeCustom;
|
||||
NSString *style = [json string:@"style"];
|
||||
if ([style isEqualToString:@"primary"]) {
|
||||
[self setAsStandardCustom];
|
||||
} else if ([style isEqualToString:@"secondary"]) {
|
||||
[self setAsSecondaryCustom];
|
||||
}
|
||||
|
||||
NSString *color = [json string:@"fillColor"];
|
||||
if (color) {
|
||||
self.fillColor = [UIColor mfGetColorForHex:color];
|
||||
@ -691,7 +698,14 @@
|
||||
}
|
||||
self.validationRequired = [json boolForKey:@"validationRequired"];
|
||||
|
||||
[self setAsSmallButton:[json boolForKey:@"small"]];
|
||||
NSString *size = [json string:@"size"];
|
||||
if ([size isEqualToString:@"small"]) {
|
||||
[self setAsSmallButton:YES];
|
||||
} else if ([size isEqualToString:@"tiny"]) {
|
||||
[self setAsTiny:YES];
|
||||
} else {
|
||||
[self setAsSmallButton:NO];
|
||||
}
|
||||
[self setWithActionMap:json delegateObject:delegateObject additionalData:additionalData];
|
||||
}
|
||||
|
||||
|
||||
@ -15,8 +15,6 @@
|
||||
+ (nullable instancetype)mfDigitTextFieldWithNumberOfDigits:(NSUInteger)numberOfDigits withBothDelegates:(nullable id<UITextFieldDelegate, MFTextFieldDelegate>)delegate;
|
||||
+ (nullable instancetype)mfDigitTextFieldWithNumberOfDigits:(NSUInteger)numberOfDigits withBothDelegates:(nullable id<UITextFieldDelegate, MFTextFieldDelegate>)delegate size:(CGFloat)size;
|
||||
|
||||
- (void)setMessage:(nullable NSString *)message;
|
||||
|
||||
- (void)setAsSecureTextEntry:(BOOL)secureTextEntry;
|
||||
|
||||
@property (nullable, nonatomic, strong) NSArray <MFDigitTextBox *>*textFields;
|
||||
|
||||
@ -18,33 +18,24 @@
|
||||
#import "MVMCoreUISplitViewController.h"
|
||||
#import "MVMCoreUIUtility.h"
|
||||
#import "MVMCoreUIConstants.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
@import MVMCore.MVMCoreDispatchUtility;
|
||||
|
||||
@interface MFDigitTextField () <UITextFieldDelegate, MFDigitTextBoxDelegate>
|
||||
|
||||
@property (nonatomic) NSUInteger numberOfDigits;
|
||||
|
||||
@property (nullable, nonatomic, weak) IBOutlet UILabel *messageLabel;
|
||||
@property (nullable, nonatomic, weak) IBOutlet UIView *textFieldsView;
|
||||
@property (nullable, nonatomic, weak) IBOutlet NSLayoutConstraint *messageToTextFieldPin;
|
||||
@property (nullable, nonatomic, weak) IBOutlet NSLayoutConstraint *labelToTextFieldPin;
|
||||
|
||||
|
||||
@property (nonatomic) BOOL switchedAutomatically;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MFDigitTextField
|
||||
|
||||
+ (nullable instancetype)mfTextField {
|
||||
MFDigitTextField *view = [super mfTextField];
|
||||
[MFStyler styleLabelB2:view.messageLabel];
|
||||
[view setMessage:@""];
|
||||
return view;
|
||||
}
|
||||
|
||||
- (MFDigitTextBox *)digitField {
|
||||
|
||||
MFDigitTextBox *textField = [[MFDigitTextBox alloc] init];
|
||||
textField.delegate = self;
|
||||
textField.mfTextBoxDelegate = self;
|
||||
@ -128,8 +119,9 @@
|
||||
}
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
[super updateView:size];
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[MFStyler styleLabelB2:self.messageLabel];
|
||||
[self.formLabel updateView:size];
|
||||
|
||||
// Remove all current UI.
|
||||
if (self.textFields.count > 0) {
|
||||
@ -146,6 +138,29 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
[super setupView];
|
||||
[self.formLabel styleB2:YES];
|
||||
[self setFormText:@""];
|
||||
[self alignCenterHorizontal];
|
||||
}
|
||||
|
||||
#pragma mark - Molecule
|
||||
|
||||
- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData {
|
||||
NSNumber *digitsNumber = [json optionalNumberForKey:@"digits"];
|
||||
NSUInteger digits = digitsNumber ? digitsNumber.integerValue : 4;
|
||||
if (digits != self.numberOfDigits) {
|
||||
self.numberOfDigits = digits;
|
||||
[self buildTextFieldsViewForSize:[MVMCoreUIUtility getWidth]];
|
||||
}
|
||||
[super setWithJSON:json delegateObject:delegateObject additionalData:additionalData];
|
||||
}
|
||||
|
||||
+ (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject {
|
||||
return 44;
|
||||
}
|
||||
|
||||
#pragma mark - Getters
|
||||
|
||||
- (NSString *)placeholder {
|
||||
@ -172,15 +187,13 @@
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setMessage:(nullable NSString *)message {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
if (message.length > 0) {
|
||||
self.messageToTextFieldPin.constant = 10;
|
||||
} else {
|
||||
self.messageToTextFieldPin.constant = 0;
|
||||
}
|
||||
self.messageLabel.text = message;
|
||||
}];
|
||||
- (void)setFormText:(NSString *)formText {
|
||||
if (formText.length > 0) {
|
||||
self.messageToTextFieldPin.constant = 10;
|
||||
} else {
|
||||
self.messageToTextFieldPin.constant = 0;
|
||||
}
|
||||
[super setFormText:formText];
|
||||
}
|
||||
|
||||
- (void)setAsSecureTextEntry:(BOOL)secureTextEntry {
|
||||
@ -284,6 +297,11 @@
|
||||
|
||||
- (void)enable:(BOOL)enable {
|
||||
[super enable:enable];
|
||||
if (enable) {
|
||||
[self.formLabel styleB2:YES];
|
||||
} else {
|
||||
self.formLabel.textColor = [UIColor mfBattleshipGrey];
|
||||
}
|
||||
for (UITextField *textField in self.textFields) {
|
||||
textField.userInteractionEnabled = enable;
|
||||
textField.enabled = enable;
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<array key="NHaasGroteskDSStd-65Md.otf">
|
||||
<string>NHaasGroteskDSStd-65Md</string>
|
||||
</array>
|
||||
</customFonts>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MFDigitTextField">
|
||||
<connections>
|
||||
<outlet property="formLabel" destination="lBe-qk-qFb" id="Z62-sL-6ri"/>
|
||||
<outlet property="label" destination="FWB-f6-Irs" id="fcc-SH-O4s"/>
|
||||
<outlet property="labelToTextFieldPin" destination="KyA-DB-529" id="SOG-MG-gMN"/>
|
||||
<outlet property="messageLabel" destination="lBe-qk-qFb" id="x3K-fb-b4U"/>
|
||||
<outlet property="messageToTextFieldPin" destination="NPs-pX-DJ0" id="41B-cE-pax"/>
|
||||
<outlet property="textFieldsView" destination="v46-w7-vQ1" id="hh8-TK-7XM"/>
|
||||
</connections>
|
||||
@ -28,7 +23,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="800" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="900" verticalCompressionResistancePriority="1000" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lBe-qk-qFb">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="800" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="900" verticalCompressionResistancePriority="1000" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lBe-qk-qFb" customClass="Label" customModule="MVMCoreUI" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="16"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<fontDescription key="fontDescription" name="NHaasGroteskDSStd-65Md" family="Neue Haas Grotesk Display Std" pointSize="13"/>
|
||||
|
||||
@ -31,7 +31,6 @@
|
||||
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *textContainerRightPin;
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *errorLableRightPin;
|
||||
@property (nonatomic) BOOL isMolecule;
|
||||
|
||||
@end
|
||||
|
||||
@ -42,7 +41,7 @@
|
||||
- (void)updateView:(CGFloat)size {
|
||||
[super updateView:size];
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
self.formLabel.font = [MFStyler fontB3];
|
||||
[self.formLabel updateView:size];
|
||||
self.label.font = [MFStyler fontForTextFieldUnderLabel];
|
||||
[MFStyler styleTextField:self.textField];
|
||||
[self.dashLine updateView:size];
|
||||
@ -55,11 +54,13 @@
|
||||
UINib *nib = [self getNib];
|
||||
NSArray *views = [nib instantiateWithOwner:self options:nil];
|
||||
UIView *view = [views firstObject];
|
||||
view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[view setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
|
||||
[view setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
|
||||
self.view = view;
|
||||
view.frame = self.frame;
|
||||
[self addSubview:view];
|
||||
[self pinViewToSuperView:view];
|
||||
|
||||
self.textField.font = [MFStyler fontForTextField];
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@ -556,10 +557,6 @@
|
||||
|
||||
#pragma mark - MVMCoreUIMoleculeViewProtocol
|
||||
|
||||
- (void)setAsMolecule {
|
||||
self.isMolecule = YES;
|
||||
}
|
||||
|
||||
- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData {
|
||||
if ([delegateObject isKindOfClass:[MVMCoreUIDelegateObject class]]) {
|
||||
[FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol];
|
||||
@ -572,6 +569,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject {
|
||||
return 76;
|
||||
}
|
||||
|
||||
#pragma mark - FormValidationProtocol
|
||||
|
||||
|
||||
|
||||
@ -108,10 +108,6 @@ open class CaretView: MFView {
|
||||
// Configure class properties with JSON values
|
||||
guard let dictionary = json else { return }
|
||||
|
||||
if let backgroundColorHex = dictionary[KeyBackgroundColor] as? String {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorHex)
|
||||
}
|
||||
|
||||
if let strokeColorHex = dictionary["strokeColor"] as? String {
|
||||
strokeColor = UIColor.mfGet(forHex: strokeColorHex)
|
||||
}
|
||||
|
||||
@ -74,10 +74,6 @@ open class DashLine: MFView {
|
||||
// Configure class properties with JSON values
|
||||
guard let jsonDictionary = json else { return }
|
||||
|
||||
if let backgroundColorHex = jsonDictionary[KeyBackgroundColor] as? String {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorHex)
|
||||
}
|
||||
|
||||
if let isHiddenValue = jsonDictionary[KeyIsHidden] as? Bool {
|
||||
isHidden = isHiddenValue
|
||||
}
|
||||
|
||||
@ -247,10 +247,11 @@ public typealias ActionBlock = () -> Void
|
||||
case "action":
|
||||
guard let actionLabel = label as? Label else { continue }
|
||||
|
||||
actionLabel.addTappableLinkAttribute(range: range,
|
||||
actionMap: json,
|
||||
additionalData: additionalData,
|
||||
delegateObject: delegate)
|
||||
actionLabel.addActionAttributes(range: range, string: attributedString)
|
||||
actionLabel.clauses.append(ActionableClause(range: range,
|
||||
actionBlock: actionLabel.createActionBlockFrom(actionMap: json,
|
||||
additionalData: additionalData,
|
||||
delegateObject: delegate)))
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@ -340,6 +341,25 @@ public typealias ActionBlock = () -> Void
|
||||
standardFontSize = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///Appends an external link image to the end of the attributed string.
|
||||
public func addExternalLinkIcon() {
|
||||
|
||||
let size = round(font.pointSize * 0.8)
|
||||
|
||||
guard let attributedText = self.attributedText else { return }
|
||||
|
||||
let fullString = NSMutableAttributedString(attributedString: attributedText)
|
||||
|
||||
let imageAttachment = NSTextAttachment()
|
||||
imageAttachment.image = MVMCoreUIUtility.imageNamed("externalLink")
|
||||
imageAttachment.bounds = CGRect(x: 0, y: 0, width: size, height: size)
|
||||
|
||||
fullString.append(NSAttributedString(string: " "))
|
||||
fullString.append(NSAttributedString(attachment: imageAttachment))
|
||||
self.attributedText = fullString
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Atomization
|
||||
@ -369,6 +389,10 @@ extension Label {
|
||||
public func alignment() -> UIStackView.Alignment {
|
||||
return .leading
|
||||
}
|
||||
|
||||
public func copyBackgroundColor() -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Multi-Action Functionality
|
||||
@ -397,13 +421,18 @@ extension Label {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func setDefaultAttributes(range: NSRange) {
|
||||
func addActionAttributes(range: NSRange, string: NSMutableAttributedString?) {
|
||||
|
||||
guard let string = string else { return }
|
||||
string.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range)
|
||||
}
|
||||
|
||||
fileprivate func setActionAttributes(range: NSRange) {
|
||||
|
||||
guard let attributedText = attributedText else { return }
|
||||
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText)
|
||||
mutableAttributedString.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range)
|
||||
|
||||
addActionAttributes(range: range, string: mutableAttributedString)
|
||||
self.attributedText = mutableAttributedString
|
||||
}
|
||||
|
||||
@ -417,7 +446,7 @@ extension Label {
|
||||
*/
|
||||
@objc public func addTappableLinkAttribute(range: NSRange, actionBlock: @escaping ActionBlock) {
|
||||
|
||||
setDefaultAttributes(range: range)
|
||||
setActionAttributes(range: range)
|
||||
clauses.append(ActionableClause(range: range, actionBlock: actionBlock))
|
||||
}
|
||||
|
||||
@ -433,7 +462,7 @@ extension Label {
|
||||
*/
|
||||
@objc public func addTappableLinkAttribute(range: NSRange, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) {
|
||||
|
||||
setDefaultAttributes(range: range)
|
||||
setActionAttributes(range: range)
|
||||
clauses.append(ActionableClause(range: range,
|
||||
actionBlock: createActionBlockFrom(actionMap: actionMap,
|
||||
additionalData: additionalData,
|
||||
|
||||
@ -170,10 +170,6 @@ import Foundation
|
||||
leftTextLabel.setWithJSON(dictionary.optionalDictionaryForKey("leftText"), delegateObject: delegateObject as? MVMCoreUIDelegateObject, additionalData: additionalData)
|
||||
rightTextLabel.setWithJSON(dictionary.optionalDictionaryForKey("rightText"), delegateObject: delegateObject as? MVMCoreUIDelegateObject, additionalData: additionalData)
|
||||
|
||||
if let backgroundColorHex = dictionary[KeyBackgroundColor] as? String {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorHex)
|
||||
}
|
||||
|
||||
if !leftTextLabel.hasText {
|
||||
constrainRightLabel()
|
||||
} else if !rightTextLabel.hasText {
|
||||
|
||||
@ -18,12 +18,17 @@ import UIKit
|
||||
var heightConstraint: NSLayoutConstraint?
|
||||
var loadingSpinnerHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
// Allows for a view to hardcode which height to use if there is none in the json.
|
||||
var imageWidth: CGFloat?
|
||||
var imageHeight: CGFloat?
|
||||
|
||||
// For keeping track of current state.
|
||||
var edges: UIRectEdge?
|
||||
var spinnerHeight: CGFloat?
|
||||
var width: CGFloat?
|
||||
var loadingImageName: String?
|
||||
var isFallbackImage: Bool = false
|
||||
private var edges: UIRectEdge?
|
||||
private var spinnerHeight: CGFloat?
|
||||
private var currentImageWidth: CGFloat?
|
||||
private var currentImageHeight: CGFloat?
|
||||
private var currentImageName: String?
|
||||
private var isFallbackImage: Bool = false
|
||||
|
||||
public init(pinnedEdges edge: UIRectEdge) {
|
||||
edges = edge
|
||||
@ -50,62 +55,24 @@ import UIKit
|
||||
imageView.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: NSLayoutConstraint.Axis.vertical)
|
||||
}
|
||||
|
||||
if let topPin = topPin {
|
||||
removeConstraint(topPin)
|
||||
}
|
||||
if edge.contains(UIRectEdge.top) {
|
||||
topPin = imageView.topAnchor.constraint(equalTo: topAnchor)
|
||||
if edge.contains(UIRectEdge.top) && edge.contains(UIRectEdge.bottom) {
|
||||
alignFillVertical()
|
||||
} else if edge.contains(UIRectEdge.top) {
|
||||
alignTop()
|
||||
} else if edge.contains(UIRectEdge.bottom) {
|
||||
alignBottom()
|
||||
} else {
|
||||
topPin = imageView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor)
|
||||
alignCenterVertical()
|
||||
}
|
||||
topPin?.isActive = true
|
||||
|
||||
if let bottomPin = bottomPin {
|
||||
removeConstraint(bottomPin)
|
||||
}
|
||||
if edge.contains(UIRectEdge.bottom) {
|
||||
bottomPin = bottomAnchor.constraint(equalTo: imageView.bottomAnchor)
|
||||
if edge.contains(UIRectEdge.left) && edge.contains(UIRectEdge.right) {
|
||||
alignFillHorizontal()
|
||||
} else if edge.contains(UIRectEdge.left) {
|
||||
alignLeft()
|
||||
} else if edge.contains(UIRectEdge.right) {
|
||||
alignRight()
|
||||
} else {
|
||||
bottomPin = bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor)
|
||||
}
|
||||
bottomPin?.isActive = true
|
||||
|
||||
if let leftPin = leftPin {
|
||||
removeConstraint(leftPin)
|
||||
}
|
||||
if edge.contains(UIRectEdge.left) {
|
||||
leftPin = imageView.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
} else {
|
||||
leftPin = imageView.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor)
|
||||
}
|
||||
leftPin?.isActive = true
|
||||
|
||||
if let rightPin = rightPin {
|
||||
removeConstraint(rightPin)
|
||||
}
|
||||
if edge.contains(UIRectEdge.right) {
|
||||
rightPin = rightAnchor.constraint(equalTo: imageView.rightAnchor)
|
||||
} else {
|
||||
rightPin = rightAnchor.constraint(greaterThanOrEqualTo: imageView.rightAnchor)
|
||||
}
|
||||
rightPin?.isActive = true
|
||||
|
||||
// If neither the top or the bottom are pinned, center it.
|
||||
if let centerY = centerY {
|
||||
removeConstraint(centerY)
|
||||
}
|
||||
if !edge.contains(UIRectEdge.top) && !edge.contains(UIRectEdge.bottom) {
|
||||
centerY = imageView.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
centerY?.isActive = true
|
||||
}
|
||||
|
||||
// If neither the left or the right are pinned, center it.
|
||||
if let centerX = centerX {
|
||||
removeConstraint(centerX)
|
||||
}
|
||||
if !edge.contains(UIRectEdge.left) && !edge.contains(UIRectEdge.right) {
|
||||
centerX = imageView.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
centerX?.isActive = true
|
||||
alignCenterHorizontal()
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +88,7 @@ import UIKit
|
||||
// Setup image.
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false;
|
||||
addSubview(imageView)
|
||||
pinView(toSuperView: imageView)
|
||||
|
||||
// Setup edges constraints
|
||||
if edges == nil {
|
||||
@ -159,10 +127,30 @@ import UIKit
|
||||
|
||||
open func shouldLoadImage(withName imageName: String?, width: CGFloat) -> Bool {
|
||||
// We should load a new image if there is no current image, the image names are different, the width is different, or we are using a fallback image.
|
||||
guard let currentWidth = self.width else {
|
||||
guard let currentWidth = self.currentImageWidth else {
|
||||
return true
|
||||
}
|
||||
return (imageView.image == nil && imageView.animatedImage == nil) || imageName != loadingImageName || !MVMCoreGetterUtility.cgfequal(width, currentWidth) || self.isFallbackImage
|
||||
return (imageView.image == nil && imageView.animatedImage == nil) || imageName != currentImageName || !MVMCoreGetterUtility.cgfequal(width, currentWidth) || isFallbackImage
|
||||
}
|
||||
|
||||
open func shouldLoadImage(withName imageName: String?, width: CGFloat?, height: CGFloat?) -> Bool {
|
||||
// We should load a new image if there is no current image, the image names are different, or we are using a fallback image.
|
||||
if ((imageView.image == nil && imageView.animatedImage == nil) || imageName != currentImageName || isFallbackImage) {
|
||||
return true
|
||||
}
|
||||
// load new image if the width is different
|
||||
if let oldWidth = self.currentImageWidth, let newWidth = width, !MVMCoreGetterUtility.cgfequal(oldWidth, newWidth) {
|
||||
return true
|
||||
} else if (self.currentImageWidth == nil) != (width == nil) {
|
||||
return true
|
||||
}
|
||||
// load new image if the height is different
|
||||
if let oldHeight = self.currentImageHeight, let newHeight = height, !MVMCoreGetterUtility.cgfequal(oldHeight, newHeight) {
|
||||
return true
|
||||
} else if (self.currentImageHeight == nil) || (height == nil) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Constrains the image view to be the size provided. Used to size it to the image to fix aspect fit defect.
|
||||
@ -191,43 +179,44 @@ import UIKit
|
||||
imageView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical)
|
||||
}
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
let width = size.rounded()
|
||||
if let imageName = json?.optionalStringForKey("image"), shouldLoadImage(withName: imageName, width: width) {
|
||||
imageView.image = nil
|
||||
imageView.animatedImage = nil
|
||||
loadImage(withName: imageName, format: json?.optionalStringForKey("imageFormat"), width: NSNumber(value: Double(width)), height: nil, customFallbackImage: json?.optionalStringForKey("fallbackImage"))
|
||||
}
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol functions
|
||||
open override func setAsMolecule() {
|
||||
addSizeConstraintsForAspectRatio = true
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return json?.optionalCGFloatForKey("height") ?? 0
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol functions
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorString)
|
||||
}
|
||||
if let accessibilityString = json?.optionalStringForKey("accessibilityText") {
|
||||
imageView.accessibilityLabel = accessibilityString
|
||||
imageView.accessibilityTraits = .staticText
|
||||
imageView.isAccessibilityElement = true
|
||||
}
|
||||
let width = json?.optionalCGFloatForKey("width") ?? imageWidth
|
||||
let height = json?.optionalCGFloatForKey("height") ?? imageHeight
|
||||
if let imageName = json?.optionalStringForKey("image"), shouldLoadImage(withName: imageName, width: width, height: height) {
|
||||
imageView.image = nil
|
||||
imageView.animatedImage = nil
|
||||
loadImage(withName: imageName, format: json?.optionalStringForKey("imageFormat"), width: width as NSNumber?, height: height as NSNumber?, customFallbackImage: json?.optionalStringForKey("fallbackImage"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - load functions
|
||||
public func loadImage(withName imageName: String?, format: String?, width: NSNumber?, height: NSNumber?, customFallbackImage: String?, completionHandler: @escaping MVMCoreGetImageBlock) {
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: { [unowned self] in
|
||||
self.loadingImageName = imageName
|
||||
if let width = width {
|
||||
self.width = width.cgfloat()
|
||||
}
|
||||
self.currentImageName = imageName
|
||||
self.currentImageWidth = width?.cgfloat()
|
||||
self.currentImageHeight = height?.cgfloat()
|
||||
self.loadingSpinner.resumeSpinnerAfterDelay()
|
||||
if let height = self.spinnerHeight {
|
||||
self.loadingSpinnerHeightConstraint?.constant = height
|
||||
}
|
||||
|
||||
let finishedLoadingBlock: MVMCoreGetImageBlock = {[weak self] (image, data, isFallbackImage) in MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
|
||||
guard let loadingImageName = self?.loadingImageName, loadingImageName == imageName else {
|
||||
guard let loadingImageName = self?.currentImageName, loadingImageName == imageName else {
|
||||
return
|
||||
}
|
||||
self?.isFallbackImage = isFallbackImage
|
||||
@ -250,14 +239,14 @@ import UIKit
|
||||
public func loadCroppedImage(withName imageName:
|
||||
String?, width: NSNumber?, height: NSNumber?, cropRect: CGRect, flipImage: Bool, customFallbackImage: String?) {
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: { [unowned self] in
|
||||
self.loadingImageName = imageName
|
||||
self.currentImageName = imageName
|
||||
self.loadingSpinner.resumeSpinnerAfterDelay()
|
||||
if let height = self.spinnerHeight {
|
||||
self.loadingSpinnerHeightConstraint?.constant = height
|
||||
}
|
||||
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?.loadingImageName, loadingImageName == imageName else {
|
||||
guard let image = image, let loadingImageName = self?.currentImageName, loadingImageName == imageName else {
|
||||
return
|
||||
}
|
||||
self?.loadingSpinnerHeightConstraint?.constant = 0
|
||||
|
||||
@ -8,6 +8,9 @@
|
||||
|
||||
#import "MFView.h"
|
||||
@import MVMCore.Swift;
|
||||
@import MVMCore.NSDictionary_MFConvenience;
|
||||
#import "MVMCoreUIConstants.h"
|
||||
#import "UIColor+MFConvenience.h"
|
||||
|
||||
@implementation MFView
|
||||
|
||||
@ -36,7 +39,6 @@
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
self.preservesSuperviewLayoutMargins = YES;
|
||||
}
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
@ -44,8 +46,17 @@
|
||||
|
||||
#pragma mark - MVMCoreUIMoleculeViewProtocol
|
||||
|
||||
- (void)reset {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData {
|
||||
self.json = json;
|
||||
|
||||
NSString *colorString = [json string:KeyBackgroundColor];
|
||||
if (colorString) {
|
||||
self.backgroundColor = [UIColor mfGetColorForHex:colorString];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -79,6 +79,9 @@ static const CGFloat CheckBoxHeightWidth = 18.0;
|
||||
additionalData: additionalData];
|
||||
}
|
||||
|
||||
+ (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject {
|
||||
return CheckBoxHeightWidth;
|
||||
}
|
||||
|
||||
#pragma mark - convenient class methods
|
||||
|
||||
|
||||
@ -31,4 +31,6 @@ typedef void(^ValueChangeBlock)(void);
|
||||
// Sets the state without triggering the value changed block.
|
||||
- (void)setState:(BOOL)stateWithoutBlock withoutBlockAnimated:(BOOL)animated;
|
||||
|
||||
+ (CGFloat)getSwitchHeight;
|
||||
|
||||
@end
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <MVMCoreUI/MFView.h>
|
||||
#import <MVMCoreUI/MVMCoreUIViewConstrainingProtocol.h>
|
||||
@class MFSizeObject;
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
@ -15,7 +16,7 @@ typedef enum : NSUInteger {
|
||||
SeparatorPositionBot
|
||||
} SeparatorPosition;
|
||||
|
||||
@interface SeparatorView : MFView
|
||||
@interface SeparatorView : MFView <MVMCoreUIViewConstrainingProtocol>
|
||||
|
||||
@property (nullable, weak, nonatomic) NSLayoutConstraint *height;
|
||||
@property (nullable, weak, nonatomic) NSLayoutConstraint *leftPin;
|
||||
|
||||
@ -70,10 +70,6 @@
|
||||
|
||||
#pragma mark - set up
|
||||
|
||||
- (void)reset {
|
||||
[self setAsLight];
|
||||
}
|
||||
|
||||
- (void)updateView:(CGFloat)size {
|
||||
[super updateView:size];
|
||||
self.height.constant = [self.heightSizeObject getValueBasedOnSize:size];
|
||||
@ -115,10 +111,6 @@
|
||||
[self setAsLight];
|
||||
}
|
||||
}
|
||||
NSString *backgroundColor = [json string:KeyBackgroundColor];
|
||||
if (backgroundColor) {
|
||||
self.backgroundColor = [UIColor mfGetColorForHex:backgroundColor];
|
||||
}
|
||||
} else {
|
||||
self.hidden = YES;
|
||||
}
|
||||
@ -165,5 +157,34 @@
|
||||
[self setNeedsLayout];
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - Molecule
|
||||
|
||||
- (void)reset {
|
||||
[self setAsLight];
|
||||
}
|
||||
|
||||
- (BOOL)needsToBeConstrained {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)copyBackgroundColor {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject {
|
||||
NSString *type = [json string:KeyType];
|
||||
if ([type isEqualToString:@"none"]) {
|
||||
return 0;
|
||||
} else {
|
||||
if ([type isEqualToString:@"medium"]) {
|
||||
return 2;
|
||||
} else if ([type isEqualToString:@"heavy"]) {
|
||||
return 4;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -12,28 +12,33 @@
|
||||
@interface ViewConstrainingView : MFView
|
||||
|
||||
// Dont set these directly.
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *leftPin;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *rightPin;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *topPin;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *bottomPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *leftPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *rightPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *topPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *bottomPin;
|
||||
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterPin;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterLeftPin;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterRightPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *alignCenterPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *alignCenterLeftPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *alignCenterRightPin;
|
||||
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterVerticalPin;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterTopPin;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterBottomPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *alignCenterVerticalPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *alignCenterTopPin;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *alignCenterBottomPin;
|
||||
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *leftPinLow;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *rightPinLow;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *topPinLow;
|
||||
@property (nullable, strong, nonatomic) NSLayoutConstraint *bottomPinLow;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *leftPinLow;
|
||||
@property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *rightPinLow;
|
||||
@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;
|
||||
|
||||
/// A flag for if we should add a molecule from json.
|
||||
@property (nonatomic) BOOL shouldSetupMoleculeFromJSON;
|
||||
|
||||
// Returns an empty view
|
||||
+ (nonnull ViewConstrainingView *)emptyView;
|
||||
@ -44,6 +49,8 @@
|
||||
// Can be initialized with a molecule to constrain
|
||||
- (nullable instancetype)initWithMolecule:(nonnull UIView <MVMCoreUIMoleculeViewProtocol>*)molecule alignment:(UIStackViewAlignment)alignment;
|
||||
|
||||
#pragma mark - Constraining
|
||||
|
||||
// Use these to sets the constants, because subclasses may align differently.
|
||||
- (void)setPinConstantsWithInsets:(UIEdgeInsets)insets;
|
||||
- (void)setTopPinConstant:(CGFloat)top left:(CGFloat)left bottom:(CGFloat)bottom right:(CGFloat)right;
|
||||
@ -63,9 +70,6 @@
|
||||
// Resets all the constraints to default.
|
||||
- (void)resetConstraints;
|
||||
|
||||
// For setting up the view.
|
||||
- (void)setupView;
|
||||
|
||||
// Add a view to be constrained in this view.
|
||||
- (void)addConstrainedView:(nonnull UIView *)view;
|
||||
|
||||
@ -82,4 +86,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
|
||||
|
||||
@ -9,12 +9,16 @@
|
||||
#import "ViewConstrainingView.h"
|
||||
@import MVMCore.MVMCoreConstants;
|
||||
@import MVMCore.MVMCoreDispatchUtility;
|
||||
@import MVMCore.NSDictionary_MFConvenience;
|
||||
#import "NSLayoutConstraint+MFConvenience.h"
|
||||
#import "MFStyler.h"
|
||||
#import "MVMCoreUIConstants.h"
|
||||
#import "MVMCoreUIMoleculeMappingObject.h"
|
||||
#import "MVMCoreUIUtility.h"
|
||||
#import "MVMCoreUIViewConstrainingProtocol.h"
|
||||
|
||||
@interface ViewConstrainingView ()
|
||||
@property (weak, nullable, nonatomic) UIView *constrainedView;
|
||||
@property (weak, nullable, nonatomic) UIView <MVMCoreUIMoleculeViewProtocol>*molecule;
|
||||
@end
|
||||
|
||||
@implementation ViewConstrainingView
|
||||
@ -26,6 +30,7 @@
|
||||
[self setAsMolecule];
|
||||
}
|
||||
self.molecule = molecule;
|
||||
[self setMoleculeAccessibility];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -48,11 +53,21 @@
|
||||
#pragma mark - Constraining
|
||||
|
||||
- (void)pinViewToSuperView:(nonnull UIView *)view {
|
||||
NSDictionary *dictionary = [NSLayoutConstraint constraintPinSubviewToSuperview:view];
|
||||
self.leftPin = dictionary[ConstraintLeading];
|
||||
self.topPin = dictionary[ConstraintTop];
|
||||
self.bottomPin = dictionary[ConstraintBot];
|
||||
self.rightPin = dictionary[ConstraintTrailing];
|
||||
NSLayoutConstraint *constraint = [view.leftAnchor constraintEqualToAnchor:view.superview.leftAnchor];
|
||||
constraint.active = YES;
|
||||
self.leftPin = constraint;
|
||||
|
||||
constraint = [view.topAnchor constraintEqualToAnchor:view.superview.topAnchor];
|
||||
constraint.active = YES;
|
||||
self.topPin = constraint;
|
||||
|
||||
constraint = [view.superview.rightAnchor constraintEqualToAnchor:view.rightAnchor];
|
||||
constraint.active = YES;
|
||||
self.rightPin = constraint;
|
||||
|
||||
constraint = [view.superview.bottomAnchor constraintEqualToAnchor:view.bottomAnchor];
|
||||
constraint.active = YES;
|
||||
self.bottomPin = constraint;
|
||||
|
||||
self.alignCenterPin = [view.centerXAnchor constraintEqualToAnchor:view.superview.centerXAnchor];
|
||||
self.alignCenterLeftPin = [view.leftAnchor constraintGreaterThanOrEqualToAnchor:view.superview.leftAnchor];
|
||||
@ -120,10 +135,12 @@
|
||||
|
||||
- (void)show {
|
||||
self.topPin.active = YES;
|
||||
self.topPinLow.active = YES;
|
||||
}
|
||||
|
||||
- (void)hide {
|
||||
self.topPin.active = NO;
|
||||
self.topPinLow.active = NO;
|
||||
}
|
||||
|
||||
- (void)resetConstraints {
|
||||
@ -265,7 +282,15 @@
|
||||
}
|
||||
|
||||
- (void)shouldSetHorizontalMargins:(BOOL)shouldSet {
|
||||
self.updateViewHorizontalDefaults = shouldSet;
|
||||
if (![self.json optionalNumberForKey:@"useHorizontalMargins"]) {
|
||||
self.updateViewHorizontalDefaults = shouldSet;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)shouldSetVerticalMargins:(BOOL)shouldSet {
|
||||
if (![self.json optionalNumberForKey:@"useVerticalMargins"]) {
|
||||
self.updateViewVerticalDefaults = shouldSet;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MVMCoreViewProtocol
|
||||
@ -274,11 +299,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 {
|
||||
@ -286,32 +307,83 @@
|
||||
if ([self.constrainedView respondsToSelector:@selector(updateView:)]) {
|
||||
[((id<MVMCoreViewProtocol>)self.constrainedView) 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)setMoleculeAccessibility {
|
||||
if (self.molecule) {
|
||||
self.isAccessibilityElement = NO;
|
||||
if (self.molecule.accessibilityElements) {
|
||||
self.accessibilityElements = self.molecule.accessibilityElements;
|
||||
} else {
|
||||
self.accessibilityElements = @[self.molecule];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
if ([self.constrainedView respondsToSelector:@selector(reset)]) {
|
||||
[self.constrainedView performSelector:@selector(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)setAsMolecule {
|
||||
self.updateViewHorizontalDefaults = YES;
|
||||
}
|
||||
|
||||
- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData {
|
||||
[super setWithJSON:json delegateObject:delegateObject additionalData:additionalData];
|
||||
if (self.molecule) {
|
||||
[self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData];
|
||||
|
||||
[self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData];
|
||||
if (self.shouldSetupMoleculeFromJSON && !self.molecule) {
|
||||
NSDictionary *moleculeJSON = [json dict:KeyMolecule];
|
||||
if (moleculeJSON) {
|
||||
UIView <MVMCoreUIMoleculeViewProtocol>*molecule = [[MVMCoreUIMoleculeMappingObject sharedMappingObject] createMoleculeForJSON:moleculeJSON delegateObject:delegateObject constrainIfNeeded:true];
|
||||
if (molecule) {
|
||||
[self insertSubview:molecule atIndex:0];
|
||||
[self pinViewToSuperView:molecule];
|
||||
}
|
||||
self.molecule = molecule;
|
||||
}
|
||||
[self setMoleculeAccessibility];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +89,9 @@
|
||||
// Returns if the screen size has changed.
|
||||
- (BOOL)screenSizeChanged;
|
||||
|
||||
/// If we have new data, this is called. It calls newDataBuildScreen and sets the ui to update.
|
||||
- (void)newDataBuildAndUpdate;
|
||||
|
||||
#pragma mark - Functions To Subclass
|
||||
|
||||
// This view controller should subclass this function and check the load to make sure it has all the needed data. Fills the error object if there are any errors. Returns if we should finish the load or not.
|
||||
@ -131,11 +134,20 @@
|
||||
// Returns an array of modules to observe for when we receive a response with JSON. Subclass this to have the ui update when the returned page types are cached.
|
||||
- (nullable NSArray *)modulesToListenFor;
|
||||
|
||||
// The function that gets called by the notification center when the JSON is updated.
|
||||
/// The function that gets called by the notification center when the JSON is updated, if we have anything we are listening for (pageTypesToListenFor, modulesToListenFor). This function also tells the screen to update (newDataBuildAndUpdate) if we received new json that we were listening for.
|
||||
- (void)responseJSONUpdated:(nonnull NSNotification *)notification;
|
||||
|
||||
// Updates the json dictionary and the screen with the passed in dictionary. Subclass to get any custom behavior if necessary.
|
||||
- (void)updateWithResponsePage:(nullable NSDictionary *)page modules:(nullable NSDictionary *)modules;
|
||||
/// Sets the page on the load object. Default returns true. Return true if the page is loaded and newDataBuildAndUpdate needs to happen. Can subclass to avoid this.
|
||||
- (BOOL)newPageLoaded:(nonnull NSDictionary *)page;
|
||||
|
||||
/// Appends to the modules on the load object. Default returns true. Return true if the modules are loaded and newDataBuildAndUpdate needs to happen. Can subclass to avoid this.
|
||||
- (BOOL)newModulesLoaded:(nonnull NSDictionary *)modules;
|
||||
|
||||
/** Verifies that all needed modules are loaded
|
||||
* @param loadObject The load data from the cache or server.
|
||||
* @param error The error object passed in will be set in the case of an error.
|
||||
* @return True if the calling process should continue. */
|
||||
+ (BOOL)verifyRequiredModulesLoadedForLoadObject:(nullable MVMCoreLoadObject *)loadObject error:(MVMCoreErrorObject *_Nonnull *_Nonnull)error;
|
||||
|
||||
#pragma mark - Navigation Item, Menu, Support, Top Alert
|
||||
|
||||
|
||||
@ -93,9 +93,11 @@
|
||||
#pragma mark - Functions To Subclass
|
||||
|
||||
- (BOOL)shouldFinishProcessingLoad:(nonnull MVMCoreLoadObject *)loadObject error:(MVMCoreErrorObject *_Nonnull *_Nonnull)error {
|
||||
self.loadObject = loadObject;
|
||||
self.pageType = loadObject.pageType;
|
||||
return YES;
|
||||
self.loadObject = loadObject;
|
||||
|
||||
// Verifies all modules needed are loaded.
|
||||
return [MFViewController verifyRequiredModulesLoadedForLoadObject:loadObject error:error];
|
||||
}
|
||||
|
||||
// Sets the screen to use the screen heading.
|
||||
@ -213,53 +215,83 @@
|
||||
// Checks for a page we are listening for.
|
||||
NSArray *pageTypesListeningFor = [self pageTypesToListenFor];
|
||||
NSDictionary *pages = [notification.userInfo dict:KeyPageMap];
|
||||
__block NSDictionary *pageUpdated = nil;
|
||||
__block BOOL newData = NO;
|
||||
[pageTypesListeningFor enumerateObjectsUsingBlock:^(NSString * _Nonnull pageToListenFor, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSDictionary *page = [pages dict:pageToListenFor];
|
||||
if (page) {
|
||||
pageUpdated = page;
|
||||
NSString *pageType = [page string:KeyPageType];
|
||||
if (page && [pageType isEqualToString:self.pageType]) {
|
||||
newData = [self newPageLoaded:page];
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
// Checks for modules we are listening for.
|
||||
NSArray *modulesListeningFor = [self modulesToListenFor];
|
||||
NSDictionary *modules = [notification.userInfo dict:KeyModuleMap];
|
||||
NSDictionary *modulesReceived = [notification.userInfo dict:KeyModuleMap];
|
||||
__block NSMutableDictionary *modulesUpdated = [NSMutableDictionary dictionary];
|
||||
[modulesListeningFor enumerateObjectsUsingBlock:^(NSString * _Nonnull moduleToListenFor, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSDictionary *module = [modules dict:moduleToListenFor];
|
||||
NSDictionary *module = [modulesReceived dict:moduleToListenFor];
|
||||
if (module) {
|
||||
[modulesUpdated setObject:module forKey:moduleToListenFor];
|
||||
}
|
||||
}];
|
||||
if (modulesUpdated.count > 0) {
|
||||
newData = [self newModulesLoaded:modulesUpdated];
|
||||
}
|
||||
|
||||
[self updateWithResponsePage:pageUpdated modules:modulesUpdated];
|
||||
if (newData) {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[self newDataBuildAndUpdate];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateWithResponsePage:(nullable NSDictionary *)page modules:(nullable NSDictionary *)modules {
|
||||
- (BOOL)newPageLoaded:(nonnull NSDictionary *)page {
|
||||
self.loadObject.pageJSON = page;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)newModulesLoaded:(nonnull NSDictionary *)modules {
|
||||
if (self.loadObject.modulesJSON) {
|
||||
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:self.loadObject.modulesJSON];
|
||||
[mutableDictionary addEntriesFromDictionary:modules];
|
||||
self.loadObject.modulesJSON = [NSDictionary dictionaryWithDictionary:mutableDictionary];
|
||||
} else {
|
||||
self.loadObject.modulesJSON = modules;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)verifyRequiredModulesLoadedForLoadObject:(nullable MVMCoreLoadObject *)loadObject error:(MVMCoreErrorObject *_Nonnull *_Nonnull)error {
|
||||
|
||||
if (page || modules.count > 0) {
|
||||
// Check if all needed modules are loaded.
|
||||
__block NSMutableArray *modulesRequired = [NSMutableArray arrayWithArray:[[MVMCoreViewControllerMappingObject sharedViewControllerMappingObject] modulesRequiredForPageType:loadObject.pageType]];
|
||||
if (modulesRequired.count > 0) {
|
||||
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[[loadObject.modulesJSON allKeys] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
|
||||
if (modules) {
|
||||
if (self.loadObject.modulesJSON) {
|
||||
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:self.loadObject.modulesJSON];
|
||||
[mutableDictionary addEntriesFromDictionary:modules];
|
||||
self.loadObject.modulesJSON = [NSDictionary dictionaryWithDictionary:mutableDictionary];
|
||||
} else {
|
||||
self.loadObject.modulesJSON = modules;
|
||||
if (modulesRequired.count == 0) {
|
||||
*stop = YES;
|
||||
} else {
|
||||
NSUInteger index = [modulesRequired indexOfObject:obj];
|
||||
if (index != NSNotFound) {
|
||||
[modulesRequired removeObjectAtIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
if (page) {
|
||||
self.loadObject.pageJSON = page;
|
||||
}
|
||||
|
||||
[self updateUI];
|
||||
[self.view setNeedsLayout];
|
||||
[self.view layoutIfNeeded];
|
||||
}];
|
||||
|
||||
if (modulesRequired.count == 0) {
|
||||
return YES;
|
||||
} else {
|
||||
|
||||
// Error, not all needed modules are loaded.
|
||||
if (error) {
|
||||
*error = [[MVMCoreErrorObject alloc] initWithTitle:nil message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] messageToLog:[modulesRequired description] code:ErrorCodeRequiredModuleNotPresent domain:ErrorDomainNative location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,7 +458,7 @@
|
||||
}
|
||||
|
||||
// Since we have new data, build stuff for the screen and update the ui once the screen is done laying out.
|
||||
[self updateUI];
|
||||
[self newDataBuildAndUpdate];
|
||||
|
||||
self.needToupdateUIOnScreenSizeChanges = YES;
|
||||
|
||||
@ -439,10 +471,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateUI {
|
||||
[self newDataBuildScreen];
|
||||
[self.formValidator enableByValidation];
|
||||
self.needToUpdateUI = YES;
|
||||
- (void)newDataBuildAndUpdate {
|
||||
[MVMCoreDispatchUtility performBlockOnMainThread:^{
|
||||
[self newDataBuildScreen];
|
||||
[self.formValidator enableByValidation];
|
||||
self.needToUpdateUI = YES;
|
||||
[self.view setNeedsLayout];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -64,10 +64,14 @@ import UIKit
|
||||
primaryButton?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?) -> CGFloat {
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return 42
|
||||
}
|
||||
|
||||
open override func copyBackgroundColor() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - Constraining
|
||||
func setupButton() {
|
||||
if let primaryButton = primaryButton, !subviews.contains(primaryButton) {
|
||||
|
||||
374
MVMCoreUI/Molecules/Carousel.swift
Normal file
374
MVMCoreUI/Molecules/Carousel.swift
Normal file
@ -0,0 +1,374 @@
|
||||
//
|
||||
// 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 = true
|
||||
}
|
||||
|
||||
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)
|
||||
registerCells(with: json, delegateObject: delegateObject)
|
||||
setupLayout(with: json)
|
||||
prepareMolecules(with: json)
|
||||
itemWidthPercent = (json?.optionalCGFloatForKey("itemWidthPercent") ?? 100) / 100
|
||||
setAlignment(with: json?.optionalStringForKey("itemAlignment"))
|
||||
collectionViewHeight?.constant = json?.optionalCGFloatForKey("height") ?? 300
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
105
MVMCoreUI/Molecules/HeadlineBody.swift
Normal file
105
MVMCoreUI/Molecules/HeadlineBody.swift
Normal file
@ -0,0 +1,105 @@
|
||||
//
|
||||
// HeadlineBody.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 7/1/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class HeadlineBody: ViewConstrainingView {
|
||||
let headlineLabel = Label.commonLabelH2(true)
|
||||
let messageLabel = Label.commonLabelB2(true)
|
||||
var spaceBetweenLabels: NSLayoutConstraint?
|
||||
var leftConstraintTitle: NSLayoutConstraint?
|
||||
var rightConstraintTitle: NSLayoutConstraint?
|
||||
var leftConstraintMessage: NSLayoutConstraint?
|
||||
var rightConstraintMessage: NSLayoutConstraint?
|
||||
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
headlineLabel.updateView(size)
|
||||
messageLabel.updateView(size)
|
||||
setSpacing()
|
||||
}
|
||||
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
guard subviews.count == 0 else {
|
||||
return
|
||||
}
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
backgroundColor = .clear
|
||||
clipsToBounds = true
|
||||
|
||||
addSubview(headlineLabel)
|
||||
addSubview(messageLabel)
|
||||
|
||||
headlineLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
messageLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
|
||||
topPin = headlineLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0)
|
||||
topPin?.isActive = true
|
||||
|
||||
spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: PaddingTwo)
|
||||
spaceBetweenLabels?.isActive = true
|
||||
|
||||
leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
leftConstraintTitle?.isActive = true
|
||||
|
||||
rightConstraintTitle = rightAnchor.constraint(equalTo: headlineLabel.rightAnchor)
|
||||
rightConstraintTitle?.isActive = true
|
||||
|
||||
leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
leftConstraintMessage?.isActive = true
|
||||
|
||||
rightConstraintMessage = rightAnchor.constraint(equalTo: messageLabel.rightAnchor)
|
||||
rightConstraintMessage?.isActive = true
|
||||
|
||||
bottomPin = bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0)
|
||||
bottomPin?.isActive = true
|
||||
}
|
||||
|
||||
// MARK: - Constraining
|
||||
public func setSpacing() {
|
||||
if headlineLabel.hasText && messageLabel.hasText {
|
||||
spaceBetweenLabels?.constant = PaddingTwo
|
||||
} else {
|
||||
spaceBetweenLabels?.constant = 0
|
||||
}
|
||||
}
|
||||
|
||||
open override func setLeftPinConstant(_ constant: CGFloat) {
|
||||
leftConstraintTitle?.constant = constant
|
||||
leftConstraintMessage?.constant = constant
|
||||
}
|
||||
|
||||
open override func setRightPinConstant(_ constant: CGFloat) {
|
||||
rightConstraintTitle?.constant = constant
|
||||
rightConstraintMessage?.constant = constant
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
let headlineJSON = json?.optionalDictionaryForKey("headline")
|
||||
headlineLabel.setWithJSON(headlineJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
let bodyJSON = json?.optionalDictionaryForKey("body")
|
||||
messageLabel.setWithJSON(bodyJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
headlineLabel.styleH2(true)
|
||||
messageLabel.styleB2(true)
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return 58
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
@import MVMCore.MVMCoreViewProtocol;
|
||||
@class MVMCoreUIDelegateObject;
|
||||
@class MVMCoreErrorObject;
|
||||
|
||||
@protocol MVMCoreUIMoleculeViewProtocol <NSObject, MVMCoreViewProtocol>
|
||||
|
||||
@ -25,11 +26,14 @@
|
||||
|
||||
|
||||
/// For the molecule list to load more efficiently.
|
||||
+ (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json;
|
||||
+ (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject;
|
||||
|
||||
/// Allows the molecule to set its name for reuse. Default could be moleculeName.
|
||||
+ (nullable NSString *)nameForReuse:(nullable NSDictionary *)molecule delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject;
|
||||
|
||||
/// Can return the required modules
|
||||
+ (nullable NSArray <NSString *>*)requiredModules:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
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
|
||||
398
MVMCoreUI/Molecules/MVMCoreUIPageControl.m
Normal file
398
MVMCoreUI/Molecules/MVMCoreUIPageControl.m
Normal file
@ -0,0 +1,398 @@
|
||||
//
|
||||
// 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];
|
||||
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];
|
||||
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
|
||||
107
MVMCoreUI/Molecules/ModuleMolecule.swift
Normal file
107
MVMCoreUI/Molecules/ModuleMolecule.swift
Normal file
@ -0,0 +1,107 @@
|
||||
//
|
||||
// ModuleMolecule.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 6/25/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
open class ModuleMolecule: ViewConstrainingView {
|
||||
|
||||
open var moduleMolecule: (UIView & MVMCoreUIMoleculeViewProtocol)?
|
||||
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
moduleMolecule?.updateView(size)
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
guard let moduleName = json?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else {
|
||||
// Critical error
|
||||
return
|
||||
}
|
||||
|
||||
if moduleMolecule == nil {
|
||||
if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: module, delegateObject: delegateObject, constrainIfNeeded: true) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
open override func setAsMolecule() {
|
||||
super.setAsMolecule()
|
||||
moduleMolecule?.setAsMolecule?()
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
moduleMolecule?.reset?()
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
guard let moduleName = json?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else {
|
||||
// Critical error
|
||||
return 0
|
||||
}
|
||||
return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: module)?.estimatedHeight?(forRow: module, delegateObject: delegateObject) ?? 0
|
||||
}
|
||||
|
||||
public override static func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? {
|
||||
guard let moduleName = molecule?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else {
|
||||
// Critical error
|
||||
return "moduleMolecule<>"
|
||||
}
|
||||
return "moduleMolecule<" + (MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: module)?.name?(forReuse: module, delegateObject: delegateObject) ?? module.stringForkey(KeyMoleculeName)) + ">"
|
||||
}
|
||||
|
||||
public override static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
|
||||
let moduleName = json?.optionalStringForKey("moduleName")
|
||||
if moduleName == nil || delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) == nil {
|
||||
if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) {
|
||||
error?.pointee = errorObject
|
||||
MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject)
|
||||
}
|
||||
}
|
||||
if let moduleName = moduleName {
|
||||
return [moduleName]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIViewConstrainingProtocol
|
||||
open override func useStandardConstraints() -> Bool {
|
||||
return (moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true
|
||||
}
|
||||
|
||||
open override func alignHorizontal(_ alignment: UIStackView.Alignment) {
|
||||
(moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(alignment)
|
||||
}
|
||||
|
||||
open override func alignVertical(_ alignment: UIStackView.Alignment) {
|
||||
(moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.alignVertical?(alignment)
|
||||
}
|
||||
|
||||
open override func shouldSetHorizontalMargins(_ shouldSet: Bool) {
|
||||
(moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(shouldSet)
|
||||
}
|
||||
|
||||
open override func shouldSetVerticalMargins(_ shouldSet: Bool) {
|
||||
(moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(shouldSet)
|
||||
}
|
||||
}
|
||||
142
MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift
Normal file
142
MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift
Normal file
@ -0,0 +1,142 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,11 +39,14 @@ public class StackItem {
|
||||
public class MoleculeStackView: ViewConstrainingView {
|
||||
var contentView: UIView = MVMCoreUICommonViewsUtility.commonView()
|
||||
var items: [StackItem] = []
|
||||
var useStackSpacingBeforeFirstItem = false
|
||||
|
||||
private var moleculesShouldSetHorizontalMargins = false
|
||||
private var moleculesShouldSetVerticalMargins = false
|
||||
|
||||
/// For setting the direction of the stack
|
||||
var axis: NSLayoutConstraint.Axis = .vertical {
|
||||
didSet {
|
||||
updateViewHorizontalDefaults = axis == .horizontal
|
||||
if axis != oldValue {
|
||||
restack()
|
||||
}
|
||||
@ -105,9 +108,12 @@ 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
|
||||
addConstrainedView(contentView)
|
||||
addSubview(contentView)
|
||||
pinView(toSuperView: contentView)
|
||||
contentView.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
||||
contentView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
}
|
||||
@ -120,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?()
|
||||
@ -144,10 +148,6 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
items = self.items
|
||||
}
|
||||
self.items = []
|
||||
|
||||
if let colorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = .mfGet(forHex: colorString)
|
||||
}
|
||||
|
||||
guard let molecules = json?.arrayForKey(KeyMolecules) as? [[String: Any]] else {
|
||||
return
|
||||
@ -157,49 +157,75 @@ 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(.leading)
|
||||
}
|
||||
|
||||
// Adds the molecules and sets the json.
|
||||
for (index, map) in molecules.enumerated() {
|
||||
if let moleculeJSON = map.optionalDictionaryForKey(KeyMolecule) {
|
||||
var view: UIView?
|
||||
if let item = items?[index] {
|
||||
(item.view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
item.update(with: moleculeJSON)
|
||||
item.update(with: map)
|
||||
view = item.view
|
||||
(view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: nil)
|
||||
addStackItem(item, lastItem: index == molecules.count - 1)
|
||||
} else if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) {
|
||||
view = molecule
|
||||
addStackItem(StackItem(with: molecule, json: map), lastItem: index == molecules.count - 1)
|
||||
}
|
||||
|
||||
(view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(axis == .vertical)
|
||||
(view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false)
|
||||
(view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(moleculesShouldSetHorizontalMargins)
|
||||
(view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(moleculesShouldSetVerticalMargins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override static func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? {
|
||||
// This will aggregate names of molecules to make an id.
|
||||
var name = ""
|
||||
guard let molecules = molecule?.optionalArrayForKey(KeyMolecules) else {
|
||||
return name
|
||||
return "stack<>"
|
||||
}
|
||||
var name = "stack<"
|
||||
for case let item as [AnyHashable: AnyHashable] in molecules {
|
||||
if let moleculeName = item.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule, KeyMoleculeName]) {
|
||||
name.append(moleculeName)
|
||||
if let molecule = item.optionalDictionaryForKey(KeyMolecule), let moleculeName = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) {
|
||||
name.append(moleculeName + ",")
|
||||
}
|
||||
}
|
||||
name.append(">")
|
||||
return name
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
guard let items = json?.optionalArrayForKey(KeyMolecules) else {
|
||||
return 0
|
||||
}
|
||||
let horizontal = json?.optionalStringForKey("axis") == "horizontal"
|
||||
var estimatedHeight: CGFloat = 0
|
||||
for case let item as [AnyHashable: AnyHashable] in items {
|
||||
if let molecule = item.optionalDictionaryForKey(KeyMolecule) {
|
||||
let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.estimatedHeight?(forRow: molecule, delegateObject: delegateObject)
|
||||
if !horizontal {
|
||||
// Vertical stack aggregates the items
|
||||
let spacing = item.optionalCGFloatForKey("spacing") ?? (estimatedHeight != 0 ? (json?.optionalCGFloatForKey("spacing") ?? 16) : 0)
|
||||
estimatedHeight += ((height ?? 0) + spacing)
|
||||
} else if let height = height {
|
||||
// Horizontal stack takes the tallest item.
|
||||
estimatedHeight = max(estimatedHeight, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
return estimatedHeight
|
||||
}
|
||||
|
||||
public override static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
|
||||
guard let items = json?.optionalArrayForKey(KeyMolecules) else {
|
||||
return nil
|
||||
}
|
||||
var modules: [String] = []
|
||||
for case let item as [AnyHashable: AnyHashable] in items {
|
||||
if let molecule = item.optionalDictionaryForKey(KeyMolecule), let modulesForMolecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.requiredModules?(molecule, delegateObject: delegateObject, error: error) {
|
||||
modules += modulesForMolecule
|
||||
}
|
||||
}
|
||||
return modules.count > 0 ? modules : nil
|
||||
}
|
||||
|
||||
// MARK: - Adding to stack
|
||||
/// Adds the view to the stack.
|
||||
func addView(_ view: UIView, lastItem: Bool) {
|
||||
@ -221,7 +247,7 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
}
|
||||
if axis == .vertical {
|
||||
if items.count == 0 {
|
||||
pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: spacing)
|
||||
pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0)
|
||||
} else if let previousView = items.last?.view {
|
||||
_ = NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: true)
|
||||
}
|
||||
@ -236,7 +262,7 @@ public class MoleculeStackView: ViewConstrainingView {
|
||||
} else {
|
||||
if items.count == 0 {
|
||||
// First horizontal item has no spacing by default unless told otherwise.
|
||||
pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: stackItem.spacing ?? 0)
|
||||
pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0)
|
||||
} else if let previousView = items.last?.view {
|
||||
_ = NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: false)
|
||||
}
|
||||
|
||||
@ -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,26 +79,44 @@ import UIKit
|
||||
}
|
||||
|
||||
public func setupView() {
|
||||
preservesSuperviewLayoutMargins = false
|
||||
contentView.preservesSuperviewLayoutMargins = false
|
||||
selectionStyle = .none
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if molecule == nil {
|
||||
if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) {
|
||||
contentView.addSubview(moleculeView)
|
||||
var standardConstraints = true
|
||||
if let castView = moleculeView as? MVMCoreUIViewConstrainingProtocol {
|
||||
standardConstraints = castView.useStandardConstraints?() ?? true
|
||||
castView.shouldSetHorizontalMargins?(!standardConstraints)
|
||||
castView.shouldSetVerticalMargins?(!standardConstraints)
|
||||
}
|
||||
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)
|
||||
@ -106,45 +128,41 @@ import UIKit
|
||||
} else {
|
||||
molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
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)
|
||||
// This molecule will by default handle margins.
|
||||
if let castView = molecule as? MVMCoreUIViewConstrainingProtocol {
|
||||
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]?) -> CGFloat {
|
||||
guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule),
|
||||
let theClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON, delegateObject: nil),
|
||||
let estimatedHeightFor = theClass.estimatedHeight else {
|
||||
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 estimatedHeightFor(moleculeJSON)
|
||||
return max(80, height)
|
||||
}
|
||||
|
||||
public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? {
|
||||
if let molecule = molecule?.optionalDictionaryForKey(KeyMolecule),
|
||||
let moleculeName = molecule.optionalStringForKey(KeyMoleculeName),
|
||||
let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping[moleculeName] as? AnyClass,
|
||||
let castClass = moleculeClass as? MVMCoreUIMoleculeViewProtocol.Type,
|
||||
let nameFunc = castClass.name {
|
||||
return nameFunc(molecule, delegateObject)
|
||||
} else {
|
||||
return molecule?.optionalDictionaryForKey(KeyMolecule)?.optionalStringForKey(KeyMoleculeName)
|
||||
guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else {
|
||||
return nil
|
||||
}
|
||||
return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName)
|
||||
}
|
||||
|
||||
public static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
|
||||
guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule),
|
||||
let theClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON) else {
|
||||
return nil
|
||||
}
|
||||
return theClass.requiredModules?(moleculeJSON, delegateObject: delegateObject, error: error)
|
||||
}
|
||||
|
||||
// MARK: - Arrow
|
||||
|
||||
@ -8,102 +8,26 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class StandardFooterView: ViewConstrainingView {
|
||||
let twoButtonView = TwoButtonView(frame: .zero)
|
||||
var textButton = MFTextButton(nil, constrainHeight: true, forWidth: MVMCoreUIUtility.getWidth())
|
||||
var spaceBetweenButtons: NSLayoutConstraint?
|
||||
var leftConstraintTwoButton: NSLayoutConstraint?
|
||||
var rightConstraintTwoButton: NSLayoutConstraint?
|
||||
var leftConstraintTextButton: NSLayoutConstraint?
|
||||
var rightConstraintTextButton: NSLayoutConstraint?
|
||||
var centerAlignTextButton: NSLayoutConstraint?
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
|
||||
public override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
twoButtonView.updateView(size)
|
||||
textButton.updateView(size)
|
||||
setSpacing()
|
||||
}
|
||||
|
||||
public override func setupView() {
|
||||
open class StandardFooterView: ViewConstrainingView {
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
guard subviews.count == 0 else {
|
||||
return
|
||||
}
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
backgroundColor = .clear
|
||||
clipsToBounds = true
|
||||
setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
setContentCompressionResistancePriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
|
||||
addSubview(twoButtonView)
|
||||
addSubview(textButton)
|
||||
|
||||
topPin = twoButtonView.topAnchor.constraint(equalTo: topAnchor, constant: PaddingDefaultVerticalSpacing)
|
||||
topPin?.isActive = true
|
||||
|
||||
spaceBetweenButtons = textButton.topAnchor.constraint(equalTo: twoButtonView.bottomAnchor, constant: PaddingTwo)
|
||||
spaceBetweenButtons?.isActive = true
|
||||
|
||||
leftConstraintTwoButton = twoButtonView.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor)
|
||||
leftConstraintTwoButton?.isActive = true
|
||||
|
||||
rightConstraintTwoButton = layoutMarginsGuide.rightAnchor.constraint(equalTo: twoButtonView.rightAnchor)
|
||||
rightConstraintTwoButton?.isActive = true
|
||||
|
||||
leftConstraintTextButton = textButton.leftAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.leftAnchor)
|
||||
leftConstraintTextButton?.isActive = true
|
||||
|
||||
rightConstraintTextButton = layoutMarginsGuide.rightAnchor.constraint(greaterThanOrEqualTo: textButton.rightAnchor)
|
||||
rightConstraintTextButton?.isActive = true
|
||||
|
||||
centerAlignTextButton = textButton.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
centerAlignTextButton?.isActive = true
|
||||
|
||||
bottomPin = bottomAnchor.constraint(equalTo: textButton.bottomAnchor, constant: PaddingDefaultVerticalSpacing)
|
||||
bottomPin?.isActive = true
|
||||
|
||||
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
|
||||
shouldSetupMoleculeFromJSON = true
|
||||
updateViewVerticalDefaults = true
|
||||
updateViewHorizontalDefaults = true
|
||||
}
|
||||
|
||||
public func setSpacing() {
|
||||
if !(twoButtonView.heightConstraint?.isActive ?? false) && textButton.title(for: UIControl.State.normal)?.count ?? 0 > 0 {
|
||||
spaceBetweenButtons?.constant = PaddingTwo
|
||||
show()
|
||||
} else if !(twoButtonView.heightConstraint?.isActive ?? false) || textButton.title(for: UIControl.State.normal)?.count ?? 0 > 0 {
|
||||
spaceBetweenButtons?.constant = 0
|
||||
show()
|
||||
} else {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
public override func show() {
|
||||
super.show()
|
||||
heightConstraint?.isActive = false
|
||||
topPin?.isActive = true
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
public override func hide() {
|
||||
super.hide()
|
||||
heightConstraint?.isActive = true
|
||||
topPin?.isActive = false
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
if let colorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = .mfGet(forHex: colorString)
|
||||
}
|
||||
twoButtonView.setWithJSON(json?.optionalDictionaryForKey("twoButtonView"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
textButton.setWithJSON(json?.optionalDictionaryForKey("textButton"), delegateObject: delegateObject, additionalData: additionalData)
|
||||
|
||||
// This molecule will by default handle margins.
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false)
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false)
|
||||
}
|
||||
|
||||
public override func reset() {
|
||||
twoButtonView.reset()
|
||||
textButton.reset()
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) {
|
||||
return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing
|
||||
}
|
||||
return 42
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,141 +9,64 @@
|
||||
import UIKit
|
||||
|
||||
public class StandardHeaderView: ViewConstrainingView {
|
||||
let headlineLabel = Label.commonLabelH2(true)
|
||||
let messageLabel = Label.commonLabelB2(true)
|
||||
var separatorView: SeparatorView?
|
||||
var spaceBetweenLabels: NSLayoutConstraint?
|
||||
var leftConstraintTitle: NSLayoutConstraint?
|
||||
var rightConstraintTitle: NSLayoutConstraint?
|
||||
var leftConstraintMessage: NSLayoutConstraint?
|
||||
var rightConstraintMessage: NSLayoutConstraint?
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
|
||||
// MARK: - MVMCoreViewProtocol
|
||||
public override func updateView(_ size: CGFloat) {
|
||||
open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
headlineLabel.updateView(size)
|
||||
messageLabel.updateView(size)
|
||||
separatorView?.updateView(size)
|
||||
setSpacing()
|
||||
if separatorView?.isHidden ?? true {
|
||||
let margins = MVMCoreUIUtility.getMarginsFor(self)
|
||||
MVMCoreUIUtility.setMarginsFor(self, leading: margins.left, top: margins.top, trailing: margins.right, bottom: 0)
|
||||
bottomPin?.constant = 0
|
||||
}
|
||||
}
|
||||
|
||||
public override func setupView() {
|
||||
super.setupView()
|
||||
if separatorView == nil {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
backgroundColor = .clear
|
||||
clipsToBounds = true
|
||||
|
||||
addSubview(headlineLabel)
|
||||
addSubview(messageLabel)
|
||||
|
||||
headlineLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
messageLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
||||
|
||||
topPin = headlineLabel.topAnchor.constraint(equalTo: topAnchor, constant: PaddingDefaultVerticalSpacing)
|
||||
topPin?.isActive = true
|
||||
|
||||
spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: PaddingTwo)
|
||||
spaceBetweenLabels?.isActive = true
|
||||
|
||||
leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
leftConstraintTitle?.isActive = true
|
||||
|
||||
rightConstraintTitle = rightAnchor.constraint(equalTo: headlineLabel.rightAnchor)
|
||||
rightConstraintTitle?.isActive = true
|
||||
|
||||
leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
leftConstraintMessage?.isActive = true
|
||||
|
||||
rightConstraintMessage = rightAnchor.constraint(equalTo: messageLabel.rightAnchor)
|
||||
rightConstraintMessage?.isActive = true
|
||||
|
||||
bottomPin = bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: PaddingDefaultVerticalSpacing)
|
||||
bottomPin?.isActive = true
|
||||
|
||||
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
|
||||
heightConstraint?.priority = UILayoutPriority(rawValue: 950)
|
||||
|
||||
if let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) {
|
||||
separatorView.setAsHeavy()
|
||||
addSubview(separatorView)
|
||||
self.separatorView = separatorView
|
||||
}
|
||||
shouldSetupMoleculeFromJSON = true
|
||||
updateViewVerticalDefaults = true
|
||||
updateViewHorizontalDefaults = true
|
||||
if separatorView == nil, let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) {
|
||||
separatorView.setAsHeavy()
|
||||
addSubview(separatorView)
|
||||
self.separatorView = separatorView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Constraining
|
||||
public func setSpacing() {
|
||||
if headlineLabel.hasText && messageLabel.hasText {
|
||||
spaceBetweenLabels?.constant = PaddingTwo
|
||||
show()
|
||||
} else if headlineLabel.hasText || messageLabel.hasText {
|
||||
spaceBetweenLabels?.constant = 0
|
||||
show()
|
||||
} else {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
public override func show() {
|
||||
super.show()
|
||||
heightConstraint?.isActive = false
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
public override func hide() {
|
||||
super.hide()
|
||||
heightConstraint?.isActive = true
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
public override func setLeftPinConstant(_ constant: CGFloat) {
|
||||
leftConstraintTitle?.constant = constant
|
||||
leftConstraintMessage?.constant = constant
|
||||
super.setLeftPinConstant(constant)
|
||||
separatorView?.leftPin?.constant = constant
|
||||
}
|
||||
|
||||
public override func setRightPinConstant(_ constant: CGFloat) {
|
||||
rightConstraintTitle?.constant = constant
|
||||
rightConstraintMessage?.constant = constant
|
||||
super.setRightPinConstant(constant)
|
||||
separatorView?.rightPin?.constant = constant
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
backgroundColor = .clear
|
||||
headlineLabel.styleH2(true)
|
||||
messageLabel.styleB2(true)
|
||||
separatorView?.setAsHeavy()
|
||||
separatorView?.show()
|
||||
}
|
||||
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
if let colorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = .mfGet(forHex: colorString)
|
||||
}
|
||||
if let colorString = json?.optionalStringForKey("contentColor") {
|
||||
let color = UIColor.mfGet(forHex: colorString)
|
||||
headlineLabel.textColor = color
|
||||
messageLabel.textColor = color
|
||||
separatorView?.backgroundColor = color
|
||||
}
|
||||
|
||||
let headlineJSON = json?.optionalDictionaryForKey("headline")
|
||||
headlineLabel.setWithJSON(headlineJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
let bodyJSON = json?.optionalDictionaryForKey("body")
|
||||
messageLabel.setWithJSON(bodyJSON, delegateObject: delegateObject, additionalData: additionalData)
|
||||
// This molecule will by default handle margins.
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false)
|
||||
(molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false)
|
||||
|
||||
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() {
|
||||
super.reset()
|
||||
separatorView?.setAsHeavy()
|
||||
separatorView?.show()
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) {
|
||||
return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing
|
||||
}
|
||||
return 121
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,5 +83,9 @@ import UIKit
|
||||
public override func alignment() -> UIStackView.Alignment {
|
||||
return UIStackView.Alignment.leading
|
||||
}
|
||||
|
||||
public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
|
||||
return MVMCoreUISwitch.getHeight()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -41,11 +41,8 @@ import UIKit
|
||||
// MARK: - MVMCoreUIMoleculeViewProtocol
|
||||
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
||||
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
||||
if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) {
|
||||
backgroundColor = UIColor.mfGet(forHex: backgroundColorString)
|
||||
}
|
||||
let primaryButtonMap = json?.optionalDictionaryForKey(KeyPrimaryButton)
|
||||
let secondaryButtonMap = json?.optionalDictionaryForKey(KeySecondaryButton)
|
||||
let primaryButtonMap = json?.optionalDictionaryForKey("primaryButton")
|
||||
let secondaryButtonMap = json?.optionalDictionaryForKey("secondaryButton")
|
||||
set(primaryButtonJSON: primaryButtonMap, secondaryButtonJSON: secondaryButtonMap, delegateObject: delegateObject, additionalData: additionalData)
|
||||
}
|
||||
|
||||
|
||||
@ -21,22 +21,19 @@
|
||||
+ (nullable instancetype)sharedMappingObject;
|
||||
|
||||
/// Returns the molecule class.
|
||||
- (nullable Class)getMoleculeClassWithJSON:(nonnull NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject;
|
||||
- (nullable Class)getMoleculeClassWithJSON:(nonnull NSDictionary *)json;
|
||||
|
||||
#pragma mark - Molecule Creation
|
||||
|
||||
/// Creates the molecule for the given name.
|
||||
- (nullable UIView <MVMCoreUIMoleculeViewProtocol>*)createMoleculeForName:(nonnull NSString *)name;
|
||||
|
||||
/// Creates the molecule for the molecule json. Takes into account moduleMolecule as well.
|
||||
/// Creates the molecule for the molecule json.
|
||||
- (nullable UIView <MVMCoreUIMoleculeViewProtocol>*)createMoleculeForJSON:(nonnull NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject;
|
||||
|
||||
/// Creates the molecule for the molecule json. Takes into account moduleMolecule as well. Also checks if the molecule needs to be constrained for a stack/list style situation.
|
||||
/// Creates the molecule for the molecule json. Also checks if the molecule needs to be constrained for a stack/list style situation.
|
||||
- (nullable UIView <MVMCoreUIMoleculeViewProtocol>*)createMoleculeForJSON:(nonnull NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject constrainIfNeeded:(BOOL)constrainIfNeeded;
|
||||
|
||||
#pragma mark - ModuleMolecule Helpers
|
||||
#pragma mark - Convenience
|
||||
|
||||
/// If the molecule is a module molecule, will get the map for the molecule to load from a module. Otherwise nil.
|
||||
+ (nullable NSDictionary *)getMoleculeMapForModuleMolecule:(nullable NSDictionary *)moduleMolecule delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error;
|
||||
+ (nullable NSArray <NSString *>*)getRequiredModulesForJSON:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error;
|
||||
+ (void)addRequiredModulesForJSON:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject moduleList:(nullable NSMutableArray <NSDictionary *>*)moduleList errorList:(nullable NSMutableArray <MVMCoreErrorObject *>*)errorList;
|
||||
|
||||
@end
|
||||
|
||||
@ -10,9 +10,11 @@
|
||||
@import MVMCore.MVMCoreActionUtility;
|
||||
@import MVMCore.NSDictionary_MFConvenience;
|
||||
@import MVMCore.MVMCoreLoadObject;
|
||||
@import MVMCore.MVMCoreErrorObject;
|
||||
#import "MVMCoreUIObject.h"
|
||||
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||
#import "MFTextField.h"
|
||||
#import "MVMCoreUIPageControl.h"
|
||||
#import "MVMCoreUIViewConstrainingProtocol.h"
|
||||
|
||||
@implementation MVMCoreUIMoleculeMappingObject
|
||||
@ -35,6 +37,7 @@
|
||||
@"caretView": CaretView.class,
|
||||
@"caretButton": CaretButton.class,
|
||||
@"textField" : MFTextField.class,
|
||||
@"digitTextField" : MFDigitTextField.class,
|
||||
@"checkbox" : MVMCoreUICheckBox.class,
|
||||
@"progressBarWithLabel" : ProgressBarWithLabel.class,
|
||||
@"progressBar": ProgressBar.class,
|
||||
@ -44,7 +47,13 @@
|
||||
@"listItem": MoleculeTableViewCell.class,
|
||||
@"switchLineItem": SwitchLineItem.class,
|
||||
@"switch": Switch.class,
|
||||
@"leftRightLabelView": LeftRightLabelView.class
|
||||
@"image": MFLoadImageView.class,
|
||||
@"leftRightLabelView": LeftRightLabelView.class,
|
||||
@"moduleMolecule": ModuleMolecule.class,
|
||||
@"headlineBody": HeadlineBody.class,
|
||||
@"carousel": Carousel.class,
|
||||
@"carouselItem": MoleculeCollectionViewCell.class,
|
||||
@"barsPager": MVMCoreUIPageControl.class,
|
||||
} mutableCopy];
|
||||
});
|
||||
return mapping;
|
||||
@ -55,9 +64,8 @@
|
||||
return [MVMCoreActionUtility initializerClassCheck:[MVMCoreUIObject sharedInstance].moleculeMap classToVerify:self];
|
||||
}
|
||||
|
||||
- (nullable Class)getMoleculeClassWithJSON:(nonnull NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject {
|
||||
NSDictionary *moleculeJSON = [MVMCoreUIMoleculeMappingObject getMoleculeMapForModuleMolecule:json delegateObject:delegateObject error:nil] ?: json;
|
||||
NSString *moleculeName = [moleculeJSON string:KeyMoleculeName];
|
||||
- (nullable Class)getMoleculeClassWithJSON:(nonnull NSDictionary *)json {
|
||||
NSString *moleculeName = [json string:KeyMoleculeName];
|
||||
if (moleculeName) {
|
||||
return [self.moleculeMapping objectForKey:moleculeName];
|
||||
}
|
||||
@ -83,36 +91,41 @@
|
||||
}
|
||||
|
||||
- (nullable UIView <MVMCoreUIMoleculeViewProtocol>*)createMoleculeForJSON:(nonnull NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject constrainIfNeeded:(BOOL)constrainIfNeeded {
|
||||
NSDictionary *moleculeJSON = [MVMCoreUIMoleculeMappingObject getMoleculeMapForModuleMolecule:json delegateObject:delegateObject error:nil] ?: json;
|
||||
NSString *moleculeName = [moleculeJSON string:KeyMoleculeName];
|
||||
NSString *moleculeName = [json string:KeyMoleculeName];
|
||||
if (!moleculeName) {
|
||||
return nil;
|
||||
}
|
||||
UIView <MVMCoreUIMoleculeViewProtocol>*molecule = [self createMoleculeForName:moleculeName];
|
||||
|
||||
|
||||
// Check if we need to constrain this view.
|
||||
UIView <MVMCoreUIViewConstrainingProtocol> *castMolecule = [molecule conformsToProtocol:@protocol(MVMCoreUIViewConstrainingProtocol)] ? (UIView <MVMCoreUIViewConstrainingProtocol> *)molecule : nil;
|
||||
if (constrainIfNeeded && [castMolecule respondsToSelector:@selector(needsToBeConstrained)] && [castMolecule needsToBeConstrained]) {
|
||||
molecule = [[ViewConstrainingView alloc] initWithMolecule:molecule alignment:[castMolecule respondsToSelector:@selector(alignment)] ? [castMolecule alignment] : UIStackViewAlignmentFill];
|
||||
}
|
||||
[molecule setWithJSON:moleculeJSON delegateObject:delegateObject additionalData:nil];
|
||||
[molecule setWithJSON:json delegateObject:delegateObject additionalData:nil];
|
||||
return molecule;
|
||||
}
|
||||
|
||||
#pragma mark - ModuleMolecule Helpers
|
||||
#pragma mark - Convenience
|
||||
|
||||
+ (nullable NSDictionary *)getMoleculeMapForModuleMolecule:(nullable NSDictionary *)moduleMolecule delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error {
|
||||
NSString *moleculeName = [moduleMolecule string:KeyMoleculeName];
|
||||
if ([moleculeName isEqualToString:@"moduleMolecule"]) {
|
||||
NSString *moduleName = [moduleMolecule string:@"moduleName"];
|
||||
NSDictionary *module = moduleName ? [delegateObject.moleculeDelegate getModuleWithName:moduleName] : nil;
|
||||
if (!module && error) {
|
||||
*error = [[MVMCoreErrorObject alloc] initWithTitle:nil message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] code:ErrorCodeModuleMolecule domain:ErrorDomainNative location:NSStringFromClass(self)];
|
||||
}
|
||||
return module;
|
||||
+ (nullable NSArray <NSString *>*)getRequiredModulesForJSON:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error {
|
||||
Class <MVMCoreUIMoleculeViewProtocol>theClass = [[MVMCoreUIMoleculeMappingObject sharedMappingObject] getMoleculeClassWithJSON:json];
|
||||
if ([theClass respondsToSelector:@selector(requiredModules:delegateObject:error:)]) {
|
||||
return [theClass requiredModules:json delegateObject:delegateObject error:error];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)addRequiredModulesForJSON:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject moduleList:(nullable NSMutableArray <NSDictionary *>*)moduleList errorList:(nullable NSMutableArray <MVMCoreErrorObject *>*)errorList {
|
||||
MVMCoreErrorObject *error = nil;
|
||||
NSArray *modules = [self getRequiredModulesForJSON:json delegateObject:delegateObject error:&error];
|
||||
if (modules) {
|
||||
[moduleList addObjectsFromArray:modules];
|
||||
}
|
||||
if (error) {
|
||||
[errorList addObject:error];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -21,6 +21,9 @@
|
||||
/// Can be used to override any standard constraints that may be added.
|
||||
- (BOOL)useStandardConstraints;
|
||||
|
||||
/// Determines if the constraining view will copy the background color of the delegate.
|
||||
- (BOOL)copyBackgroundColor;
|
||||
|
||||
/// Will align if it can.
|
||||
- (void)alignHorizontal:(UIStackViewAlignment)alignment;
|
||||
- (void)alignVertical:(UIStackViewAlignment)alignment;
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
//
|
||||
// MoleculeMappingObject+Extension.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 5/23/19.
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension MVMCoreUIMoleculeMappingObject {
|
||||
|
||||
/// Gets the molecule, and if it belonged to a moduleMolecule, the module name or error.
|
||||
static func getMoleculeJSON(for map: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> (molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)? {
|
||||
guard let map = map, let moleculeName = map.optionalStringForKey(KeyMoleculeName) else {
|
||||
return nil
|
||||
}
|
||||
guard moleculeName == "moduleMolecule" else {
|
||||
// Not a module molecule.
|
||||
return (map, nil, nil)
|
||||
}
|
||||
|
||||
guard let moduleName = map.optionalStringForKey("moduleName"),
|
||||
let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else {
|
||||
guard let error = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) else {
|
||||
return nil
|
||||
}
|
||||
MVMCoreUILoggingHandler.shared()?.addError(toLog: error)
|
||||
return (nil, nil, error)
|
||||
}
|
||||
return (module, moduleName, nil)
|
||||
}
|
||||
|
||||
/// Gets the molecule, and if it belonged to a moduleMolecule adds the module name or error to the passed lists.
|
||||
static func getMoleculeJSON(for map: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, moduleNames: inout [String]?, errors: inout [MVMCoreErrorObject]?) -> [AnyHashable: Any]? {
|
||||
guard let molecule = getMoleculeJSON(for: map, delegateObject: delegateObject) else {
|
||||
return nil
|
||||
}
|
||||
if let moduleName = molecule.moduleName {
|
||||
moduleNames?.append(moduleName)
|
||||
}
|
||||
if let error = molecule.error {
|
||||
errors?.append(error)
|
||||
}
|
||||
return molecule.molecule
|
||||
}
|
||||
}
|
||||
@ -99,11 +99,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2;
|
||||
[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);
|
||||
}
|
||||
[MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:verticalPadding trailing:horizontalPadding bottom:verticalPadding];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
6
MVMCoreUI/SupportingFiles/Media.xcassets/Contents.json
Normal file
6
MVMCoreUI/SupportingFiles/Media.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
23
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/Contents.json
vendored
Normal file
23
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "exportBlack.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "exportBlack@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "exportBlack@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/exportBlack.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/exportBlack.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 345 B |
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/exportBlack@2x.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/exportBlack@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 589 B |
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/exportBlack@3x.png
vendored
Normal file
BIN
MVMCoreUI/SupportingFiles/Media.xcassets/externalLink.imageset/exportBlack@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 869 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 |
@ -47,6 +47,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";
|
||||
|
||||
@ -9,22 +9,23 @@
|
||||
import UIKit
|
||||
|
||||
open class MoleculeListTemplate: ThreeLayerTableViewController {
|
||||
var molecules: [(molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)]?
|
||||
var moleculesInfo: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])]?
|
||||
var observer: NSKeyValueObservation?
|
||||
|
||||
open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject>) -> Bool {
|
||||
var shouldFinish = super.shouldFinishProcessingLoad(loadObject, error: error)
|
||||
guard shouldFinish else {
|
||||
return shouldFinish
|
||||
open override var loadObject: MVMCoreLoadObject? {
|
||||
didSet {
|
||||
if loadObject != oldValue {
|
||||
updateRequiredModules()
|
||||
observer?.invalidate()
|
||||
if let newObject = loadObject {
|
||||
observer = newObject.observe(\MVMCoreLoadObject.pageJSON, options: [.old, .new]) { [weak self] (object, change) in
|
||||
self?.updateRequiredModules()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let firstError = setup()?.first {
|
||||
// Don't continue if there was an error loading needed modules.
|
||||
error.pointee = firstError
|
||||
shouldFinish = false
|
||||
}
|
||||
return shouldFinish
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
@ -43,41 +44,36 @@ open class MoleculeListTemplate: ThreeLayerTableViewController {
|
||||
|
||||
open override func newDataBuildScreen() {
|
||||
super.newDataBuildScreen()
|
||||
_ = setupMoleculeList()
|
||||
setup()
|
||||
registerWithTable()
|
||||
}
|
||||
|
||||
// MARK: - table
|
||||
open override func registerWithTable() {
|
||||
super.registerWithTable()
|
||||
guard let molecules = molecules else {
|
||||
guard let moleculesInfo = moleculesInfo else {
|
||||
return
|
||||
}
|
||||
for molecule in molecules {
|
||||
if let moleculeInfo = getMoleculeInfo(with: molecule), let moleculeToRegister = moleculeInfo.name {
|
||||
tableView?.register(moleculeInfo.class, forCellReuseIdentifier: moleculeToRegister)
|
||||
}
|
||||
for moleculeInfo in moleculesInfo {
|
||||
tableView?.register(moleculeInfo.class, forCellReuseIdentifier: moleculeInfo.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
open override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
guard let molecule = molecules?[indexPath.row],
|
||||
let moleculeInfo = getMoleculeInfo(with: molecule),
|
||||
let estimatedHeight = moleculeInfo.class.estimatedHeight else {
|
||||
guard let moleculeInfo = moleculesInfo?[indexPath.row],
|
||||
let estimatedHeight = moleculeInfo.class.estimatedHeight?(forRow: moleculeInfo.molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) else {
|
||||
return 0
|
||||
}
|
||||
return estimatedHeight(molecule.molecule)
|
||||
return estimatedHeight
|
||||
}
|
||||
|
||||
open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return molecules?.count ?? 0
|
||||
return moleculesInfo?.count ?? 0
|
||||
}
|
||||
|
||||
open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let molecule = molecules?[indexPath.row],
|
||||
let moleculeInfo = getMoleculeInfo(with: molecule),
|
||||
let moleculeName = moleculeInfo.name,
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: moleculeName) else {
|
||||
guard let moleculeInfo = moleculesInfo?[indexPath.row],
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier) else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
let delegate = delegateObject() as? MVMCoreUIDelegateObject
|
||||
@ -86,7 +82,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController {
|
||||
}
|
||||
if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol {
|
||||
protocolCell.reset?()
|
||||
protocolCell.setWithJSON(molecule.molecule, delegateObject: delegate, additionalData: nil)
|
||||
protocolCell.setWithJSON(moleculeInfo.molecule, delegateObject: delegate, additionalData: nil)
|
||||
protocolCell.updateView(tableView.bounds.width)
|
||||
}
|
||||
return cell
|
||||
@ -107,79 +103,64 @@ open class MoleculeListTemplate: ThreeLayerTableViewController {
|
||||
}
|
||||
|
||||
open override func modulesToListenFor() -> [Any]? {
|
||||
// Get all of the molecules that need modules.
|
||||
return modulesNeeded()
|
||||
return loadObject?.requestParameters?.modules
|
||||
}
|
||||
|
||||
// MARK: - Module Molecule Handling
|
||||
/// Returns the (name, class) of the molecule for the given map.
|
||||
func getMoleculeInfo(with molecule: (molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)) -> (name: String?, class: AnyClass)? {
|
||||
guard let map = molecule.molecule, let moleculeName = map.optionalStringForKey(KeyMoleculeName), let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping[moleculeName] as? AnyClass else {
|
||||
// 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])? {
|
||||
guard let molecule = molecule,
|
||||
let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule),
|
||||
let moleculeName = moleculeClass.name?(forReuse: molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) else {
|
||||
return nil
|
||||
}
|
||||
if let moleculeClass = moleculeClass as? MVMCoreUIMoleculeViewProtocol.Type, let moleculeNameFunc = moleculeClass.name {
|
||||
return (moleculeNameFunc(map, delegateObject() as? MVMCoreUIDelegateObject), moleculeClass)
|
||||
} else {
|
||||
return (moleculeName, moleculeClass)
|
||||
}
|
||||
return (moleculeName, moleculeClass, molecule)
|
||||
}
|
||||
|
||||
/// Sets up the molecule list and ensures no errors loading all content.
|
||||
func setupMoleculeList() -> [MVMCoreErrorObject]? {
|
||||
var errors: [MVMCoreErrorObject] = []
|
||||
let delegate = delegateObject() as? MVMCoreUIDelegateObject
|
||||
var moleculeList: [(molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)] = []
|
||||
func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])]? {
|
||||
var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = []
|
||||
if let molecules = loadObject?.pageJSON?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] {
|
||||
for molecule in molecules {
|
||||
if let object = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: molecule, delegateObject: delegate) {
|
||||
if let error = object.error {
|
||||
errors.append(error)
|
||||
} else {
|
||||
moleculeList.append(object)
|
||||
}
|
||||
if let info = getMoleculeInfo(with: molecule) {
|
||||
moleculeList.append(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
molecules = moleculeList
|
||||
return errors.count > 0 ? errors : nil
|
||||
return moleculeList.count > 0 ? moleculeList : nil
|
||||
}
|
||||
|
||||
/// Sets up the header, footer, molecule list and ensures no errors loading all content.
|
||||
func setup() -> [MVMCoreErrorObject]? {
|
||||
var errors: [MVMCoreErrorObject] = []
|
||||
let delegate = delegateObject() as? MVMCoreUIDelegateObject
|
||||
MoleculeListTemplate.addToErrorList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate), errors: &errors)
|
||||
MoleculeListTemplate.addToErrorList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate), errors: &errors)
|
||||
if let newErrors = setupMoleculeList() {
|
||||
errors.append(contentsOf: newErrors)
|
||||
}
|
||||
return errors.count > 0 ? errors : nil
|
||||
}
|
||||
|
||||
static func addToErrorList(with moleculeObject: (molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)?, errors: inout [MVMCoreErrorObject]) {
|
||||
if let error = moleculeObject?.error {
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
static func addToModuleList(with moleculeObject: (molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)?, moduleNames: inout [String]) {
|
||||
if let moduleName = moleculeObject?.moduleName {
|
||||
moduleNames.append(moduleName)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a list of required modules
|
||||
func modulesNeeded() -> [String]? {
|
||||
var modules: [String] = []
|
||||
let delegate = delegateObject() as? MVMCoreUIDelegateObject
|
||||
|
||||
MoleculeListTemplate.addToModuleList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate), moduleNames: &modules)
|
||||
MoleculeListTemplate.addToModuleList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate), moduleNames: &modules)
|
||||
if let molecules = molecules {
|
||||
func setup() {
|
||||
var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = []
|
||||
if let molecules = loadObject?.pageJSON?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] {
|
||||
for molecule in molecules {
|
||||
MoleculeListTemplate.addToModuleList(with: molecule, moduleNames: &modules)
|
||||
if let info = getMoleculeInfo(with: molecule) {
|
||||
moleculeList.append(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
return (modules.count > 0 ? modules : nil)
|
||||
moleculesInfo = moleculeList
|
||||
}
|
||||
|
||||
/// Adds modules from requiredModules() to the MVMCoreViewControllerMapping.requiredModules map.
|
||||
open func updateRequiredModules() {
|
||||
if let requiredModules = requiredModules(), let pageType = pageType {
|
||||
MVMCoreViewControllerMappingObject.shared()?.addRequiredModules(toMapping: requiredModules, forPageType: pageType)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets modules required by the loadObject.pageJSON.
|
||||
open func requiredModules() -> [Any]? {
|
||||
let modules: NSMutableArray = []
|
||||
let delegate = delegateObject() as? MVMCoreUIDelegateObject
|
||||
MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate, moduleList: modules, errorList: nil)
|
||||
MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate, moduleList: modules, errorList: nil)
|
||||
if let molecules = loadObject?.pageJSON?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] {
|
||||
for molecule in molecules {
|
||||
MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: molecule, delegateObject: delegate, moduleList: modules, errorList: nil)
|
||||
}
|
||||
}
|
||||
return modules as? [Any]
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,69 +8,77 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
open class MoleculeStackTemplate: ThreeLayerViewController {
|
||||
|
||||
open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject>) -> Bool {
|
||||
var shouldFinish = super.shouldFinishProcessingLoad(loadObject, error: error)
|
||||
if shouldFinish, let firstError = modulesNeeded().errors?.first {
|
||||
// Don't continue if there was an error loading needed modules.
|
||||
error.pointee = firstError
|
||||
shouldFinish = false
|
||||
}
|
||||
return shouldFinish
|
||||
}
|
||||
|
||||
open override func spaceBetweenTopAndMiddle() -> CGFloat? {
|
||||
return 0
|
||||
}
|
||||
|
||||
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("moleculeStack") else {
|
||||
return nil
|
||||
}
|
||||
return MoleculeStackView(withJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// MARK: - cache handling
|
||||
open override func pageTypesToListenFor() -> [Any]? {
|
||||
guard let pageType = self.pageType else {
|
||||
return super.pageTypesToListenFor()
|
||||
}
|
||||
return [pageType]
|
||||
}
|
||||
|
||||
open override func modulesToListenFor() -> [Any]? {
|
||||
// Get all of the molecules that need modules.
|
||||
return modulesNeeded().modules
|
||||
}
|
||||
|
||||
// MARK: - Module Molecule Handling
|
||||
func modulesNeeded() -> (modules: [String]?, errors: [MVMCoreErrorObject]?) {
|
||||
var modules: [String]? = []
|
||||
var errors: [MVMCoreErrorObject]? = []
|
||||
let delegate = delegateObject() as? MVMCoreUIDelegateObject
|
||||
public class MoleculeStackTemplate: ThreeLayerViewController {
|
||||
var observer: NSKeyValueObservation?
|
||||
|
||||
let _ = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate, moduleNames: &modules, errors: &errors)
|
||||
let _ = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate, moduleNames: &modules, errors: &errors)
|
||||
if let molecules = loadObject?.pageJSON?.optionalArrayForChainOfKeysOrIndexes(["moleculeStack",KeyMolecules]) as? [[AnyHashable: Any]] {
|
||||
for molecule in molecules {
|
||||
let _ = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: molecule, delegateObject: delegate, moduleNames: &modules, errors: &errors)
|
||||
}
|
||||
}
|
||||
return (modules?.count ?? 0 > 0 ? modules : nil, errors?.count ?? 0 > 0 ? errors : nil)
|
||||
}
|
||||
open override var loadObject: MVMCoreLoadObject? {
|
||||
didSet {
|
||||
if loadObject != oldValue {
|
||||
updateRequiredModules()
|
||||
observer?.invalidate()
|
||||
if let newObject = loadObject {
|
||||
observer = newObject.observe(\MVMCoreLoadObject.pageJSON, options: [.old, .new]) { [weak self] (object, change) in
|
||||
self?.updateRequiredModules()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override func spaceBetweenTopAndMiddle() -> CGFloat? {
|
||||
return 0
|
||||
}
|
||||
|
||||
public 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? {
|
||||
guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("moleculeStack") else {
|
||||
return nil
|
||||
}
|
||||
let stack = MoleculeStackView(frame: .zero)
|
||||
stack.useStackSpacingBeforeFirstItem = true
|
||||
stack.setWithJSON(moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil)
|
||||
return stack
|
||||
}
|
||||
|
||||
override public 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
|
||||
}
|
||||
|
||||
// MARK: - cache handling
|
||||
public override func pageTypesToListenFor() -> [Any]? {
|
||||
guard let pageType = self.pageType else {
|
||||
return super.pageTypesToListenFor()
|
||||
}
|
||||
return [pageType]
|
||||
}
|
||||
|
||||
public override func modulesToListenFor() -> [Any]? {
|
||||
return loadObject?.requestParameters?.modules
|
||||
}
|
||||
|
||||
/// Adds modules from requiredModules() to the MVMCoreViewControllerMapping.requiredModules map.
|
||||
open func updateRequiredModules() {
|
||||
if let requiredModules = requiredModules(), let pageType = pageType {
|
||||
MVMCoreViewControllerMappingObject.shared()?.addRequiredModules(toMapping: requiredModules, forPageType: pageType)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets modules required by the loadObject.pageJSON.
|
||||
open func requiredModules() -> [Any]? {
|
||||
let modules: NSMutableArray = []
|
||||
let delegate = delegateObject() as? MVMCoreUIDelegateObject
|
||||
MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate, moduleList: modules, errorList: nil)
|
||||
MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate, moduleList: modules, errorList: nil)
|
||||
MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("moleculeStack"), delegateObject: delegate, moduleList: modules, errorList: nil)
|
||||
return modules as? [Any]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user