From 69309cf3b170be028580e732f13338857e440fa7 Mon Sep 17 00:00:00 2001 From: "Suresh, Kamlesh" Date: Tue, 26 Mar 2019 13:51:33 -0400 Subject: [PATCH] molecule testfields --- MVMCoreUI.xcodeproj/project.pbxproj | 46 +++++++ MVMCoreUI/Atoms/Buttons/PrimaryButton.h | 3 +- MVMCoreUI/Atoms/Buttons/PrimaryButton.m | 62 +++++++-- MVMCoreUI/Atoms/TextFields/MFTextField.h | 6 +- MVMCoreUI/Atoms/TextFields/MFTextField.m | 124 +++++++++++++----- MVMCoreUI/Atoms/TextFields/MFTextField.xib | 14 +- MVMCoreUI/BaseControllers/MFViewController.m | 39 ++++-- .../ThreeLayerViewController.swift | 24 ++-- MVMCoreUI/MVMCoreUI.h | 5 + MVMCoreUI/Models/FormValidator.swift | 14 ++ .../MVMCoreUIFormValidator+FormParams.swift | 33 +++++ .../MVMCoreUIFormValidator+TextFields.swift | 46 +++++++ MVMCoreUI/Models/MVMCoreUIFormValidator.h | 31 +++++ MVMCoreUI/Models/MVMCoreUIFormValidator.m | 79 +++++++++++ MVMCoreUI/Molecules/MFTextFieldListView.swift | 14 +- MVMCoreUI/Molecules/MoleculeStackView.swift | 6 +- .../MVMCoreUIMoleculeMappingObject.m | 7 +- .../MVMCoreUIFormMoleculesProtocol.h | 18 +++ .../MVMCoreUIFormValidationProtocol.h | 27 ++++ .../MoleculeStackCenteredTemplate.swift | 6 +- 20 files changed, 521 insertions(+), 83 deletions(-) create mode 100644 MVMCoreUI/Models/FormValidator.swift create mode 100644 MVMCoreUI/Models/MVMCoreUIFormValidator+FormParams.swift create mode 100644 MVMCoreUI/Models/MVMCoreUIFormValidator+TextFields.swift create mode 100644 MVMCoreUI/Models/MVMCoreUIFormValidator.h create mode 100644 MVMCoreUI/Models/MVMCoreUIFormValidator.m create mode 100644 MVMCoreUI/Protocols/MVMCoreUIFormMoleculesProtocol.h create mode 100644 MVMCoreUI/Protocols/MVMCoreUIFormValidationProtocol.h diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index a222046d..23cb82de 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -7,8 +7,16 @@ objects = { /* Begin PBXBuildFile section */ + 01174B912243E54200A8FE68 /* MVMCoreUIFormValidator+TextFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01174B902243E54200A8FE68 /* MVMCoreUIFormValidator+TextFields.swift */; }; + 01BDA2D522442E6B001DACC9 /* MVMCoreUIFormValidator+FormParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BDA2D422442E6B001DACC9 /* MVMCoreUIFormValidator+FormParams.swift */; }; + 01BDA2D722442F59001DACC9 /* MVMCoreUIFormMoleculesProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 01BDA2D622442F59001DACC9 /* MVMCoreUIFormMoleculesProtocol.h */; }; + 01C0B24A224A9C2000F89DF2 /* FormValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C0B249224A9C2000F89DF2 /* FormValidator.swift */; }; + 01C74D8C22429978009C25A3 /* MVMCoreUIFormValidator.h in Headers */ = {isa = PBXBuildFile; fileRef = 01C74D8A22429978009C25A3 /* MVMCoreUIFormValidator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 01C74D8D22429978009C25A3 /* MVMCoreUIFormValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 01C74D8B22429978009C25A3 /* MVMCoreUIFormValidator.m */; }; + 01C74D8F22429A0F009C25A3 /* MVMCoreUIFormValidationProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 01C74D8E22429A0F009C25A3 /* MVMCoreUIFormValidationProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */; }; 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; + 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */; }; D22D1F1A220341F60077CEC0 /* MVMCoreUICheckBox.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F18220341F50077CEC0 /* MVMCoreUICheckBox.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -161,6 +169,13 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 01174B902243E54200A8FE68 /* MVMCoreUIFormValidator+TextFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIFormValidator+TextFields.swift"; sourceTree = ""; }; + 01BDA2D422442E6B001DACC9 /* MVMCoreUIFormValidator+FormParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIFormValidator+FormParams.swift"; sourceTree = ""; }; + 01BDA2D622442F59001DACC9 /* MVMCoreUIFormMoleculesProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIFormMoleculesProtocol.h; sourceTree = ""; }; + 01C0B249224A9C2000F89DF2 /* FormValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormValidator.swift; sourceTree = ""; }; + 01C74D8A22429978009C25A3 /* MVMCoreUIFormValidator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIFormValidator.h; sourceTree = ""; }; + 01C74D8B22429978009C25A3 /* MVMCoreUIFormValidator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIFormValidator.m; sourceTree = ""; }; + 01C74D8E22429A0F009C25A3 /* MVMCoreUIFormValidationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIFormValidationProtocol.h; sourceTree = ""; }; 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = ""; }; @@ -331,6 +346,27 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 01C74D86224298C3009C25A3 /* Protocols */ = { + isa = PBXGroup; + children = ( + 01C74D8E22429A0F009C25A3 /* MVMCoreUIFormValidationProtocol.h */, + 01BDA2D622442F59001DACC9 /* MVMCoreUIFormMoleculesProtocol.h */, + ); + path = Protocols; + sourceTree = ""; + }; + 01C74D87224298E2009C25A3 /* Models */ = { + isa = PBXGroup; + children = ( + 01C0B249224A9C2000F89DF2 /* FormValidator.swift */, + 01C74D8A22429978009C25A3 /* MVMCoreUIFormValidator.h */, + 01C74D8B22429978009C25A3 /* MVMCoreUIFormValidator.m */, + 01174B902243E54200A8FE68 /* MVMCoreUIFormValidator+TextFields.swift */, + 01BDA2D422442E6B001DACC9 /* MVMCoreUIFormValidator+FormParams.swift */, + ); + path = Models; + sourceTree = ""; + }; D22D1F582204D2590077CEC0 /* LegacyControllers */ = { isa = PBXGroup; children = ( @@ -364,6 +400,8 @@ D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = { isa = PBXGroup; children = ( + 01C74D87224298E2009C25A3 /* Models */, + 01C74D86224298C3009C25A3 /* Protocols */, D29DF31421ECECA7003B2FB9 /* SupportingFiles */, D29DF27021E79B2C003B2FB9 /* OtherHandlers */, D29DF13A21E68682003B2FB9 /* Utility */, @@ -701,6 +739,7 @@ D29DF27921E7A533003B2FB9 /* MVMCoreUISession.h in Headers */, D29DF25C21E6A2B6003B2FB9 /* DashLine.h in Headers */, D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */, + 01C74D8C22429978009C25A3 /* MVMCoreUIFormValidator.h in Headers */, D29DF29D21E7AE38003B2FB9 /* MFStyler.h in Headers */, D29DF2B021E7B3A4003B2FB9 /* MFTextView.h in Headers */, D29DF2A921E7B2F9003B2FB9 /* MVMCoreUIConstants.h in Headers */, @@ -740,6 +779,7 @@ D2C5001D21F8EE67001DA659 /* LabelWithInternalButton.h in Headers */, D29DF11621E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h in Headers */, D29DF17721E69E1F003B2FB9 /* MFTextButton.h in Headers */, + 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */, D29DF16221E69996003B2FB9 /* MFViewController.h in Headers */, D29DF13121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h in Headers */, D29DF2C421E7BF57003B2FB9 /* MFTabBarSwipeAnimator.h in Headers */, @@ -755,9 +795,11 @@ D29770F321F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.h in Headers */, D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */, D29770FD21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h in Headers */, + 01BDA2D722442F59001DACC9 /* MVMCoreUIFormMoleculesProtocol.h in Headers */, D29DF17421E69E1F003B2FB9 /* MFCustomButton.h in Headers */, D29DF29721E7ADB8003B2FB9 /* MFScrollingViewController.h in Headers */, D29DF26F21E6AA0B003B2FB9 /* FLAnimatedImageView.h in Headers */, + 01C74D8F22429A0F009C25A3 /* MVMCoreUIFormValidationProtocol.h in Headers */, D29DF2A121E7AF4E003B2FB9 /* MVMCoreUIUtility.h in Headers */, D29DF17621E69E1F003B2FB9 /* PrimaryButton.h in Headers */, D29DF2C821E7BFC1003B2FB9 /* MFSizeObject.h in Headers */, @@ -850,6 +892,7 @@ files = ( D29DF32121ED0CBA003B2FB9 /* LabelView.m in Sources */, D29770F221F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m in Sources */, + 01BDA2D522442E6B001DACC9 /* MVMCoreUIFormValidator+FormParams.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, @@ -884,6 +927,7 @@ D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */, D29DF2BF21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.m in Sources */, D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, + 01C74D8D22429978009C25A3 /* MVMCoreUIFormValidator.m in Sources */, D29DF28A21E7AC2B003B2FB9 /* MFLabel.m in Sources */, D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, @@ -906,10 +950,12 @@ D29DF17821E69E1F003B2FB9 /* MFCaretButton.m in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, + 01C0B24A224A9C2000F89DF2 /* FormValidator.swift in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D2C5001E21F8EE67001DA659 /* LabelWithInternalButton.m in Sources */, + 01174B912243E54200A8FE68 /* MVMCoreUIFormValidator+TextFields.swift in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h b/MVMCoreUI/Atoms/Buttons/PrimaryButton.h index 182a527b..5947c634 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton.h @@ -11,6 +11,7 @@ #import #import #import +#import "MVMCoreUIFormValidationProtocol.h" typedef enum : NSUInteger { PrimaryButtonTypeRed, @@ -26,7 +27,7 @@ typedef enum : NSUInteger { static CGFloat const PrimaryButtonHeight = 42.0; static CGFloat const PrimaryButtonSmallHeight = 30.0; -@interface PrimaryButton : MFCustomButton +@interface PrimaryButton : MFCustomButton @property (nonatomic, readonly, assign) PrimaryButtonType primaryButtonType; //use reset function to set diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m index 960e29e7..718f5b7f 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m @@ -12,23 +12,26 @@ #import "MVMCoreUISplitViewController.h" #import "MFStyler.h" #import "UIColor+MFConvenience.h" + +#import "MVMCoreUIFormValidator.h" + @import MVMCore.MVMCoreDispatchUtility; @import MVMCore.MVMCoreGetterUtility; @import MVMCore.NSDictionary_MFConvenience; -@interface PrimaryButton () - +@interface PrimaryButton() +@property (nonatomic) BOOL validationRequired; @property (nonatomic) BOOL smallButton; @property (assign, nonatomic) BOOL tinyButton; +@property (nonatomic) CGFloat sizeForSizing; + @property (weak, nonatomic) NSLayoutConstraint *height; @property (weak, nonatomic) NSLayoutConstraint *width; -@property (strong, nonatomic) NSArray *textFields; - +@property (strong, nonatomic) NSMutableArray *textFields; @property (nonatomic, readwrite, assign) PrimaryButtonType primaryButtonType; - -@property (nonatomic) CGFloat sizeForSizing; +@property (strong, nonatomic) MVMCoreUIFormValidator* formValidator; @end @@ -645,6 +648,9 @@ } - (void)setWithJSON:(NSDictionary *)json delegate:(NSObject *)delegate additionalData:(nullable NSDictionary *)additionalData { + + [MVMCoreUIFormValidator setupValidationFor:self delegate:delegate]; + self.primaryButtonType = PrimaryButtonTypeCustom; NSString *color = [json string:@"fillColor"]; if (color) { @@ -666,6 +672,8 @@ if ((color = [json string:@"disabledBorderColor"])) { self.disabledBorderColor = [UIColor mfGetColorForHex:color]; } + self.validationRequired = [json boolForKey:@"validationRequired"]; + [self setAsSmallButton:[json boolForKey:@"small"]]; [self setWithActionMap:json actionDelegate:([delegate conformsToProtocol:@protocol(MVMCoreActionDelegateProtocol)] ? (NSObject *)delegate : nil) additionalData:additionalData buttonDelegate:([delegate conformsToProtocol:@protocol(ButtonDelegateProtocol)] ? (id )delegate : nil)]; } @@ -673,6 +681,8 @@ #pragma mark - Handling Validations - (void)setEnabledByValidity { + + __block BOOL valid = YES; [self.textFields enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (!((MFTextField *)obj).valid) { @@ -680,7 +690,7 @@ *stop = YES; } }]; - + [MVMCoreDispatchUtility performBlockOnMainThread:^{ self.enabled = valid && (self.extraValidationBlock ? self.extraValidationBlock() : YES); }]; @@ -692,7 +702,22 @@ field.mfTextFieldDelegate = nil; } } - self.textFields = textFields; + self.textFields = [textFields mutableCopy]; + + for (MFTextField *field in self.textFields) { + field.mfTextFieldDelegate = self; + [field setDefaultValidationBlock]; + } + [self setEnabledByValidity]; +} + +- (void)addTextFieldsForValidation:(nonnull MFTextField *)textField{ + if (self.textFields == nil) { + self.textFields = [NSMutableArray array]; + } + if (textField) { + [self.textFields addObject:textField]; + } for (MFTextField *field in self.textFields) { field.mfTextFieldDelegate = self; @@ -708,7 +733,7 @@ field.mfTextFieldDelegate = nil; } } - self.textFields = textFields; + self.textFields = [textFields mutableCopy]; for (MFTextField *field in self.textFields) { field.mfTextFieldDelegate = self; @@ -733,4 +758,23 @@ } } +#pragma mark - MVMCoreUIFormValidationProtocol + +- (nullable MVMCoreUIFormValidator *) formValidatorModel { + return self.formValidator; +} + +- (void)setFormValidationModel:(nonnull MVMCoreUIFormValidator *) formValidatorModel { + self.formValidator = formValidatorModel; +} + +- (void)enableField:(BOOL) enable { + + if (self.validationRequired == NO) { + self.enabled = YES; + } else { + self.enabled = enable; + } +} + @end diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.h b/MVMCoreUI/Atoms/TextFields/MFTextField.h index 1be2f3e3..fa7cc6e9 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.h +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.h @@ -8,6 +8,7 @@ #import #import +#import "MVMCoreUIFormValidationProtocol.h" @class PrimaryButton; @class MFTextField; @@ -28,7 +29,7 @@ @end -@interface MFTextField : MFView +@interface MFTextField : MFView @property (nullable, weak, nonatomic) UIView *view; @@ -127,4 +128,7 @@ - (void)setAccessibilityString:(nullable NSString *)accessibilityString; +- (nullable NSString *)formFieldName; +- (nullable id)formFieldValue; + @end diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.m b/MVMCoreUI/Atoms/TextFields/MFTextField.m index 7cdab21a..4cf01441 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.m +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.m @@ -15,17 +15,29 @@ #import "MFLabel.h" #import "MVMCoreUIUtility.h" #import "MVMCoreUIConstants.h" +#import "MVMCoreUIFormValidator.h" + +#import + @import MVMCore.MVMCoreDispatchUtility; @import MVMCore.NSDictionary_MFConvenience; @import MVMCore.MVMCoreJSONConstants; @interface MFTextField() +@property (strong, nonatomic) MVMCoreUIFormValidator* formValidator; @property (strong, nonatomic) UIColor *customPlaceHolderColor; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *separatorHeightConstraint; @property (strong, nonatomic) UIBezierPath *borderPath; @property (strong, nonatomic) NSCalendar *calendar; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *textContainerLeftPin; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *errorLableLeftPin; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *formLabelLeftPin; + +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *textContainerRightPin; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *errorLableRightPin; + @end @implementation MFTextField @@ -53,10 +65,7 @@ view.frame = self.frame; [self addSubview:view]; -// self.textFieldContainerView.layer.borderWidth = 1; -// self.textFieldContainerView.layer.borderColor = [UIColor mfSilver].CGColor; self.textField.font = [MFStyler fontForTextField]; - self.translatesAutoresizingMaskIntoConstraints = NO; self.formLabel.font = [MFStyler fontB3]; @@ -101,40 +110,34 @@ return textField; } +- (void)setWithJSON:(NSDictionary *)json delegate:(nullable id)delegate additionalData:(NSDictionary *)additionalData { + [MVMCoreUIFormValidator setupValidationFor:self delegate:delegate]; + + [self setWithMap:json bothDelegates:delegate]; + self.mfTextFieldDelegate = self.formValidator; + self.uiTextFieldDelegate = self.formValidator; + + [self setVerticalPadding:[MFStyler defaultHorizontalPaddingForApplicationWidth]]; +} + + + +- (void) setVerticalPadding:(CGFloat) padding { + self.textContainerLeftPin.constant = padding; + self.errorLableLeftPin.constant = padding; + self.formLabelLeftPin.constant = padding; + + self.textContainerRightPin.constant = padding; + self.errorLableRightPin.constant = padding; + self.formLabelRightPin.constant = padding; +} + + + (nullable instancetype)mfTextFieldWithMap:(nullable NSDictionary *)map bothDelegates:(nullable id)delegate { MFTextField *textField = [self mfTextField]; textField.translatesAutoresizingMaskIntoConstraints = NO; - [textField setWithMap:map bothDelegates:delegate]; - if (map.count > 0) { - - // Can add these to the set with map function later after verifying - NSString *string = [map string:@"fieldKey"]; - if (string.length > 0) { - textField.fieldKey = string; - } - - string = [map string:KeyType]; - if ([string isEqualToString:@"dropDown"]) { - [[textField dropDownCarrotLabel] setHidden:NO]; - [textField setHasDropDown:YES]; - } else if ([string isEqualToString:@"password"]) { - textField.textField.secureTextEntry = YES; - } else if ([string isEqualToString:@"number"]) { - textField.textField.keyboardType = UIKeyboardTypeNumberPad; - } else if ([string isEqualToString:@"email"]) { - textField.textField.keyboardType = UIKeyboardTypeEmailAddress; - } - - string = [map string:@"regex"]; - if (string.length > 0) { - textField.validationBlock = ^BOOL(NSString * _Nullable enteredValue) { - return [MVMCoreUIUtility validateString:enteredValue withRegularExpression:string]; - }; - } else { - [textField setDefaultValidationBlock]; - } - } - + [textField setWithJSON:map delegate:delegate additionalData:nil]; + [textField setVerticalPadding:0]; return textField; } @@ -320,6 +323,9 @@ - (void)setWithMap:(nullable NSDictionary *)map bothDelegates:(nullable id)delegate { if (map.count > 0) { + [MVMCoreUICommonViewsUtility addDismissToolbar:self.textField delegate:delegate]; + [self setBothTextFieldDelegates:delegate]; + NSString *string = [map string:KeyLabel]; if (string.length > 0) { self.formText = string; @@ -336,8 +342,33 @@ if (string.length > 0) { self.errMessage = string; } - [MVMCoreUICommonViewsUtility addDismissToolbar:self.textField delegate:delegate]; - [self setBothTextFieldDelegates:delegate]; + + // key used to send text value to server + string = [map string:@"fieldKey"]; + if (string.length > 0) { + self.fieldKey = string; + } + + string = [map string:KeyType]; + if ([string isEqualToString:@"dropDown"]) { + [[self dropDownCarrotLabel] setHidden:NO]; + [self setHasDropDown:YES]; + } else if ([string isEqualToString:@"password"]) { + self.textField.secureTextEntry = YES; + } else if ([string isEqualToString:@"number"]) { + self.textField.keyboardType = UIKeyboardTypeNumberPad; + } else if ([string isEqualToString:@"email"]) { + self.textField.keyboardType = UIKeyboardTypeEmailAddress; + } + + string = [map string:@"regex"]; + if (string.length) { + self.validationBlock = ^BOOL(NSString * _Nullable enteredValue) { + return [MVMCoreUIUtility validateString:enteredValue withRegularExpression:string]; + }; + } else { + [self setDefaultValidationBlock]; + } } } @@ -530,4 +561,25 @@ } } +#pragma mark - MVMCoreUIMoleculeViewProtocol + +- (nullable MVMCoreUIFormValidator *) formValidatorModel { + return self.formValidator; +} + +- (void)setFormValidationModel:(nonnull MVMCoreUIFormValidator *) formValidatorModel { + self.formValidator = formValidatorModel; +} + +- (BOOL) isValidField { + return self.valid; +} + +- (nullable NSString *)formFieldName { + return self.fieldKey; +} +- (nullable id)formFieldValue { + return self.text; +} + @end diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.xib b/MVMCoreUI/Atoms/TextFields/MFTextField.xib index a32399fe..359041ad 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.xib +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.xib @@ -1,29 +1,29 @@ - + - + - - - HelveticaNeue - - + + + + + diff --git a/MVMCoreUI/BaseControllers/MFViewController.m b/MVMCoreUI/BaseControllers/MFViewController.m index 1349721d..2b8cd1cf 100644 --- a/MVMCoreUI/BaseControllers/MFViewController.m +++ b/MVMCoreUI/BaseControllers/MFViewController.m @@ -36,9 +36,14 @@ #import "MVMCoreUILoggingHandler.h" #import "MVMCoreUITabBarPageControlViewController.h" #import "MVMCoreUINavigationController.h" +#import "MVMCoreUIFormValidationProtocol.h" +#import "MVMCoreUIFormValidator.h" +#import + + @import MVMAnimationFramework; -@interface MFViewController () +@interface MFViewController() // A flag for if this view controller is observing for cache updates or not. @property (nonatomic) BOOL observingForResponseJSONUpdates; @@ -52,10 +57,19 @@ // title view for navigation bar, used for custom navigation titles @property (weak, nonatomic) UILabel *titleLabel; +@property (strong, nonatomic) MVMCoreUIFormValidator* formValidator; + @end @implementation MFViewController +- (MVMCoreUIFormValidator *)formValidatorModel { + if (self.formValidator == nil) { + self.formValidator = [MVMCoreUIFormValidator new]; + } + return self.formValidator; +} + - (void)dismiss { if (self.presentingViewController) { [[MVMCoreNavigationHandler sharedNavigationHandler] dismissViewController:self animated:YES]; @@ -241,9 +255,10 @@ if (page) { self.loadObject.pageJSON = page; } - - [self newDataBuildScreen]; - self.needToUpdateUI = YES; + + [self updateUI]; +// [self newDataBuildScreen]; +// self.needToUpdateUI = YES; [self.view setNeedsLayout]; [self.view layoutIfNeeded]; }]; @@ -401,11 +416,7 @@ } // Since we have new data, build stuff for the screen. - [self newDataBuildScreen]; - - // Update the UI after the view is loaded. - self.needToUpdateUI = YES; - self.needToupdateUIOnScreenSizeChanges = YES; + [self updateUI]; if (UIAccessibilityIsVoiceOverRunning()) { self.disableAnimations = YES; @@ -416,6 +427,15 @@ } } +- (void) updateUI { + [self newDataBuildScreen]; + [self.formValidator setEnabledByValidity]; + + // Update the UI after the view is loaded. + self.needToUpdateUI = YES; + self.needToupdateUIOnScreenSizeChanges = YES; +} + - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. @@ -587,6 +607,7 @@ [[MVMCoreUISession sharedGlobal].splitViewController.rightPanel willOpenWithActionInformation:actionInformation]; } + [self.formValidator addFormParamsWithRequestParameters:requestParameters]; requestParameters.parentPageType = [self.loadObject.pageJSON stringForKey:@"parentPageType"]; [[MVMCoreLoadHandler sharedGlobal] loadRequest:requestParameters dataForPage:additionalData delegate:self]; diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index 756dd8e8..c1fadf55 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -10,7 +10,7 @@ import UIKit import MVMAnimationFramework -public class ThreeLayerViewController: ProgrammaticScrollViewController { +open class ThreeLayerViewController: ProgrammaticScrollViewController { // The three main views var topView: UIView? @@ -22,8 +22,8 @@ public class ThreeLayerViewController: ProgrammaticScrollViewController { private var safeAreaView: UIView? private var heightConstraint: NSLayoutConstraint? - - public override func updateViews() { + + open override func updateViews() { super.updateViews() let width = view.bounds.width if let topView = topView as? MVMCoreViewProtocol { @@ -37,7 +37,7 @@ public class ThreeLayerViewController: ProgrammaticScrollViewController { } } - public override func updateViewConstraints() { + open override func updateViewConstraints() { super.updateViewConstraints() guard let scrollView = scrollView else { return @@ -50,7 +50,7 @@ public class ThreeLayerViewController: ProgrammaticScrollViewController { } } - public override func loadView() { + open override func loadView() { super.loadView() // The height is used to keep the bottom view at the bottom. if let contentView = contentView, let scrollView = scrollView { @@ -59,7 +59,7 @@ public class ThreeLayerViewController: ProgrammaticScrollViewController { } } - public override func newDataBuildScreen() { + open override func newDataBuildScreen() { super.newDataBuildScreen() // Removes the views @@ -78,27 +78,27 @@ public class ThreeLayerViewController: ProgrammaticScrollViewController { //MARK:-Functions to subclass // Subclass for a top view. - public func viewForTop() -> UIView? { + open func viewForTop() -> UIView? { return nil } // Subclass for a middle view. - public func viewForMiddle() -> UIView? { + open func viewForMiddle() -> UIView? { return nil } // Subclass for a bottom view. - public func viewForBottom() -> UIView? { + open func viewForBottom() -> UIView? { return nil } // If a value is set, the middle view is pinned this value below the top view, if not, space is left to fill. - public func spaceBetweenTopAndMiddle() -> CGFloat? { + open func spaceBetweenTopAndMiddle() -> CGFloat? { return nil } // If a value is set, the middle view is pinned this value above the bottom view, if not, space is left to fill. - public func spaceBetweenMiddleAndBottom() -> CGFloat? { + open func spaceBetweenMiddleAndBottom() -> CGFloat? { return nil } } @@ -246,7 +246,7 @@ extension ThreeLayerViewController { //MARK:-Animation extension ThreeLayerViewController { - public override func setupIntroAnimations() { + open override func setupIntroAnimations() { if let topView = topView, topView.subviews.count > 0 { introAnimationManager?.addAnimation(animation: MVMAnimations.fadeUpAnimation(view: topView)) } diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index 3a8029c4..d43b1eac 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -112,4 +112,9 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import +#import +#import + + + #pragma mark - Templates diff --git a/MVMCoreUI/Models/FormValidator.swift b/MVMCoreUI/Models/FormValidator.swift new file mode 100644 index 00000000..58241740 --- /dev/null +++ b/MVMCoreUI/Models/FormValidator.swift @@ -0,0 +1,14 @@ +// +// FormValidator.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 3/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +class FormValidator: NSObject { + var delegate: MVMCoreUIFormValidationProtocol? + var molecules: [MVMCoreUIFormValidationProtocol]? +} diff --git a/MVMCoreUI/Models/MVMCoreUIFormValidator+FormParams.swift b/MVMCoreUI/Models/MVMCoreUIFormValidator+FormParams.swift new file mode 100644 index 00000000..f21d700e --- /dev/null +++ b/MVMCoreUI/Models/MVMCoreUIFormValidator+FormParams.swift @@ -0,0 +1,33 @@ +// +// MVMCoreUIFormValidator+FormParams.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 3/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc public extension MVMCoreUIFormValidator { + @objc public func addFormParams(requestParameters: MVMCoreRequestParameters) { + DispatchQueue.main.sync { + requestParameters.add(self.getFormParams()) + } + } + + @objc public func getFormParams() -> [String: Any] { + + var extraParam: [String: Any] = [:] + for molecule in self.molecules { + if let molecule = molecule as? MVMCoreUIFormValidationProtocol, + let formFieldName = molecule.formFieldName, + let formFieldValue = molecule.formFieldValue, + let fieldName = formFieldName(), + let fieldValue = formFieldValue() { + + extraParam[fieldName] = fieldValue + } + } + return extraParam + } +} diff --git a/MVMCoreUI/Models/MVMCoreUIFormValidator+TextFields.swift b/MVMCoreUI/Models/MVMCoreUIFormValidator+TextFields.swift new file mode 100644 index 00000000..c966bcfd --- /dev/null +++ b/MVMCoreUI/Models/MVMCoreUIFormValidator+TextFields.swift @@ -0,0 +1,46 @@ +// +// MVMCoreUIFormValidator+TextFields.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 3/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc extension MVMCoreUIFormValidator: UITextFieldDelegate, UITextViewDelegate, MFTextFieldDelegate{ + @objc public func textFieldDidEndEditing(_ textField: UITextField) { + setEnabledByValidity() + } + + @objc public func dismissFieldInput(_ sender: Any?) { + if let delegate = delegate as? MFTextFieldDelegate, + let dismissFieldInput = delegate.dismissFieldInput { + dismissFieldInput(sender) + } + } + + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + + @objc public func textFieldDidBeginEditing(_ textField: UITextField) { + if let delegate = delegate as? UITextFieldDelegate, + let textFieldDidBeginEditing = delegate.textFieldDidBeginEditing { + textFieldDidBeginEditing(textField) + } + } + + @objc public func entryIsValid(_ textfield: MFTextField?) { + DispatchQueue.main.async { + self.setEnabledByValidity() + } + } + + @objc public func entryIsInvalid(_ textfield: MFTextField?) { + DispatchQueue.main.async { + self.setEnabledByValidity() + } + } +} diff --git a/MVMCoreUI/Models/MVMCoreUIFormValidator.h b/MVMCoreUI/Models/MVMCoreUIFormValidator.h new file mode 100644 index 00000000..ddbaddb8 --- /dev/null +++ b/MVMCoreUI/Models/MVMCoreUIFormValidator.h @@ -0,0 +1,31 @@ +// +// MVMCoreUIFormValidator.h +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 3/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +#import +#import +#import "MVMCoreUIFormValidationProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MFTextField; + +@interface MVMCoreUIFormValidator : NSObject + +@property (weak, nonatomic) NSObject * delegate; +@property (strong, nonatomic) NSMutableArray *>* molecules; + +@property (nullable, copy, nonatomic) BOOL(^extraValidationBlock)(void); + +- (void)setEnabledByValidity; +- (void)insertMolecule:(nonnull UIView *) molecule; + ++ (void)setupValidationFor:(nonnull UIView *) molecule delegate:(nonnull NSObject *)delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MVMCoreUI/Models/MVMCoreUIFormValidator.m b/MVMCoreUI/Models/MVMCoreUIFormValidator.m new file mode 100644 index 00000000..b6f968f3 --- /dev/null +++ b/MVMCoreUI/Models/MVMCoreUIFormValidator.m @@ -0,0 +1,79 @@ +// +// MVMCoreUIFormValidator.m +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 3/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +#import "MVMCoreUIFormValidator.h" +#import "MFTextField.h" +@import MVMCore.MVMCoreDispatchUtility; + + +@interface MVMCoreUIFormValidator() +@property (strong, nonatomic) NSMutableArray *textFields; +@end + +@implementation MVMCoreUIFormValidator + + +- (void)insertMolecule:(nonnull UIView *) molecule { + if (self.molecules == nil) { + self.molecules = [NSMutableArray array]; + } + if (molecule) { + [self.molecules addObject:molecule]; + } +} + ++ (void)setupValidationFor:(nonnull UIView *) molecule delegate:(nonnull NSObject *)delegate { + if ([delegate conformsToProtocol:@protocol(MVMCoreUIFormValidationProtocol)] + && [delegate respondsToSelector:@selector(formValidatorModel)] + && [molecule conformsToProtocol:@protocol(MVMCoreUIFormValidationProtocol)] + && [molecule respondsToSelector:@selector(setFormValidationModel:)]) { + + MVMCoreUIFormValidator *validator = [delegate formValidatorModel]; + validator.delegate = delegate; + [validator insertMolecule:molecule]; + [molecule setFormValidationModel:validator]; + } +} + + +- (void)setEnabledByValidity { + __block BOOL valid = YES; + [self.molecules enumerateObjectsUsingBlock:^(UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if ([obj conformsToProtocol:@protocol(MVMCoreUIFormValidationProtocol)] + && [obj respondsToSelector:@selector(isValidField)] + && [obj isValidField] == NO) { + valid = NO; + *stop = YES; + } + }]; + BOOL shouldEnable = valid && (self.extraValidationBlock ? self.extraValidationBlock() : YES); + [self shouldEnable:shouldEnable]; +} + +- (void)shouldEnable:(BOOL) enable { + [self.molecules enumerateObjectsUsingBlock:^(UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + if ([obj conformsToProtocol:@protocol(MVMCoreUIFormValidationProtocol)] + && [obj respondsToSelector:@selector(enableField:)]) { + + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + [obj enableField:enable]; + }]; + } + }]; +} + +- (void)entryIsValid:(nullable MFTextField *)textfield { + [self setEnabledByValidity]; +} + +- (void)entryIsInvalid:(nullable MFTextField *)textfield { + [self shouldEnable:false]; +} + + +@end diff --git a/MVMCoreUI/Molecules/MFTextFieldListView.swift b/MVMCoreUI/Molecules/MFTextFieldListView.swift index fde03981..bb267d29 100644 --- a/MVMCoreUI/Molecules/MFTextFieldListView.swift +++ b/MVMCoreUI/Molecules/MFTextFieldListView.swift @@ -10,7 +10,7 @@ import UIKit import MVMCore -public class MFTextFieldListView: MFView { +public class MFTextFieldListView: ViewConstrainingView { public var textFieldMapList: [[String: Any]]? public var parentViewContoller: MFViewController? @@ -24,7 +24,19 @@ public class MFTextFieldListView: MFView { self.primaryButton = primaryButton super.init(frame: .zero) } + + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegate: NSObject?, additionalData: [AnyHashable : Any]?) { + self.textFieldMapList = json?.arrayForKey("textFields") as? [[String : Any]] + + if let threeLayerVC = delegate as? ThreeLayerViewController { + self.parentViewContoller = threeLayerVC + self.primaryButton = (threeLayerVC.bottomView as? StandardFooterView)?.twoButtonView.primaryButton + } + setupView() + } + + public required init?(coder decoder: NSCoder) { super.init(coder: decoder) } diff --git a/MVMCoreUI/Molecules/MoleculeStackView.swift b/MVMCoreUI/Molecules/MoleculeStackView.swift index a9ea7d8e..ab27c481 100644 --- a/MVMCoreUI/Molecules/MoleculeStackView.swift +++ b/MVMCoreUI/Molecules/MoleculeStackView.swift @@ -16,17 +16,17 @@ public class MoleculeStackView: MFView { super.init(frame: frame) } - init(withJSON json: [AnyHashable : Any]?, delegate: NSObject?, additionalData: [AnyHashable : Any]?) { + public init(withJSON json: [AnyHashable : Any]?, delegate: NSObject?, additionalData: [AnyHashable : Any]?) { super.init(frame: CGRect.zero) setWithJSON(json, delegate: delegate, additionalData: additionalData) } - convenience init(withJSON json: [AnyHashable : Any]?, delegate: NSObject?, spacingBlock: ((Any) -> UIEdgeInsets)?) { + public convenience init(withJSON json: [AnyHashable : Any]?, delegate: NSObject?, spacingBlock: ((Any) -> UIEdgeInsets)?) { self.init(withJSON: json, delegate: delegate, additionalData: nil) self.spacingBlock = spacingBlock } - required init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 3c36a612..9b24f257 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -11,6 +11,7 @@ @import MVMCore.NSDictionary_MFConvenience; #import "MVMCoreUIObject.h" #import +#import "MFTextField.h" @implementation MVMCoreUIMoleculeMappingObject @@ -28,7 +29,8 @@ @"standardHeader": StandardHeaderView.class, @"moleculeStack": MoleculeStackView.class, @"twoButtonView": PrimaryButtonView.class, - @"standardFooter": StandardFooterView.class + @"standardFooter": StandardFooterView.class, + @"textField" : MFTextField.class, } mutableCopy]; }); return mapping; @@ -57,8 +59,9 @@ return nil; } UIView *molecule = [self getMoleculeForName:moleculeName]; - [molecule setWithJSON:json delegate:delegate additionalData:nil]; + [molecule setWithJSON:json delegate:delegate additionalData:nil]; return molecule; } + @end diff --git a/MVMCoreUI/Protocols/MVMCoreUIFormMoleculesProtocol.h b/MVMCoreUI/Protocols/MVMCoreUIFormMoleculesProtocol.h new file mode 100644 index 00000000..3c76f16f --- /dev/null +++ b/MVMCoreUI/Protocols/MVMCoreUIFormMoleculesProtocol.h @@ -0,0 +1,18 @@ +// +// MVMCoreUIFormMoleculesProtocol.h +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 3/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +#import + + +@protocol MVMCoreUIFormMoleculesProtocol +//- (BOOL)isValidField; +//- (void)enableField:(BOOL) enable; +// +//- (NSString *)formFieldName; +//- (NSString *)formFieldValue; +@end diff --git a/MVMCoreUI/Protocols/MVMCoreUIFormValidationProtocol.h b/MVMCoreUI/Protocols/MVMCoreUIFormValidationProtocol.h new file mode 100644 index 00000000..16eaeb82 --- /dev/null +++ b/MVMCoreUI/Protocols/MVMCoreUIFormValidationProtocol.h @@ -0,0 +1,27 @@ +// +// MVMCoreUIFormValidationProtocol.h +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 3/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +#import + +@class MVMCoreUIFormValidator; + +@protocol MVMCoreUIFormValidationProtocol + +// Returns the form validator model +- (nullable MVMCoreUIFormValidator *) formValidatorModel; + +@optional +- (void)setFormValidationModel:(nonnull MVMCoreUIFormValidator *) formValidatorModel; + +- (BOOL)isValidField; +- (void)enableField:(BOOL) enable; + +- (nullable NSString *)formFieldName; +- (nullable id)formFieldValue; + +@end diff --git a/MVMCoreUI/Templates/MoleculeStackCenteredTemplate.swift b/MVMCoreUI/Templates/MoleculeStackCenteredTemplate.swift index 7f7411a3..ab7b6dec 100644 --- a/MVMCoreUI/Templates/MoleculeStackCenteredTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeStackCenteredTemplate.swift @@ -17,14 +17,16 @@ public class MoleculeStackCenteredTemplate: ThreeLayerViewController { } public override func viewForTop() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { return nil } return molecule } override public func viewForBottom() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { return nil } return molecule