diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 401a5589..2df3793d 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 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, ); }; }; + D296E15A22A7FBCC0051EBE7 /* HorizontalStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D296E15922A7FBCC0051EBE7 /* HorizontalStackView.swift */; }; 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, ); }; }; D29770F221F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770EE21F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m */; }; @@ -204,6 +205,7 @@ D296E1402295EBBA0051EBE7 /* MoleculeDelegateProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoleculeDelegateProtocol.h; sourceTree = ""; }; D296E142229729C30051EBE7 /* MoleculeMappingObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MoleculeMappingObject+Extension.swift"; sourceTree = ""; }; D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewConstrainingProtocol.h; sourceTree = ""; }; + D296E15922A7FBCC0051EBE7 /* HorizontalStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HorizontalStackView.swift; path = /Users/scott/Documents/Source/mvmrc_ios/mvm_core_ui/MVMCoreUI/Molecules/HorizontalStackView.swift; sourceTree = ""; }; D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopLabelsView.m; sourceTree = ""; }; D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TopLabelsView.h; sourceTree = ""; }; D29770EE21F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopLabelsAndBottomButtonsTableViewController.m; sourceTree = ""; }; @@ -463,6 +465,7 @@ D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */, D274CA322236A78900B01B62 /* StandardFooterView.swift */, D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */, + D296E15922A7FBCC0051EBE7 /* HorizontalStackView.swift */, ); path = Molecules; sourceTree = ""; @@ -913,6 +916,7 @@ D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, D29DF24D21E6A177003B2FB9 /* MFTextField.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, + D296E15A22A7FBCC0051EBE7 /* HorizontalStackView.swift in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index f2792081..5e3b1b5b 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -309,6 +309,9 @@ import MVMCore public func setAsMolecule() { setContentHuggingPriority(.required, for: .vertical) + setContentHuggingPriority(.required, for: .horizontal) + //setContentCompressionResistancePriority(.required, for: .horizontal) + //setContentCompressionResistancePriority(.required, for: .vertical) } public func needsToBeConstrained() -> Bool { diff --git a/MVMCoreUI/Atoms/Views/LabelView.h b/MVMCoreUI/Atoms/Views/LabelView.h index a6b1948c..8a0eb310 100644 --- a/MVMCoreUI/Atoms/Views/LabelView.h +++ b/MVMCoreUI/Atoms/Views/LabelView.h @@ -15,9 +15,4 @@ // Customize the label. @property (nullable, weak, nonatomic) Label *label; -// Change the alignment of the label -- (void)alignLeft; -- (void)alignCenter; -- (void)alignRight; - @end diff --git a/MVMCoreUI/Atoms/Views/LabelView.m b/MVMCoreUI/Atoms/Views/LabelView.m index e0817374..d13ff39a 100644 --- a/MVMCoreUI/Atoms/Views/LabelView.m +++ b/MVMCoreUI/Atoms/Views/LabelView.m @@ -11,11 +11,6 @@ #import @interface LabelView () -// Change to customize. -@property (strong, nonatomic) NSLayoutConstraint *alignCenterPin; -@property (strong, nonatomic) NSLayoutConstraint *alignCenterLeftPin; -@property (strong, nonatomic) NSLayoutConstraint *alignCenterRightPin; - // Sets up the view. - (void)setupView; @@ -36,39 +31,7 @@ Label *label = [Label commonLabelB2:YES]; [self addSubview:label]; self.label = label; - - // Align left and right constants. - NSLayoutConstraint *leftPin = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]; - self.leftPin = leftPin; - leftPin.active = YES; - - NSLayoutConstraint *topPin = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; - topPin.priority = 999; - self.topPin = topPin; - topPin.active = YES; - - NSLayoutConstraint *bottomPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:label attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]; - self.bottomPin = bottomPin; - bottomPin.active = YES; - - NSLayoutConstraint *rightPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:label attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; - rightPin.priority = 999; - self.rightPin = rightPin; - rightPin.active = YES; - - // Center alignments - NSLayoutConstraint *alignCenter = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]; - self.alignCenterPin = alignCenter; - alignCenter.active = YES; - - NSLayoutConstraint *centerLeftPin = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]; - self.alignCenterLeftPin = centerLeftPin; - centerLeftPin.active = YES; - - NSLayoutConstraint *centerRightPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:label attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; - self.alignCenterRightPin = centerRightPin; - centerRightPin.active = YES; - + [self pinViewToSuperView:label]; [self alignLeft]; } } @@ -79,42 +42,20 @@ } - (void)alignLeft { - self.alignCenterPin.active = NO; - self.alignCenterLeftPin.active = NO; - self.alignCenterRightPin.active = NO; - self.leftPin.active = YES; - self.rightPin.active = YES; + [super alignLeft]; self.label.textAlignment = NSTextAlignmentLeft; } -- (void)alignCenter { - self.alignCenterPin.active = YES; - self.alignCenterLeftPin.active = YES; - self.alignCenterRightPin.active = YES; - self.leftPin.active = NO; - self.rightPin.active = NO; +- (void)alignCenterHorizontal { + [super alignCenterHorizontal]; self.label.textAlignment = NSTextAlignmentCenter; } - (void)alignRight { - self.alignCenterPin.active = NO; - self.alignCenterLeftPin.active = NO; - self.alignCenterRightPin.active = NO; - self.leftPin.active = YES; - self.rightPin.active = YES; + [super alignRight]; self.label.textAlignment = NSTextAlignmentRight; } -- (void)setLeftPinConstant:(CGFloat)constant { - [super setLeftPinConstant:constant]; - self.alignCenterLeftPin.constant = constant; -} - -- (void)setRightPinConstant:(CGFloat)constant { - [super setRightPinConstant:constant]; - self.alignCenterRightPin.constant = constant; -} - - (void)resetConstraints { [super resetConstraints]; [self alignLeft]; diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h b/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h index 757b937e..a70804b6 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h @@ -15,9 +15,4 @@ + (nullable instancetype)createWithDelegate:(nullable id )delegate; -// Change the alignment of the label -- (void)alignLeft; -- (void)alignCenter; -- (void)alignRight; - @end diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m b/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m index 4e3b2f72..f43b6356 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m @@ -10,9 +10,6 @@ #import "NSLayoutConstraint+MFConvenience.h" @interface MVMCoreUITextFieldView () -@property (weak, nonatomic) NSLayoutConstraint *alignCenterPin; -@property (weak, nonatomic) NSLayoutConstraint *alignCenterLeftPin; -@property (weak, nonatomic) NSLayoutConstraint *alignCenterRightPin; @end @implementation MVMCoreUITextFieldView @@ -31,74 +28,11 @@ mfTextField.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:mfTextField]; self.mvmTextField = mfTextField; - - NSLayoutConstraint *leftPin = [NSLayoutConstraint constraintWithItem:mfTextField attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]; - self.leftPin = leftPin; - leftPin.active = YES; - - NSLayoutConstraint *topPin = [NSLayoutConstraint constraintWithItem:mfTextField attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; - self.topPin = topPin; - topPin.active = YES; - - NSLayoutConstraint *bottomPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:mfTextField attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]; - self.bottomPin = bottomPin; - bottomPin.active = YES; - - NSLayoutConstraint *rightPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:mfTextField attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; - self.rightPin = rightPin; - rightPin.active = YES; - - // Center alignments - NSLayoutConstraint *alignCenter = [NSLayoutConstraint constraintWithItem:mfTextField attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]; - self.alignCenterPin = alignCenter; - alignCenter.active = YES; - - NSLayoutConstraint *centerLeftPin = [NSLayoutConstraint constraintWithItem:mfTextField attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]; - self.alignCenterLeftPin = centerLeftPin; - centerLeftPin.active = YES; - - NSLayoutConstraint *centerRightPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:mfTextField attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; - self.alignCenterRightPin = centerRightPin; - centerRightPin.active = YES; - + [self pinViewToSuperView:mfTextField]; [self alignLeft]; } } -- (void)alignLeft { - self.alignCenterPin.active = NO; - self.alignCenterLeftPin.active = NO; - self.alignCenterRightPin.active = NO; - self.leftPin.active = YES; - self.rightPin.active = YES; -} - -- (void)alignCenter { - self.alignCenterPin.active = YES; - self.alignCenterLeftPin.active = YES; - self.alignCenterRightPin.active = YES; - self.leftPin.active = NO; - self.rightPin.active = NO; -} - -- (void)alignRight { - self.alignCenterPin.active = NO; - self.alignCenterLeftPin.active = NO; - self.alignCenterRightPin.active = NO; - self.leftPin.active = YES; - self.rightPin.active = YES; -} - -- (void)setLeftPinConstant:(CGFloat)constant { - self.leftPin.constant = constant; - self.alignCenterLeftPin.constant = constant; -} - -- (void)setRightPinConstant:(CGFloat)constant { - self.rightPin.constant = constant; - self.alignCenterRightPin.constant = constant; -} - - (void)resetConstraints { [super resetConstraints]; [self alignLeft]; diff --git a/MVMCoreUI/Atoms/Views/TextButtonView.h b/MVMCoreUI/Atoms/Views/TextButtonView.h index 0d4a6995..49d0c9a0 100644 --- a/MVMCoreUI/Atoms/Views/TextButtonView.h +++ b/MVMCoreUI/Atoms/Views/TextButtonView.h @@ -20,11 +20,6 @@ // inits with two buttons. - (nullable instancetype)initWithTwoButtons; -// Change the alignment of the button. Default is left. -- (void)alignLeft; -- (void)alignCenter; -- (void)alignRight; - // To add dotted underline below text button, in case of one button - (void)addDotLineBelowButton; diff --git a/MVMCoreUI/Atoms/Views/TextButtonView.m b/MVMCoreUI/Atoms/Views/TextButtonView.m index 7c9f5e23..9e6435e0 100644 --- a/MVMCoreUI/Atoms/Views/TextButtonView.m +++ b/MVMCoreUI/Atoms/Views/TextButtonView.m @@ -14,10 +14,6 @@ #import @interface TextButtonView () - -@property (weak, nonatomic) NSLayoutConstraint *alignCenterPin; -@property (weak, nonatomic) NSLayoutConstraint *alignCenterLeftPin; -@property (weak, nonatomic) NSLayoutConstraint *alignCenterRightPin; @property (strong, nonatomic) UIView *dotLine; // Sets up the view. @@ -78,37 +74,7 @@ button.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:button]; self.textButton = button; - - // Align left and right constants. - NSLayoutConstraint *leftPin = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]; - self.leftPin = leftPin; - leftPin.active = YES; - - NSLayoutConstraint *topPin = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; - self.topPin = topPin; - topPin.active = YES; - - NSLayoutConstraint *bottomPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]; - self.bottomPin = bottomPin; - bottomPin.active = YES; - - NSLayoutConstraint *rightPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; - self.rightPin = rightPin; - rightPin.active = YES; - - // Center alignments - NSLayoutConstraint *alignCenter = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]; - self.alignCenterPin = alignCenter; - alignCenter.active = YES; - - NSLayoutConstraint *centerLeftPin = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]; - self.alignCenterLeftPin = centerLeftPin; - centerLeftPin.active = YES; - - NSLayoutConstraint *centerRightPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:button attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; - self.alignCenterRightPin = centerRightPin; - centerRightPin.active = YES; - + [self pinViewToSuperView:button]; [self alignLeft]; } } @@ -154,40 +120,6 @@ return self; } -- (void)alignLeft { - self.alignCenterPin.active = NO; - self.alignCenterLeftPin.active = NO; - self.alignCenterRightPin.active = YES; - self.leftPin.active = YES; - self.rightPin.active = NO; -} - -- (void)alignCenter { - self.alignCenterPin.active = YES; - self.alignCenterLeftPin.active = YES; - self.alignCenterRightPin.active = YES; - self.leftPin.active = NO; - self.rightPin.active = NO; -} - -- (void)alignRight { - self.alignCenterPin.active = NO; - self.alignCenterLeftPin.active = YES; - self.alignCenterRightPin.active = NO; - self.leftPin.active = NO; - self.rightPin.active = YES; -} - -- (void)setLeftPinConstant:(CGFloat)constant { - [super setLeftPinConstant:constant]; - self.alignCenterLeftPin.constant = constant; -} - -- (void)setRightPinConstant:(CGFloat)constant { - [super setRightPinConstant:constant]; - self.alignCenterRightPin.constant = constant; -} - - (void)resetConstraints { [super resetConstraints]; [self alignLeft]; diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index 6b97f891..cba1915e 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -17,9 +17,18 @@ @property (nullable, strong, nonatomic) NSLayoutConstraint *topPin; @property (nullable, strong, nonatomic) NSLayoutConstraint *bottomPin; +@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterPin; +@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterLeftPin; +@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterRightPin; + +@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterVerticalPin; +@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterTopPin; +@property (nullable, strong, nonatomic) NSLayoutConstraint *alignCenterBottomPin; + // In updateView, will set horizontal padding to default if set to YES. @property (nonatomic) BOOL updateViewHorizontalDefaults; + // Returns an empty view + (nonnull ViewConstrainingView *)emptyView; @@ -43,6 +52,7 @@ // Pins all edges to its super. 0 constant - (void)pinToSuperView; +- (void)pinViewToSuperView:(nonnull UIView *)view; // Resets all the constraints to default. - (void)resetConstraints; @@ -53,4 +63,17 @@ // Add a view to be constrained in this view. - (void)addConstrainedView:(nonnull UIView *)view; +// Change the alignment of the label +- (void)alignLeft; +- (void)alignCenterHorizontal; +- (void)alignRight; +- (void)alignFillHorizontal; +- (void)alignTop; +- (void)alignCenterVertical; +- (void)alignBottom; +- (void)alignFillVertical; + +/// Convenience function for getting the alignment from a map. ++ (UIStackViewAlignment)getAlignmentForString:(nullable NSString *)alignmentString defaultAlignment:(UIStackViewAlignment)defaultAlignment; + @end diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index 2830f19b..6b8eac4d 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -45,12 +45,26 @@ return constrainingView; } -- (void)pinToSuperView { - NSDictionary *dictionary = [NSLayoutConstraint constraintPinSubviewToSuperview:self]; +#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]; + + self.alignCenterPin = [view.centerXAnchor constraintEqualToAnchor:view.superview.centerXAnchor]; + self.alignCenterLeftPin = [view.leftAnchor constraintGreaterThanOrEqualToAnchor:view.superview.leftAnchor]; + self.alignCenterRightPin = [view.superview.rightAnchor constraintGreaterThanOrEqualToAnchor:view.rightAnchor]; + + self.alignCenterVerticalPin = [view.centerYAnchor constraintEqualToAnchor:view.superview.centerYAnchor]; + self.alignCenterTopPin = [view.topAnchor constraintGreaterThanOrEqualToAnchor:view.superview.topAnchor]; + self.alignCenterBottomPin = [view.superview.bottomAnchor constraintGreaterThanOrEqualToAnchor:view.bottomAnchor]; +} + +- (void)pinToSuperView { + [self pinViewToSuperView:self]; } - (void)setPinConstantsWithInsets:(UIEdgeInsets)insets { @@ -66,18 +80,22 @@ - (void)setLeftPinConstant:(CGFloat)constant { self.leftPin.constant = constant; + self.alignCenterLeftPin.constant = constant; } - (void)setRightPinConstant:(CGFloat)constant { self.rightPin.constant = constant; + self.alignCenterRightPin.constant = constant; } - (void)setTopPinConstant:(CGFloat)constant { self.topPin.constant = constant; + self.alignCenterTopPin.constant = constant; } - (void)setBottomPinConstant:(CGFloat)constant { self.bottomPin.constant = constant; + self.alignCenterBottomPin.constant = constant; } - (void)show { @@ -100,30 +118,138 @@ view.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:view]; self.constrainedView = view; - - NSLayoutRelation leftRelation; - if (alignment == UIStackViewAlignmentFill || alignment == UIStackViewAlignmentLeading || alignment == UIStackViewAlignmentFirstBaseline) { - leftRelation = NSLayoutRelationEqual; - } else { - leftRelation = NSLayoutRelationGreaterThanOrEqual; - } - NSLayoutRelation rightRelation; - if (alignment == UIStackViewAlignmentFill || alignment == UIStackViewAlignmentTrailing || alignment == UIStackViewAlignmentLastBaseline) { - rightRelation = NSLayoutRelationEqual; - } else { - rightRelation = NSLayoutRelationGreaterThanOrEqual; - } - NSDictionary *dictionary = [NSLayoutConstraint constraintPinSubview:view topRelation:NSLayoutRelationEqual bottomRelation:NSLayoutRelationEqual leftRelation:leftRelation rightRelation:rightRelation]; - self.leftPin = dictionary[ConstraintLeading]; - self.topPin = dictionary[ConstraintTop]; - self.bottomPin = dictionary[ConstraintBot]; - self.rightPin = dictionary[ConstraintTrailing]; + [self pinViewToSuperView:view]; + [self alignHorizontal:alignment]; } - (void)addConstrainedView:(nonnull UIView *)view { [self addConstrainedView:view alignment:UIStackViewAlignmentFill]; } +- (void)alignLeft { + self.alignCenterPin.active = NO; + self.alignCenterLeftPin.active = NO; + self.alignCenterRightPin.active = YES; + self.leftPin.active = YES; + self.rightPin.active = NO; +} + +- (void)alignCenterHorizontal { + self.alignCenterPin.active = YES; + self.alignCenterLeftPin.active = YES; + self.alignCenterRightPin.active = YES; + self.leftPin.active = NO; + self.rightPin.active = NO; +} + +- (void)alignRight { + self.alignCenterPin.active = NO; + self.alignCenterLeftPin.active = YES; + self.alignCenterRightPin.active = NO; + self.leftPin.active = NO; + self.rightPin.active = YES; +} + +- (void)alignFillHorizontal { + self.alignCenterPin.active = NO; + self.alignCenterLeftPin.active = NO; + self.alignCenterRightPin.active = NO; + self.leftPin.active = YES; + self.rightPin.active = YES; +} + +- (void)alignTop { + self.alignCenterVerticalPin.active = NO; + self.alignCenterTopPin.active = NO; + self.alignCenterBottomPin.active = YES; + self.topPin.active = YES; + self.bottomPin.active = NO; +} + +- (void)alignCenterVertical { + self.alignCenterVerticalPin.active = YES; + self.alignCenterTopPin.active = YES; + self.alignCenterBottomPin.active = YES; + self.topPin.active = NO; + self.bottomPin.active = NO; +} + +- (void)alignBottom { + self.alignCenterVerticalPin.active = NO; + self.alignCenterTopPin.active = YES; + self.alignCenterBottomPin.active = NO; + self.topPin.active = NO; + self.bottomPin.active = YES; +} + +- (void)alignFillVertical { + self.alignCenterVerticalPin.active = NO; + self.alignCenterTopPin.active = NO; + self.alignCenterBottomPin.active = NO; + self.topPin.active = YES; + self.bottomPin.active = YES; +} + ++ (UIStackViewAlignment)getAlignmentForString:(nullable NSString *)alignmentString defaultAlignment:(UIStackViewAlignment)defaultAlignment { + if ([alignmentString isEqualToString:@"leading"]) { + return UIStackViewAlignmentLeading; + } else if ([alignmentString isEqualToString:@"trailing"]) { + return UIStackViewAlignmentTrailing; + } else if ([alignmentString isEqualToString:@"center"]) { + return UIStackViewAlignmentCenter; + } else if ([alignmentString isEqualToString:@"fill"]) { + return UIStackViewAlignmentFill; + } else { + return defaultAlignment; + } +} + +#pragma mark - MVMCoreUIViewConstrainingProtocol + +- (void)alignHorizontal:(UIStackViewAlignment)alignment { + switch (alignment) { + case UIStackViewAlignmentCenter: + [self alignCenterHorizontal]; + break; + case UIStackViewAlignmentLeading: + [self alignLeft]; + break; + case UIStackViewAlignmentTrailing: + [self alignRight]; + break; + case UIStackViewAlignmentFill: + [self alignFillHorizontal]; + break; + default: + break; + } +} + +- (void)alignVertical:(UIStackViewAlignment)alignment { + switch (alignment) { + case UIStackViewAlignmentCenter: + [self alignCenterVertical]; + break; + case UIStackViewAlignmentLeading: + [self alignTop]; + break; + case UIStackViewAlignmentTrailing: + [self alignBottom]; + break; + case UIStackViewAlignmentFill: + [self alignFillVertical]; + break; + default: + break; + } +} + +- (void)shouldSetHorizontalMargins:(BOOL)shouldSet { + self.updateViewHorizontalDefaults = shouldSet; +} + +#pragma mark - MVMCoreViewProtocol + - (void)setupView { [super setupView]; self.translatesAutoresizingMaskIntoConstraints = NO; @@ -145,14 +271,14 @@ } } +#pragma mark - MVMCoreUIMoleculeViewProtocol + - (void)reset { if ([self.constrainedView respondsToSelector:@selector(reset)]) { [self.constrainedView performSelector:@selector(reset)]; } } -#pragma mark - MVMCoreUIMoleculeViewProtocol - - (void)setAsMolecule { self.updateViewHorizontalDefaults = YES; } diff --git a/MVMCoreUI/Molecules/ButtonView.swift b/MVMCoreUI/Molecules/ButtonView.swift index 3e863a8e..4596164c 100644 --- a/MVMCoreUI/Molecules/ButtonView.swift +++ b/MVMCoreUI/Molecules/ButtonView.swift @@ -10,9 +10,6 @@ import UIKit @objcMembers open class ButtonView: ViewConstrainingView { open var primaryButton: PrimaryButton? = PrimaryButton.button() - open var alignCenterPin: NSLayoutConstraint? - open var alignCenterLeftPin: NSLayoutConstraint? - open var alignCenterRightPin: NSLayoutConstraint? // MARK: - Inits public init() { @@ -53,7 +50,7 @@ import UIKit open override func setupView() { super.setupView() setupButton() - alignCenter() + alignCenterHorizontal() } // MARK: - MVMCoreUIMoleculeViewProtocol @@ -75,56 +72,9 @@ import UIKit func setupButton() { if let primaryButton = primaryButton, !subviews.contains(primaryButton) { addSubview(primaryButton) - setupConstraints(forView: primaryButton) + pinView(toSuperView: primaryButton) } } - - func setupConstraints(forView view: UIView) { - leftPin = view.leftAnchor.constraint(equalTo: leftAnchor) - topPin = view.topAnchor.constraint(equalTo: topAnchor) - rightPin = rightAnchor.constraint(equalTo: view.rightAnchor) - bottomPin = bottomAnchor.constraint(equalTo: view.bottomAnchor) - leftPin?.isActive = true - topPin?.isActive = true - rightPin?.isActive = true - bottomPin?.isActive = true - - alignCenterPin = view.centerXAnchor.constraint(equalTo: centerXAnchor) - alignCenterLeftPin = view.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor) - alignCenterRightPin = rightAnchor.constraint(greaterThanOrEqualTo: view.rightAnchor) - } - - open func alignLeft() { - alignCenterPin?.isActive = false - alignCenterLeftPin?.isActive = false - alignCenterRightPin?.isActive = true - leftPin?.isActive = true - rightPin?.isActive = false - } - - open func alignCenter() { - alignCenterPin?.isActive = true - alignCenterLeftPin?.isActive = true - alignCenterRightPin?.isActive = true - leftPin?.isActive = false - rightPin?.isActive = false - } - - open func alignRight() { - alignCenterPin?.isActive = false - alignCenterLeftPin?.isActive = true - alignCenterRightPin?.isActive = false - leftPin?.isActive = false - rightPin?.isActive = true - } - - open func alignFill() { - alignCenterPin?.isActive = false - alignCenterLeftPin?.isActive = false - alignCenterRightPin?.isActive = false - leftPin?.isActive = true - rightPin?.isActive = true - } open override func setLeftPinConstant(_ constant: CGFloat) { super.setLeftPinConstant(constant) diff --git a/MVMCoreUI/Molecules/HorizontalStackView.swift b/MVMCoreUI/Molecules/HorizontalStackView.swift new file mode 100644 index 00000000..ffa7fea7 --- /dev/null +++ b/MVMCoreUI/Molecules/HorizontalStackView.swift @@ -0,0 +1,75 @@ +// +// HorizontalStackView.swift +// +// +// Created by Scott Pfeil on 6/5/19. +// + +import UIKit + +@objcMembers public class HorizontalStackView: ViewConstrainingView { + let stackView = UIStackView(frame: .zero) + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + for view in stackView.arrangedSubviews { + stackView.removeArrangedSubview(view) + if let molecule = view as? MVMCoreViewProtocol { + molecule.updateView(size) + } + stackView.addArrangedSubview(view) + } + } + + override public func setupView() { + super.setupView() + guard stackView.superview == nil else { + return + } + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.distribution = .fill + addConstrainedView(stackView) + } + + public func setAxisWithJSON(_ json: [AnyHashable: Any]?) { + switch json?.optionalStringForKey("axis") { + case "horizontal": + stackView.axis = .horizontal + default: + stackView.axis = .vertical + } + } + + override public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + MVMCoreUIStackableViewController.remove(stackView.subviews) + + // Set the horizontal alignment for the stack in the containing view. Typically fill, but occasionally others for horizontal stacks. + //align(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: UIStackView.Alignment.fill)) + + // Sets the stack attributes + setAxisWithJSON(json) + stackView.spacing = json?.optionalCGFloatForKey("spacing") ?? 16 + + // The alignment of the items perpendicular to the axis. + stackView.alignment = ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("itemAlignment"), defaultAlignment: UIStackView.Alignment.fill) + + guard let molecules = json?.optionalArrayForKey("molecules") else { + return + } + for case let map as [AnyHashable: Any] in molecules { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: map, delegateObject: delegateObject, constrainIfNeeded: true) { + if let moleculeCast = molecule as? MVMCoreUIViewConstrainingProtocol { + // Set the horizontal alignement of the molecule. + //moleculeCast.align!(ViewConstrainingView.getAlignmentFor(map.optionalStringForKey("alignment"), defaultAlignment: .fill)) + + // Currently no vertical margins. + moleculeCast.shouldSetVerticalMargins?(false) + moleculeCast.shouldSetHorizontalMargins?(stackView.axis == .vertical) + } + + stackView.addArrangedSubview(molecule) + } + } + } +} diff --git a/MVMCoreUI/Molecules/MoleculeStackView.swift b/MVMCoreUI/Molecules/MoleculeStackView.swift index 5f7f8957..b21e2245 100644 --- a/MVMCoreUI/Molecules/MoleculeStackView.swift +++ b/MVMCoreUI/Molecules/MoleculeStackView.swift @@ -8,10 +8,72 @@ import UIKit -public class MoleculeStackView: MFView { +public class MoleculeStackView: ViewConstrainingView { var spacingBlock: ((Any) -> UIEdgeInsets)? var moleculesArray: [UIView]? var useMargins: Bool = false + var alignment: UIStackView.Alignment = .fill + var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() + + private var spacingBetweenItems: [NSLayoutConstraint]? + + /// For setting the direction of the stack + var axis: NSLayoutConstraint.Axis = .vertical { + didSet { + if axis != oldValue { + MVMCoreDispatchUtility.performBlock(onMainThread: { + // remove constraints + if self.axis == .vertical { + // layout vertical + } else { + // layout horizontal + } + }) + } + } + } + + /// For setting the alignment perpendicular to the direction of the stack. Default fill for vertical and center for horizontal. Can be overriden at the item level. + var itemAlignment: UIStackView.Alignment = .fill { + didSet { + if itemAlignment != oldValue { + MVMCoreDispatchUtility.performBlock(onMainThread: { + // remove constraints + if self.axis == .vertical { + // layout vertical + } else { + // layout horizontal + } + }) + } + } + } + + var spacing: Float = 16 { + didSet { + if spacing != oldValue { + MVMCoreDispatchUtility.performBlock(onMainThread: { + // loop space bettwen constraints and update. skip custom ones... + }) + } + } + } + + public func setAxisWithJSON(_ json: [AnyHashable: Any]?) { + switch json?.optionalStringForKey("axis") { + case "horizontal": + axis = .horizontal + default: + axis = .vertical + } + } + + + public func pinView(_ view: UIView, toView: UIView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, priority: UILayoutPriority, constant: CGFloat) { + let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant) + constraint.priority = priority + constraint.isActive = true + } // MARK: - Inits public override init(frame: CGRect) { @@ -35,10 +97,14 @@ public class MoleculeStackView: MFView { // MARK: - MFViewProtocol public override func setupView() { super.setupView() + guard contentView.superview == nil else { + return + } translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear - self.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - self.setContentCompressionResistancePriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + addConstrainedView(contentView) + //self.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + //self.setContentCompressionResistancePriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) } public override func updateView(_ size: CGFloat) { @@ -53,29 +119,71 @@ public class MoleculeStackView: MFView { // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + MVMCoreUIStackableViewController.remove(contentView.subviews) + guard let molecules = json?.arrayForKey(KeyMolecules) as? [[String: Any]] else { return } - // Create the molecules and set the json. - var moleculesArray = [] as [UIView] - for moleculeJSON in molecules { - if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { - moleculesArray.append(molecule) - } - } + // Sets the stack attributes + setAxisWithJSON(json) + spacing = json?["spacing"] as? Float ?? 16 - guard moleculesArray.count > 0 else { - return - } - - if let spacingBlock = spacingBlock { - MVMCoreUIStackableViewController.populateView(self, withUIArray: moleculesArray, useMargins: useMargins, withSpacingBlock: spacingBlock) + // 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 { - let separation = json?.optionalCGFloatForKey("separation") ?? PaddingDefault - MVMCoreUIStackableViewController.populateView(self, withUIArray: moleculesArray, useMargins: useMargins) { (object) -> UIEdgeInsets in - return UIEdgeInsets.init(top: separation, left: 0, bottom: 0, right: 0) + alignHorizontal(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: .fill)) + alignVertical(.leading) + } + + // Create the molecules and set the json. + var previousObject = contentView + for (index, map) in molecules.enumerated() { + if let moleculeJSON = map.optionalDictionaryForKey(KeyMolecule), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { + contentView.addSubview(molecule) + molecule.translatesAutoresizingMaskIntoConstraints = false + + let spacing = CGFloat(map["spacing"] as? Float ?? self.spacing) + let percent = map["percent"] as? Int + let verticalAlignment = ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("verticalAlignment"), defaultAlignment: (percent == nil && axis == .vertical ? UIStackView.Alignment.fill : UIStackView.Alignment.leading)) + let horizontalAlignment = ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("horizontalAlignment"), defaultAlignment: (axis == .vertical || percent == nil ? UIStackView.Alignment.fill : UIStackView.Alignment.leading)) + + if let molecule = molecule as? MVMCoreUIViewConstrainingProtocol { + molecule.alignHorizontal?(horizontalAlignment) + molecule.alignVertical?(verticalAlignment) + } + if axis == .vertical { + if index == 0 { + pinView(molecule, toView: previousObject, attribute: .top, relation: .equal, priority: .required, constant: spacing) + } else { + NSLayoutConstraint(pinFirstView: previousObject, toSecondView: molecule, withConstant: spacing, directionVertical: true)?.isActive = true + } + pinView(molecule, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) + pinView(contentView, toView: molecule, attribute: .trailing, relation: .equal, priority: .required, constant: 0) + if let percent = percent { + molecule.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0).isActive = true + } + } else { + if index == 0 { + pinView(molecule, toView: previousObject, attribute: .leading, relation: .equal, priority: .required, constant: spacing) + } else { + NSLayoutConstraint(pinFirstView: previousObject, toSecondView: molecule, withConstant: spacing, directionVertical: false)?.isActive = true + } + pinView(molecule, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) + pinView(contentView, toView: molecule, attribute: .bottom, relation: .equal, priority: .required, constant: 0) + if let percent = percent { + molecule.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0).isActive = true + } + } + previousObject = molecule } } + if axis == .vertical { + pinView(contentView, toView: previousObject, attribute: .bottom, relation: .equal, priority: .required, constant: 0) + } else { + pinView(contentView, toView: previousObject, attribute: .right, relation: .equal, priority: .required, constant: 0) + } } } diff --git a/MVMCoreUI/Molecules/TwoButtonView.swift b/MVMCoreUI/Molecules/TwoButtonView.swift index 48becca2..c5085cce 100644 --- a/MVMCoreUI/Molecules/TwoButtonView.swift +++ b/MVMCoreUI/Molecules/TwoButtonView.swift @@ -103,8 +103,8 @@ import UIKit addSubview(viewForButtons) self.viewForButtons = viewForButtons - setupConstraints(forView: viewForButtons) - alignCenter() + pinView(toSuperView: viewForButtons) + alignCenterHorizontal() createPrimaryButton() createSecondaryButton() @@ -118,8 +118,8 @@ import UIKit createPrimaryButton() if let primaryButton = primaryButton { addSubview(primaryButton) - setupConstraints(forView: primaryButton) - alignCenter() + pinView(toSuperView: primaryButton) + alignCenterHorizontal() } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 0004027a..c70be6a5 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -38,7 +38,8 @@ @"checkbox": MVMCoreUICheckBox.class, @"listItem": MoleculeTableViewCell.class, @"switchLineItem": SwitchLineItem.class, - @"switch": Switch.class + @"switch": Switch.class, + @"testStack": HorizontalStackView.class } mutableCopy]; }); return mapping; diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h b/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h index 8bdc3eb1..42114416 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h @@ -21,4 +21,14 @@ /// Can be used to override any standard constraints that may be added. - (BOOL)useStandardConstraints; +/// Will align if it can. +- (void)alignHorizontal:(UIStackViewAlignment)alignment; +- (void)alignVertical:(UIStackViewAlignment)alignment; + +/// Containing Views can tell the contained if they should use horizontal margins. +- (void)shouldSetHorizontalMargins:(BOOL)shouldSet; + +/// Containing Views can tell the contained if they should use vertical margins. +- (void)shouldSetVerticalMargins:(BOOL)shouldSet; + @end