diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index c1f4a798..17fc19f7 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 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 */; }; D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */; }; + D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */; }; D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */; }; D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */; }; D282AACB2243C61700C46919 /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AACA2243C61700C46919 /* ButtonView.swift */; }; @@ -226,6 +227,7 @@ D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.swift; sourceTree = ""; }; + D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EyebrowHeadlineBodyLink.swift; sourceTree = ""; }; D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFLoadImageView.swift; sourceTree = ""; }; D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFTransparentGIFView.swift; sourceTree = ""; }; D282AACA2243C61700C46919 /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = ""; }; @@ -418,6 +420,7 @@ children = ( D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, + D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */, ); path = VerticalCombinationViews; sourceTree = ""; @@ -1067,6 +1070,7 @@ D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, + D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, diff --git a/MVMCoreUI/Atoms/Views/MultiProgress.swift b/MVMCoreUI/Atoms/Views/MultiProgress.swift index 619e31e7..fe902d9f 100644 --- a/MVMCoreUI/Atoms/Views/MultiProgress.swift +++ b/MVMCoreUI/Atoms/Views/MultiProgress.swift @@ -76,7 +76,7 @@ import UIKit override open func setupView() { super.setupView() translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .mfSilver() + backgroundColor = .mfLightSilver() clipsToBounds = true if thicknessConstraint == nil { thicknessConstraint = heightAnchor.constraint(equalToConstant: defaultHeight) @@ -84,6 +84,12 @@ import UIKit } } + open override func reset() { + super.reset() + backgroundColor = .mfLightSilver() + progressList = nil + } + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) thicknessConstraint?.constant = json?.optionalCGFloatForKey("thickness") ?? defaultHeight diff --git a/MVMCoreUI/Atoms/Views/ProgressBar.swift b/MVMCoreUI/Atoms/Views/ProgressBar.swift index 5416f0a7..a9333966 100644 --- a/MVMCoreUI/Atoms/Views/ProgressBar.swift +++ b/MVMCoreUI/Atoms/Views/ProgressBar.swift @@ -70,7 +70,7 @@ import Foundation thickness = 8 progress = 0 progressTintColor = UIColor.mfCerulean() - trackTintColor = UIColor.mfSilver() + trackTintColor = UIColor.mfLightSilver() } public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index a5e359d0..df443afd 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -34,6 +34,9 @@ @property (nonatomic) BOOL updateViewHorizontalDefaults; @property (nonatomic) BOOL updateViewVerticalDefaults; +@property (nonatomic) CGFloat topMarginPadding; +@property (nonatomic) CGFloat bottomMarginPadding; + /// A molecule if we constrain one. @property (weak, nullable, nonatomic) UIView *molecule; diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index ba5f1bd0..66a3a83d 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -304,18 +304,15 @@ [super setupView]; self.translatesAutoresizingMaskIntoConstraints = NO; self.backgroundColor = [UIColor clearColor]; + self.topMarginPadding = PaddingDefaultVerticalSpacing3; + self.bottomMarginPadding = PaddingDefaultVerticalSpacing3; [MVMCoreUIUtility setMarginsForView:self leading:0 top:0 trailing:0 bottom:0]; } - (void)updateView:(CGFloat)size { [super updateView:size]; - if ([self.constrainedView respondsToSelector:@selector(updateView:)]) { - [((id)self.constrainedView) updateView:size]; - } - if (self.molecule != self.constrainedView) { - [self.molecule updateView:size]; - } - [MFStyler setDefaultMarginsForView:self size:size horizontal:self.updateViewHorizontalDefaults vertical:self.updateViewVerticalDefaults]; + [self.molecule updateView:size]; + [MFStyler setMarginsForView:self size:size defaultHorizontal:self.updateViewHorizontalDefaults top:(self.updateViewVerticalDefaults ? self.topMarginPadding : 0) bottom:(self.updateViewVerticalDefaults ? self.bottomMarginPadding : 0)]; UIEdgeInsets margins = [MVMCoreUIUtility getMarginsForView:self]; if (self.updateViewHorizontalDefaults) { [self setLeftPinConstant:margins.left]; @@ -344,6 +341,8 @@ [super reset]; self.updateViewHorizontalDefaults = NO; self.updateViewVerticalDefaults = NO; + self.topMarginPadding = PaddingDefaultVerticalSpacing3; + self.bottomMarginPadding = PaddingDefaultVerticalSpacing3; if ([self.molecule respondsToSelector:@selector(alignment)]) { [self alignHorizontal:[(UIView *)self.molecule alignment]]; } @@ -354,8 +353,10 @@ } - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { - [super setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; - + // Only treated as a container if we are constraining a molecule. + if (!self.constrainedView) { + [super setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; + } [self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; if (self.shouldSetupMoleculeFromJSON) { NSDictionary *moleculeJSON = [json dict:KeyMolecule]; diff --git a/MVMCoreUI/Molecules/Scroller.swift b/MVMCoreUI/Molecules/Scroller.swift index cd2949d8..e4289bc3 100644 --- a/MVMCoreUI/Molecules/Scroller.swift +++ b/MVMCoreUI/Molecules/Scroller.swift @@ -19,7 +19,8 @@ import UIKit } translatesAutoresizingMaskIntoConstraints = false scrollView.translatesAutoresizingMaskIntoConstraints = false - addConstrainedView(scrollView) + addSubview(scrollView) + pinView(toSuperView: scrollView) scrollView.addSubview(contentView) NSLayoutConstraint.constraintPinSubview(toSuperview: contentView) let constraint = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0) diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/StandardFooterView.swift index f00af028..7734883e 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/StandardFooterView.swift @@ -11,6 +11,8 @@ import UIKit open class StandardFooterView: ViewConstrainingView { open override func setupView() { super.setupView() + topMarginPadding = PaddingDefaultVerticalSpacing + bottomMarginPadding = PaddingDefaultVerticalSpacing shouldSetupMoleculeFromJSON = true updateViewVerticalDefaults = true updateViewHorizontalDefaults = true diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/StandardHeaderView.swift index 093a2e31..12819313 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/StandardHeaderView.swift @@ -22,6 +22,8 @@ public class StandardHeaderView: ViewConstrainingView { shouldSetupMoleculeFromJSON = true updateViewVerticalDefaults = true updateViewHorizontalDefaults = true + topMarginPadding = PaddingDefaultVerticalSpacing + bottomMarginPadding = PaddingDefaultVerticalSpacing if separatorView == nil, let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) { separatorView.setAsHeavy() addSubview(separatorView) @@ -54,6 +56,8 @@ public class StandardHeaderView: ViewConstrainingView { open override func reset() { super.reset() + topMarginPadding = PaddingDefaultVerticalSpacing + bottomMarginPadding = PaddingDefaultVerticalSpacing separatorView?.setAsHeavy() separatorView?.show() } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift new file mode 100644 index 00000000..306cf690 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -0,0 +1,70 @@ +// +// EyebrowHeadlineBodyLink.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class EyebrowHeadlineBodyLink: ViewConstrainingView { + let stack = MoleculeStackView(frame: .zero) + let eyebrow = Label.commonLabelB3(true) + let headline = Label.commonLabelB1(true) + let body = Label.commonLabelB2(true) + let link = MFTextButton(nil, constrainHeight: false, forWidth: MVMCoreUIUtility.getWidth()) + + // MARK: - MFViewProtocol + open override func setupView() { + super.setupView() + guard stack.superview == nil else { + return + } + stack.spacing = 0 + stack.updateViewHorizontalDefaults = false + addSubview(stack) + pinView(toSuperView: stack) + stack.addStackItem(StackItem(with: eyebrow), lastItem: false) + stack.addStackItem(StackItem(with: headline), lastItem: false) + stack.addStackItem(StackItem(with: body), lastItem: false) + + // To visually take into account the extra padding in the intrinsic content of a button. + let stackItem = StackItem(with: link) + stackItem.spacing = -6 + stack.addStackItem(stackItem, lastItem: true) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + stack.updateView(size) + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + eyebrow.setWithJSON(json?.optionalDictionaryForKey("eyebrow"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[0].gone = !eyebrow.hasText + headline.setWithJSON(json?.optionalDictionaryForKey("headline"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[1].gone = !headline.hasText + body.setWithJSON(json?.optionalDictionaryForKey("body"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[2].gone = !body.hasText + link.setWithJSON(json?.optionalDictionaryForKey("link"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[3].gone = link.titleLabel?.text?.count ?? 0 == 0 + stack.restack() + } + + open override func reset() { + super.reset() + stack.reset() + stack.spacing = 0 + stack.updateViewHorizontalDefaults = false + eyebrow.styleB3(true) + headline.styleB1(true) + body.styleB2(true) + } + + public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 65 + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift index db30c8d6..e63eb2ba 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -32,6 +32,8 @@ open class HeadlineBody: ViewConstrainingView { stylePageHeader() case "item": styleListItem() + case "itemHeader": + styleListItemDivider() default: break } } @@ -53,6 +55,12 @@ open class HeadlineBody: ViewConstrainingView { messageLabel.styleB2(true) spaceBetweenLabelsConstant = 0 } + + func styleListItemDivider() { + headlineLabel.styleH3(true) + messageLabel.styleB2(true) + spaceBetweenLabelsConstant = 0 + } // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { @@ -80,10 +88,9 @@ open class HeadlineBody: ViewConstrainingView { headlineLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) messageLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + view.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - topPin = headlineLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 0) - topPin?.isActive = true + headlineLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: spaceBetweenLabelsConstant) spaceBetweenLabels?.isActive = true @@ -100,8 +107,7 @@ open class HeadlineBody: ViewConstrainingView { rightConstraintMessage = view.rightAnchor.constraint(equalTo: messageLabel.rightAnchor) rightConstraintMessage?.isActive = true - bottomPin = view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0) - bottomPin?.isActive = true + view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0).isActive = true } // MARK: - Constraining diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index b2f70e6d..14ad9e70 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -14,25 +14,27 @@ public class StackItem { var percentage: Int? var verticalAlignment: UIStackView.Alignment? var horizontalAlignment: UIStackView.Alignment? + var gone = false init(with view: UIView) { self.view = view } - init(with view: UIView, json: [AnyHashable: Any]) { + init(with view: UIView, json: [AnyHashable: Any]?) { self.view = view update(with: json) } - func update(with json: [AnyHashable: Any]) { - spacing = json.optionalCGFloatForKey("spacing") - percentage = json["percent"] as? Int - if let alignment = json.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"verticalAlignment"]) { + func update(with json: [AnyHashable: Any]?) { + gone = json?.boolForKey("gone") ?? (json == nil) + spacing = json?.optionalCGFloatForKey("spacing") + percentage = json?["percent"] as? Int + if let alignment = json?.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"verticalAlignment"]) { verticalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) } else { verticalAlignment = nil } - if let alignment = json.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"horizontalAlignment"]) { + if let alignment = json?.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"horizontalAlignment"]) { horizontalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) } else { horizontalAlignment = nil @@ -84,11 +86,13 @@ public class MoleculeStackView: ViewConstrainingView { /// Restacks the existing items. func restack() { - MVMCoreUIStackableViewController.remove(contentView.subviews) - let stackItems = items - items.removeAll() - for (index, item) in stackItems.enumerated() { - addStackItem(item, lastItem: index == stackItems.count - 1) + setWithStackItems(items) + } + + /// Removes all stack items views from the view. + func removeAllItemViews() { + for item in items { + item.view.removeFromSuperview() } } @@ -144,7 +148,7 @@ public class MoleculeStackView: ViewConstrainingView { open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { let previousJSON = self.json super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - MVMCoreUIStackableViewController.remove(contentView.subviews) + removeAllItemViews() // If the items in the stack are the same, just update previous items instead of re-allocating. var items: [StackItem]? @@ -238,6 +242,10 @@ public class MoleculeStackView: ViewConstrainingView { /// Adds the stack item to the stack. func addStackItem(_ stackItem: StackItem, lastItem: Bool) { + guard !stackItem.gone else { + items.append(stackItem) + return + } let view = stackItem.view contentView.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false @@ -249,10 +257,13 @@ public class MoleculeStackView: ViewConstrainingView { view.alignHorizontal?(horizontalAlignment) view.alignVertical?(verticalAlignment) } + let first = items.first { !$0.gone } == nil if axis == .vertical { - if items.count == 0 { + if first { pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0) - } else if let previousView = items.last?.view { + } else if let previousView = items.last(where: { stackItem in + return !stackItem.gone + })?.view { _ = NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: true) } pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) @@ -264,10 +275,12 @@ public class MoleculeStackView: ViewConstrainingView { pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) } } else { - if items.count == 0 { + if first { // First horizontal item has no spacing by default unless told otherwise. pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0) - } else if let previousView = items.last?.view { + } else if let previousView = items.last(where: { stackItem in + return !stackItem.gone + })?.view { _ = NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: false) } pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) @@ -281,4 +294,20 @@ public class MoleculeStackView: ViewConstrainingView { } items.append(stackItem) } + + func setWithStackItems(_ items: [StackItem]) { + removeAllItemViews() + self.items.removeAll() + var previousPresentItem: StackItem? = nil + for item in items { + if !item.gone { + previousPresentItem = item + } + addStackItem(item, lastItem: false) + } + if let lastView = previousPresentItem?.view { + let attribute: NSLayoutConstraint.Attribute = axis == .vertical ? .bottom : .right + pinView(contentView, toView: lastView, attribute: attribute, relation: .equal, priority: .required, constant: 0) + } + } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 86267332..945eb7bf 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -60,7 +60,8 @@ @"headlineBodySwitch": HeadlineBodySwitch.class, @"headlineBodyTextButton": HeadlineBodyTextButton.class, @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class, - @"tabsListItem": TabsTableViewCell.class + @"tabsListItem": TabsTableViewCell.class, + @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class } mutableCopy]; }); return mapping;