diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index e600e30f..4c3b5900 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; + 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; }; D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -43,6 +44,8 @@ D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; + 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 */; }; @@ -206,6 +209,7 @@ 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 = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; + 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = ""; }; @@ -228,6 +232,8 @@ D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; 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 = ""; }; @@ -430,6 +436,7 @@ children = ( D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, + D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */, ); path = VerticalCombinationViews; sourceTree = ""; @@ -467,6 +474,7 @@ D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, + D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, ); path = Items; sourceTree = ""; @@ -698,6 +706,7 @@ D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, B8200E142280C4CF007245F4 /* ProgressBar.swift */, + 948DB67D2326DCD90011F916 /* MultiProgress.swift */, DBC4391622442196001AB423 /* CaretView.swift */, DBC4391722442197001AB423 /* DashLine.swift */, DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */, @@ -1014,6 +1023,7 @@ DBC4391922442197001AB423 /* DashLine.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, + D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */, D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, @@ -1079,6 +1089,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 */, @@ -1087,6 +1098,7 @@ D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, + 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, diff --git a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift index bc1c2219..ff2e751d 100644 --- a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift +++ b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift @@ -174,7 +174,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt super.init(frame: .zero) setText(fullText, startTag: startTag, endTag: endTag) - actionBlock = label?.createActionBlockFrom(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) + setActionMap(actionMap, additionalData: additionalData, delegateObject: delegateObject) } //------------------------------------------------------ diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h index fc76ed71..f1638f6a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h @@ -12,7 +12,7 @@ @class Label; @class MFSizeObject; -@interface MVMCoreUICheckBox : UIControl +@interface MVMCoreUICheckBox : UIControl @property (nullable, weak, nonatomic) MVMCoreUICheckMarkView *checkMark; @property (readonly, nonatomic) BOOL isSelected; diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h index a21dd55f..32e98f2a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h @@ -9,10 +9,11 @@ #import #import +#import @import MVMCore.MVMCoreViewProtocol; typedef void(^ValueChangeBlock)(void); -@interface MVMCoreUISwitch : UIControl +@interface MVMCoreUISwitch : UIControl @property (assign, nonatomic, getter=isOn) BOOL on; @property (nullable, strong, nonatomic) UIColor *onTintColor; diff --git a/MVMCoreUI/Atoms/Views/MultiProgress.swift b/MVMCoreUI/Atoms/Views/MultiProgress.swift new file mode 100644 index 00000000..fe902d9f --- /dev/null +++ b/MVMCoreUI/Atoms/Views/MultiProgress.swift @@ -0,0 +1,99 @@ +// +// MultiProgress.swift +// MVMCoreUI +// +// Created by Ryan on 9/9/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class ProgressBarObject { + ///from 0.0 to 1.0. input progress should be [0 100] + var progress: CGFloat = 0.0 + ///default color is cerulean + var color: UIColor = UIColor.mfCerulean() + + init(_ module: [AnyHashable: Any]?) { + progress = (module?.optionalCGFloatForKey("progress") ?? 0.0)/100 + if let colorString = module?.optionalStringForKey("progressColor") { + color = UIColor.mfGet(forHex: colorString) + } + } + + static func getProgressBarObjectList(_ list: [[AnyHashable: Any]]?) -> [ProgressBarObject]? { + guard list?.count ?? 0 > 0 else { + return nil + } + var progressList = [ProgressBarObject]() + for module in list! { + let progressObject = ProgressBarObject(module) + progressList.append(progressObject) + } + return progressList + } +} + +@objcMembers open class MultiProgress: MFView { + ///passing value to progressList creates corresponding progress bars + var progressList: Array? { + didSet { + for subview in subviews { + subview.removeFromSuperview() + } + guard (progressList?.count ?? 0) > 0 else { + return + } + var previous: UIView? + for progressObject in progressList! { + guard progressObject.progress > 0.0 else { + continue + } + let view = MFView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + view.backgroundColor = progressObject.color + view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: progressObject.progress).isActive = true + view.leadingAnchor.constraint(equalTo: previous?.trailingAnchor ?? leadingAnchor).isActive = true + previous = view + NSLayoutConstraint.constraintPinSubview(view, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false) + } + } + } + + var roundedRect: Bool = false { + didSet { + if roundedRect { + layer.cornerRadius = (thicknessConstraint?.constant ?? defaultHeight)/2 + } else { + layer.cornerRadius = 0 + } + } + } + var thicknessConstraint: NSLayoutConstraint? + let defaultHeight: CGFloat = 8 + + override open func setupView() { + super.setupView() + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .mfLightSilver() + clipsToBounds = true + if thicknessConstraint == nil { + thicknessConstraint = heightAnchor.constraint(equalToConstant: defaultHeight) + thicknessConstraint?.isActive = true + } + } + + 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 + roundedRect = json?.optionalBoolForKey("roundedRect") ?? false + progressList = ProgressBarObject.getProgressBarObjectList(json?.optionalArrayForKey("progressList") as? [[AnyHashable: Any]]) + } +} 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/Containers/TabBarController/TopTabbar.h b/MVMCoreUI/Containers/TabBarController/TopTabbar.h index e63911e7..d851294b 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.h +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.h @@ -30,6 +30,9 @@ @property (nonatomic, readonly) NSInteger selectedIndex; +/// A flag for if there should be padding before the first item. +@property (nonatomic) BOOL paddingBeforeFirstTab; + //method to set the height - (void)pinHeight:(CGFloat)height; diff --git a/MVMCoreUI/Containers/TabBarController/TopTabbar.m b/MVMCoreUI/Containers/TabBarController/TopTabbar.m index c52e9d66..e9d77c77 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.m +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.m @@ -75,6 +75,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)setupView { + self.paddingBeforeFirstTab = YES; self.maxHeight = BAR_HEIGHT; self.selectedIndex = 0; self.backgroundColor = [UIColor whiteColor]; @@ -229,6 +230,9 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + if (!self.paddingBeforeFirstTab && section == 0) { + return UIEdgeInsetsMake(SECTION_TOPPIN, 0,SECTION_BOTPIN, 0); + } return UIEdgeInsetsMake(SECTION_TOPPIN, SECTION_PADDING,SECTION_BOTPIN, 0); } @@ -341,7 +345,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)selectIndex:(NSInteger)index animated:(BOOL)animated { - if (self.collectionView) { + if (self.collectionView && [self.datasource numberOfTopTabbarItems:self] > 0) { [MVMCoreDispatchUtility performBlockOnMainThread:^{ NSInteger currentIndex = self.selectedIndex; self.selectedIndex = index; diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift index f1e295fe..aca450aa 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift @@ -20,27 +20,30 @@ import UIKit guard subviews.count == 0 else { return } - MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) headlineBody.headlineLabel.styleB1(true) headlineBody.spaceBetweenLabelsConstant = 0 - addSubview(headlineBody) - addSubview(imageView) - NSLayoutConstraint.pinViewTop(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true - NSLayoutConstraint.pinViewRight(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true - var constraint = NSLayoutConstraint.pinViewBottom(toSuperview: headlineBody, useMargins: true, constant: 0) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + view.addSubview(headlineBody) + view.addSubview(imageView) + + headlineBody.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor).isActive = true + view.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true + var constraint = view.bottomAnchor.constraint(equalTo: headlineBody.bottomAnchor) constraint.priority = .defaultLow constraint.isActive = true - imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - NSLayoutConstraint.pinViewLeft(toSuperview: imageView, useMargins: true, constant: 0).isActive = true - imageView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true - constraint = NSLayoutConstraint.pinViewBottom(toSuperview: imageView, useMargins: true, constant: 0) + imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + imageView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + imageView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor).isActive = true + view.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true + constraint = view.bottomAnchor.constraint(equalTo: imageView.bottomAnchor) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true - constraint = NSLayoutConstraint.pinViewTop(toSuperview: imageView, useMargins: true, constant: 0) + constraint = imageView.topAnchor.constraint(equalTo: view.topAnchor) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true diff --git a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift index b188d45b..075d33f1 100644 --- a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -34,9 +34,9 @@ import UIKit } if accordionButton.isSelected { - delegateObject?.moleculeDelegate?.addMolecules?(molecules, senderIndexPath: indexPath) + delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: .automatic) } else { - delegateObject?.moleculeDelegate?.removeMolecules?(molecules, senderIndexPath: indexPath) + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: .automatic) } if (json?.boolForKey("hideSeparatorWhenExpanded") ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 210eac6f..641b3721 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -137,7 +137,7 @@ import UIKit if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { updateViewHorizontalDefaults = useHorizontalMargins } - if json?.optionalBoolForKey("useVerticalMargins") ?? false { + if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false { topMarginPadding = 0 bottomMarginPadding = 0 } diff --git a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift new file mode 100644 index 00000000..1c509a8f --- /dev/null +++ b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift @@ -0,0 +1,81 @@ +// +// TabsTableViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/6/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class TabsTableViewCell: MoleculeTableViewCell { + let tabs = TopTabbar(frame: .zero) + var delegateObject: MVMCoreUIDelegateObject? + var previousTabIndex = 0 + + // MARK: - MFViewProtocol + override public func setupView() { + super.setupView() + guard tabs.superview == nil else { + return + } + tabs.paddingBeforeFirstTab = false + topMarginPadding = 8 + bottomMarginPadding = 0 + + tabs.translatesAutoresizingMaskIntoConstraints = false + tabs.delegate = self + tabs.datasource = self + contentView.addSubview(tabs) + + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values)) + tabs.reloadData() + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + tabs.updateView(size) + } + + // MARK: - MoleculeDelegateProtocol + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + tabs.reloadData() + } + + public override func reset() { + super.reset() + topMarginPadding = 8 + bottomMarginPadding = 0 + } +} + +extension TabsTableViewCell: TopTabbarDelegate { + public func shouldSelectItem(at index: Int, topTabbar: TopTabbar) -> Bool { + if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[topTabbar.selectedIndex] as? [[AnyHashable: Any]] { + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: index < tabs.selectedIndex ? .right : .left) + } + previousTabIndex = tabs.selectedIndex + return true + } + + public func topTabbar(_ topTabbar: TopTabbar, didSelectItemAt index: Int) { + if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[index] as? [[AnyHashable: Any]] { + delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: index < previousTabIndex ? .left : .right) + } + } +} + +extension TabsTableViewCell: TopTabbarDataSource { + public func number(ofTopTabbarItems topTabbar: TopTabbar) -> Int { + return json?.optionalDictionaryForKey("tabs")?.optionalArrayForKey("tabs")?.count ?? 0 + } + + public func topTabbar(_ topTabbar: TopTabbar, titleForItemAt index: Int) -> String? { + guard let tabs = json?.optionalDictionaryForKey("tabs")?.arrayForKey("tabs"), let label = tabs[index] as? [AnyHashable: Any], let title = label.optionalStringForKey(KeyText) else { + return "Select" + } + return title + } +} diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift index ffab9920..d373ce87 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift @@ -25,8 +25,12 @@ import UIKit return } headlineBody.styleListItem() - addSubview(headlineBody) - addSubview(mvmSwitch) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineBody) + view.addSubview(mvmSwitch) NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBody, rightView: mvmSwitch) } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift index 754f508b..0f184544 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift @@ -24,9 +24,13 @@ import UIKit guard mvmSwitch.superview == nil else { return } + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + headlineBodyTextButton.headlineBody.styleListItem() - addSubview(headlineBodyTextButton) - addSubview(mvmSwitch) + view.addSubview(headlineBodyTextButton) + view.addSubview(mvmSwitch) NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBodyTextButton, rightView: mvmSwitch) } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift index 017eee99..d47cf44c 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift @@ -24,8 +24,12 @@ import UIKit guard mvmSwitch.superview == nil else { return } - addSubview(label) - addSubview(mvmSwitch) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(label) + view.addSubview(mvmSwitch) label.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) NSLayoutConstraint.pinSubviewsCenter(leftView: label, rightView: mvmSwitch) } 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 1077ac0e..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) { @@ -70,34 +78,36 @@ open class HeadlineBody: ViewConstrainingView { translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear clipsToBounds = true - - addSubview(headlineLabel) - addSubview(messageLabel) + + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineLabel) + view.addSubview(messageLabel) headlineLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) messageLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + view.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - topPin = headlineLabel.topAnchor.constraint(equalTo: 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 - leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintTitle?.isActive = true - rightConstraintTitle = rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) + rightConstraintTitle = view.rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) rightConstraintTitle?.isActive = true - leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintMessage?.isActive = true - rightConstraintMessage = rightAnchor.constraint(equalTo: messageLabel.rightAnchor) + rightConstraintMessage = view.rightAnchor.constraint(equalTo: messageLabel.rightAnchor) rightConstraintMessage?.isActive = true - bottomPin = bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0) - bottomPin?.isActive = true + view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0).isActive = true } // MARK: - Constraining @@ -109,16 +119,6 @@ open class HeadlineBody: ViewConstrainingView { } } - open override func setLeftPinConstant(_ constant: CGFloat) { - leftConstraintTitle?.constant = constant - leftConstraintMessage?.constant = constant - } - - open override func setRightPinConstant(_ constant: CGFloat) { - rightConstraintTitle?.constant = constant - rightConstraintMessage?.constant = constant - } - // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift index bb638624..009189cb 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift @@ -28,23 +28,27 @@ import UIKit guard subviews.count == 0 else { return } - addSubview(headlineBody) - addSubview(textButton) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineBody) + view.addSubview(textButton) headlineBody.styleListItem() - headlineBody.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0).isActive = true - headlineBody.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true - var constraint = layoutMarginsGuide.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) + headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true + headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultHigh constraint.isActive = true spaceBetween = textButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetween?.isActive = true - textButton.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(equalTo: textButton.bottomAnchor).isActive = true - layoutMarginsGuide.rightAnchor.constraint(greaterThanOrEqualTo: textButton.rightAnchor).isActive = true - constraint = layoutMarginsGuide.rightAnchor.constraint(equalTo: textButton.rightAnchor) + textButton.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.bottomAnchor.constraint(equalTo: textButton.bottomAnchor).isActive = true + view.rightAnchor.constraint(greaterThanOrEqualTo: textButton.rightAnchor).isActive = true + constraint = view.rightAnchor.constraint(equalTo: textButton.rightAnchor) constraint.priority = .defaultHigh constraint.isActive = true } diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index ad395eec..14ad9e70 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -14,24 +14,30 @@ 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.optionalStringForKey("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.optionalStringForKey("horizontalAlignment") { + if let alignment = json?.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"horizontalAlignment"]) { horizontalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) + } else { + horizontalAlignment = nil } } } @@ -80,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() } } @@ -140,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]? @@ -234,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 @@ -245,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) @@ -260,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) @@ -277,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 9175781b..945eb7bf 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -41,6 +41,7 @@ @"checkbox" : MVMCoreUICheckBox.class, @"cornerLabels" : CornerLabels.class, @"progressBar": ProgressBar.class, + @"multiProgressBar": MultiProgress.class, @"checkbox": MVMCoreUICheckBox.class, @"listItem": MoleculeTableViewCell.class, @"accordionListItem": AccordionMoleculeTableViewCell.class, @@ -58,7 +59,9 @@ @"labelSwitch": LabelSwitch.class, @"headlineBodySwitch": HeadlineBodySwitch.class, @"headlineBodyTextButton": HeadlineBodyTextButton.class, - @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class + @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class, + @"tabsListItem": TabsTableViewCell.class, + @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class } mutableCopy]; }); return mapping; diff --git a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h index cfa1504f..57eb63f5 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h +++ b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h @@ -17,7 +17,7 @@ - (void)moleculeLayoutUpdated:(nonnull UIView *)molecule; /// Asks the delegate to add or remove molecules. -- (void)addMolecules:(nonnull NSArray *)molecules senderIndexPath:(nonnull NSIndexPath *)indexPath; -- (void)removeMolecules:(nonnull NSArray *)molecules senderIndexPath:(nonnull NSIndexPath *)indexPath; +- (void)addMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; +- (void)removeMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; @end diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 7ad011b3..0270cb63 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -117,34 +117,40 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } - open override func addMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { - var indexPaths: [IndexPath] = [] - var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = [] - for (index, molecule) in molecules.enumerated() { - if let info = getMoleculeInfo(with: molecule) { - moleculeList.append(info) - indexPaths.append(IndexPath(row: indexPath.row + 1 + index, section: 0)) - tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + open override func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. + DispatchQueue.main.async { + guard let cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else { + return } + var indexPaths: [IndexPath] = [] + for molecule in molecules { + if let info = self.getMoleculeInfo(with: molecule) { + self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + let index = indexPath.row + 1 + indexPaths.count + self.moleculesInfo?.insert(info, at: index) + indexPaths.append(IndexPath(row: index, section: 0)) + } + } + self.tableView?.insertRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() } - moleculesInfo?.insert(contentsOf: moleculeList, at: indexPath.row + 1) - tableView?.insertRows(at: indexPaths, with: .automatic) } - open override func removeMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { - guard let moleculesList = moleculesInfo else { - return - } + open override func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { var indexPaths: [IndexPath] = [] - for (index, moleculeInfo) in moleculesList.enumerated() { - if molecules.contains(where: { (molecule) -> Bool in + for molecule in molecules { + if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in return NSDictionary(dictionary: molecule).isEqual(to: moleculeInfo.molecule) }) { - indexPaths.append(IndexPath(row: index, section: 0)) - moleculesInfo?.remove(at: index) + moleculesInfo?.remove(at: removeIndex) + indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) } } - tableView?.deleteRows(at: indexPaths, with: .automatic) + self.tableView?.deleteRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() } // MARK: - Convenience