diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index ad6177dd..2d45fd63 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -20,8 +20,9 @@ 01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */; }; 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; - B8200E192281DC1A007245F4 /* ProgressBarWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* ProgressBarWithLabel.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, ); }; }; D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; @@ -169,8 +170,11 @@ D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A638FC22CA98280052ED1F /* HeadlineBody.swift */; }; D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390022CBB1820052ED1F /* Carousel.swift */; }; D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */; }; + D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */; }; D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */; }; + D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */; }; + D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */; }; D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; }; D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */; }; D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; }; @@ -196,8 +200,9 @@ 01CA51B4229716F60071A6EE /* Switch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = ""; }; 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; + 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; - B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBarWithLabel.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 = ""; }; D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUINavigationController.m; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; @@ -349,8 +354,11 @@ D2A638FC22CA98280052ED1F /* HeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBody.swift; sourceTree = ""; }; D2A6390022CBB1820052ED1F /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeCollectionViewCell.swift; sourceTree = ""; }; + D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeadlineBody.swift; sourceTree = ""; }; D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewControllerMappingObject.h; sourceTree = ""; }; D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIViewControllerMappingObject.m; sourceTree = ""; }; + D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroller.swift; sourceTree = ""; }; + D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplate.swift; sourceTree = ""; }; D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = ""; }; D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeTableViewCell.swift; sourceTree = ""; }; D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTableViewController.swift; sourceTree = ""; }; @@ -447,6 +455,7 @@ D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */, D296E13B2295969C0051EBE7 /* MoleculeListCellProtocol.h */, D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */, + D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, ); path = Templates; sourceTree = ""; @@ -473,6 +482,7 @@ D29DF10E21E67A77003B2FB9 /* Molecules */ = { isa = PBXGroup; children = ( + 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */, 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */, D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, @@ -490,6 +500,7 @@ B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */, B8200E182281DC1A007245F4 /* ProgressBarView.swift */, 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */, + B8200E182281DC1A007245F4 /* CornerLabels.swift */, D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */, D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D2A6390022CBB1820052ED1F /* Carousel.swift */, @@ -497,6 +508,8 @@ D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, + D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */, + D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */, ); path = Molecules; sourceTree = ""; @@ -958,12 +971,14 @@ D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, + D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, + D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, @@ -996,6 +1011,7 @@ D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, + D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, @@ -1008,11 +1024,12 @@ D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, + 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, - B8200E192281DC1A007245F4 /* ProgressBarWithLabel.swift in Sources */, + B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 10b9220e..06ddbed5 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -178,6 +178,17 @@ public typealias ActionBlock = () -> Void setLabel(label, withHTML: json?.optionalStringForKey("html")) + if let alignment = json?.optionalStringForKey("textAlignment") { + switch alignment { + case "center": + label.textAlignment = .center + case "right": + label.textAlignment = .right + default: + label.textAlignment = .left + } + } + if let backgroundColorHex = json?.optionalStringForKey(KeyBackgroundColor), !backgroundColorHex.isEmpty { label.backgroundColor = UIColor.mfGet(forHex: backgroundColorHex) } @@ -217,7 +228,7 @@ public typealias ActionBlock = () -> Void case "strikethrough": attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) - + attributedString.addAttribute(.baselineOffset, value: 0, range: range) case "color": if let colorHex = attribute.optionalStringForKey(KeyTextColor), !colorHex.isEmpty { attributedString.removeAttribute(.foregroundColor, range: range) @@ -368,6 +379,7 @@ extension Label { public func reset() { text = nil attributedText = nil + textAlignment = .left originalAttributedString = nil styleB2(true) } diff --git a/MVMCoreUI/Atoms/Views/MFLoadImageView.swift b/MVMCoreUI/Atoms/Views/MFLoadImageView.swift index 703ffd65..21802b38 100644 --- a/MVMCoreUI/Atoms/Views/MFLoadImageView.swift +++ b/MVMCoreUI/Atoms/Views/MFLoadImageView.swift @@ -22,9 +22,11 @@ import UIKit var imageWidth: CGFloat? var imageHeight: CGFloat? + var delegateObject: MVMCoreUIDelegateObject? + // For keeping track of current state. - private var edges: UIRectEdge? - private var spinnerHeight: CGFloat? + private var edges = UIRectEdge(rawValue: 0) + private var spinnerHeight: CGFloat = 0 private var currentImageWidth: CGFloat? private var currentImageHeight: CGFloat? private var currentImageName: String? @@ -37,7 +39,6 @@ import UIKit // The default is an image that is centered with no edges pinned. So it will take the size of the content and fill as needed. public init() { - edges = UIRectEdge(rawValue: 0) super.init(frame: .zero) } @@ -91,10 +92,7 @@ import UIKit pinView(toSuperView: imageView) // Setup edges constraints - if edges == nil { - edges = UIRectEdge(rawValue: 0) - } - pinEdges(edges!) + pinEdges(edges) // Setup spinner. loadingSpinner.clipsToBounds = true @@ -153,6 +151,40 @@ import UIKit return false } + // MARK: - Constraints + + func setHeight(_ height: CGFloat) { + if let heightConstraint = heightConstraint, MVMCoreGetterUtility.cgfequal(heightConstraint.multiplier, 1) { + heightConstraint.constant = height + } else { + heightConstraint?.isActive = false + heightConstraint = imageView.heightAnchor.constraint(equalToConstant: height) + heightConstraint?.priority = UILayoutPriority(rawValue: 900) + } + heightConstraint?.isActive = true + } + + func setWidth(_ width: CGFloat) { + if let widthConstraint = widthConstraint, MVMCoreGetterUtility.cgfequal(widthConstraint.multiplier, 1) { + widthConstraint.constant = width + } else { + widthConstraint = imageView.widthAnchor.constraint(equalToConstant: width) + widthConstraint?.priority = UILayoutPriority(rawValue: 900) + } + widthConstraint?.isActive = true + } + + func layoutWillChange(width: CGFloat?, height: CGFloat?, size: CGSize?) -> Bool { + guard addSizeConstraintsForAspectRatio else { + return false + } + let widthWillChange = !MVMCoreGetterUtility.cgfequal(widthConstraint?.constant ?? 0, width ?? 0) + let heightWillChange = !MVMCoreGetterUtility.cgfequal(heightConstraint?.constant ?? 0, height ?? 0) + let sizeWillChange = (width == nil || height == nil) && !(size?.equalTo(imageView.image?.size ?? CGSize.zero) ?? false) + let heightChangeFromSpinner = ((height ?? size?.height) ?? 0) < loadingSpinnerHeightConstraint?.constant ?? CGFloat.leastNormalMagnitude + return widthWillChange || heightWillChange || sizeWillChange || heightChangeFromSpinner + } + // Constrains the image view to be the size provided. Used to size it to the image to fix aspect fit defect. func addConstraints(width: NSNumber?, height: NSNumber?, size: CGSize?) { widthConstraint?.isActive = false @@ -162,26 +194,25 @@ import UIKit } if let width = width, let height = height { - heightConstraint = imageView.heightAnchor.constraint(equalToConstant: height.cgfloat()) - widthConstraint = imageView.widthAnchor.constraint(equalToConstant: width.cgfloat()) + setHeight(height.cgfloat()) + setWidth(width.cgfloat()) } else if let width = width, let size = size { - widthConstraint = imageView.widthAnchor.constraint(equalToConstant: width.cgfloat()) + setWidth(width.cgfloat()) heightConstraint = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: size.height/size.width) + heightConstraint?.priority = UILayoutPriority(rawValue: 900) + heightConstraint?.isActive = true } else if let height = height, let size = size { - heightConstraint = imageView.heightAnchor.constraint(equalToConstant: height.cgfloat()) + setHeight(height.cgfloat()) widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor, multiplier: size.width/size.height) + widthConstraint?.priority = UILayoutPriority(rawValue: 900) + widthConstraint?.isActive = true } - widthConstraint?.priority = UILayoutPriority(rawValue: 900) - heightConstraint?.priority = UILayoutPriority(rawValue: 900) - heightConstraint?.isActive = true - widthConstraint?.isActive = true - imageView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal) - imageView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical) } // MARK: - MVMCoreUIMoleculeViewProtocol functions open override func setAsMolecule() { addSizeConstraintsForAspectRatio = true + pinEdges(.all) } public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { @@ -190,6 +221,7 @@ import UIKit open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject if let accessibilityString = json?.optionalStringForKey("accessibilityText") { imageView.accessibilityLabel = accessibilityString imageView.accessibilityTraits = .staticText @@ -197,6 +229,13 @@ import UIKit } let width = json?.optionalCGFloatForKey("width") ?? imageWidth let height = json?.optionalCGFloatForKey("height") ?? imageHeight + // For smoother transitions, set constraints that we know immediately. + if let width = width, addSizeConstraintsForAspectRatio { + setWidth(width) + } + if let height = height, addSizeConstraintsForAspectRatio { + setHeight(height) + } if let imageName = json?.optionalStringForKey("image"), shouldLoadImage(withName: imageName, width: width, height: height) { imageView.image = nil imageView.animatedImage = nil @@ -210,9 +249,9 @@ import UIKit self.currentImageName = imageName self.currentImageWidth = width?.cgfloat() self.currentImageHeight = height?.cgfloat() - self.loadingSpinner.resumeSpinnerAfterDelay() - if let height = self.spinnerHeight { - self.loadingSpinnerHeightConstraint?.constant = height + if MVMCoreCache.isHostedImage(imageName) { + self.loadingSpinner.resumeSpinnerAfterDelay() + self.loadingSpinnerHeightConstraint?.constant = self.spinnerHeight } let finishedLoadingBlock: MVMCoreGetImageBlock = {[weak self] (image, data, isFallbackImage) in MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in @@ -220,9 +259,13 @@ import UIKit return } self?.isFallbackImage = isFallbackImage - self?.loadingSpinnerHeightConstraint?.constant = 0 self?.loadingSpinner.pause() + let layoutWillChange = self?.layoutWillChange(width: self?.currentImageWidth, height: self?.currentImageHeight, size: image?.size) ?? false self?.addConstraints(width: width, height: height, size: image?.size) + self?.loadingSpinnerHeightConstraint?.constant = 0 + if layoutWillChange { + self?.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self!) + } completionHandler(image,data,isFallbackImage) })} @@ -241,9 +284,8 @@ import UIKit MVMCoreDispatchUtility.performBlock(onMainThread: { [unowned self] in self.currentImageName = imageName self.loadingSpinner.resumeSpinnerAfterDelay() - if let height = self.spinnerHeight { - self.loadingSpinnerHeightConstraint?.constant = height - } + self.loadingSpinnerHeightConstraint?.constant = self.spinnerHeight + MVMCoreCache.shared()?.getCroppedImage(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, finalRect: cropRect, flipImage: flipImage, localFallbackImageName: customFallbackImage ?? MVMCoreUIUtility.localizedImageName("fallback"), completionHandler: { [weak self] (image, data, isFallBackImage) in MVMCoreDispatchUtility.performBlock(onMainThread: { guard let image = image, let loadingImageName = self?.currentImageName, loadingImageName == imageName else { diff --git a/MVMCoreUI/Atoms/Views/MFRadioButton.h b/MVMCoreUI/Atoms/Views/MFRadioButton.h index 86837256..c5e053c0 100644 --- a/MVMCoreUI/Atoms/Views/MFRadioButton.h +++ b/MVMCoreUI/Atoms/Views/MFRadioButton.h @@ -30,7 +30,6 @@ typedef void (^PerformActionForRadioButton)(void); @property (nullable, copy, nonatomic) PerformActionForRadioButton performActionForCheck; typedef void (^CircleSelectedClosure)(_Nonnull id sender); - @property (nonatomic, strong, nullable) UIColor* fillColor; @property (nonatomic, strong, nullable) UIColor* borderColor; @property (nonatomic) CGFloat circleBorderWidth; @@ -38,11 +37,16 @@ typedef void (^CircleSelectedClosure)(_Nonnull id sender); @property (nonatomic, copy, nullable) CircleSelectedClosure handleSelectionBlock; @property (nonatomic) CGFloat circleDiameter; @property (nonatomic, nullable, strong, readonly) CAShapeLayer * myCircle; - +@property (nullable, weak, nonatomic) UIView *outerCircleView; +@property (nullable, weak, nonatomic) UIView *innerCircleView; @property (nonatomic, strong, nullable) NSLayoutConstraint *heightConstraint; @property (nonatomic, strong, nullable) NSLayoutConstraint *widthConstraint; @property (nonatomic) BOOL respondsToTapGesture; @property (nonatomic, getter = isSelected) BOOL selected; +@property (strong, nonatomic) NSLayoutConstraint *innerHeightConstarint; +@property (strong, nonatomic) NSLayoutConstraint *innerWidthConstarint; +@property (strong, nonatomic) NSLayoutConstraint *outerHeightConstarint; +@property (strong, nonatomic) NSLayoutConstraint *outerWidthConstarint; // Set line width manually - (void)setCheckMarkLineWidth:(CGFloat)lineWidth; diff --git a/MVMCoreUI/Atoms/Views/MFRadioButton.m b/MVMCoreUI/Atoms/Views/MFRadioButton.m index d1a9ee03..06f6c3a0 100644 --- a/MVMCoreUI/Atoms/Views/MFRadioButton.m +++ b/MVMCoreUI/Atoms/Views/MFRadioButton.m @@ -12,11 +12,6 @@ static CGFloat const DefaultOuterCircleSize = 31; static CGFloat const DefaultInnerCircleSize = 19; -@interface MFRadioButton () -@property (nullable, weak, nonatomic) UIView *outerCircleView; -@property (nullable, weak, nonatomic) UIView *innerCircleView; -@end - @implementation MFRadioButton @@ -64,9 +59,11 @@ static CGFloat const DefaultInnerCircleSize = 19; [outerCircleView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES; [outerCircleView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES; [outerCircleView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES; - - [outerCircleView.widthAnchor constraintEqualToConstant:DefaultOuterCircleSize].active = YES; - [outerCircleView.heightAnchor constraintEqualToConstant:DefaultOuterCircleSize].active = YES; + + self.outerHeightConstarint = [outerCircleView.widthAnchor constraintEqualToConstant:DefaultOuterCircleSize]; + self.outerHeightConstarint.active = YES; + self.outerWidthConstarint = [outerCircleView.heightAnchor constraintEqualToConstant:DefaultOuterCircleSize]; + self.outerWidthConstarint.active = YES; UIView *innerCircleView = [[UIView alloc] init]; [outerCircleView addSubview:innerCircleView]; @@ -76,8 +73,11 @@ static CGFloat const DefaultInnerCircleSize = 19; [innerCircleView.centerXAnchor constraintEqualToAnchor:outerCircleView.centerXAnchor].active = YES; [innerCircleView.centerYAnchor constraintEqualToAnchor:outerCircleView.centerYAnchor].active = YES; - [innerCircleView.widthAnchor constraintEqualToConstant:DefaultInnerCircleSize].active = YES; - [innerCircleView.heightAnchor constraintEqualToConstant:DefaultInnerCircleSize].active = YES; + + self.innerHeightConstarint = [innerCircleView.heightAnchor constraintEqualToConstant:DefaultInnerCircleSize]; + self.innerHeightConstarint.active = YES; + self.innerWidthConstarint = [innerCircleView.widthAnchor constraintEqualToConstant:DefaultInnerCircleSize]; + self.innerWidthConstarint.active = YES; outerCircleView.userInteractionEnabled = NO; innerCircleView.userInteractionEnabled = NO; diff --git a/MVMCoreUI/Atoms/Views/MFView.m b/MVMCoreUI/Atoms/Views/MFView.m index 937da3f2..4e0d71ab 100644 --- a/MVMCoreUI/Atoms/Views/MFView.m +++ b/MVMCoreUI/Atoms/Views/MFView.m @@ -46,6 +46,13 @@ #pragma mark - MVMCoreUIMoleculeViewProtocol +- (void)setAsMolecule { + self.translatesAutoresizingMaskIntoConstraints = NO; + if (@available(iOS 11.0, *)) { + self.insetsLayoutMarginsFromSafeArea = NO; + } +} + - (void)reset { self.backgroundColor = [UIColor clearColor]; } diff --git a/MVMCoreUI/Atoms/Views/SeparatorView.m b/MVMCoreUI/Atoms/Views/SeparatorView.m index 2d01e6d2..a666d450 100644 --- a/MVMCoreUI/Atoms/Views/SeparatorView.m +++ b/MVMCoreUI/Atoms/Views/SeparatorView.m @@ -114,6 +114,11 @@ } else { self.hidden = YES; } + + NSString *colorString = [json string:KeyBackgroundColor]; + if (colorString) { + self.backgroundColor = [UIColor mfGetColorForHex:colorString]; + } } #pragma mark - helper diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index b07fd6bb..a5e359d0 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -73,6 +73,9 @@ // Add a view to be constrained in this view. - (void)addConstrainedView:(nonnull UIView *)view; +/// Can override to change how the molecule is added when shouldSetupMoleculeFromJSON = true. Inserts the molecule at 0 and calls pinToSuperView. +- (void)addMolecule:(nonnull UIView *)molecule; + // Change the alignment of the label - (void)alignLeft; - (void)alignCenterHorizontal; diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index 6295a218..ba5f1bd0 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -241,6 +241,11 @@ } } +- (void)addMolecule:(nonnull UIView *)molecule { + [self insertSubview:molecule atIndex:0]; + [self pinViewToSuperView:molecule]; +} + #pragma mark - MVMCoreUIViewConstrainingProtocol - (void)alignHorizontal:(UIStackViewAlignment)alignment { @@ -307,6 +312,9 @@ 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]; UIEdgeInsets margins = [MVMCoreUIUtility getMarginsForView:self]; if (self.updateViewHorizontalDefaults) { @@ -333,6 +341,7 @@ } - (void)reset { + [super reset]; self.updateViewHorizontalDefaults = NO; self.updateViewVerticalDefaults = NO; if ([self.molecule respondsToSelector:@selector(alignment)]) { @@ -344,24 +353,24 @@ } } -- (void)setAsMolecule { -} - - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { [super setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; [self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; - if (self.shouldSetupMoleculeFromJSON && !self.molecule) { + if (self.shouldSetupMoleculeFromJSON) { NSDictionary *moleculeJSON = [json dict:KeyMolecule]; - if (moleculeJSON) { + if (self.molecule) { + [self.molecule setWithJSON:moleculeJSON delegateObject:delegateObject additionalData:additionalData]; + } else if (moleculeJSON) { UIView *molecule = [[MVMCoreUIMoleculeMappingObject sharedMappingObject] createMoleculeForJSON:moleculeJSON delegateObject:delegateObject constrainIfNeeded:true]; if (molecule) { - [self insertSubview:molecule atIndex:0]; - [self pinViewToSuperView:molecule]; + [self addMolecule:molecule]; } self.molecule = molecule; + [self setMoleculeAccessibility]; } - [self setMoleculeAccessibility]; + } else { + [self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; } NSNumber *useHorizontalMargins = [json optionalNumberForKey:@"useHorizontalMargins"]; diff --git a/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m b/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m index b728b063..98d2b5ef 100644 --- a/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m +++ b/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m @@ -61,6 +61,9 @@ tableView.separatorStyle = UITableViewCellSeparatorStyleNone; tableView.delegate = self; tableView.dataSource = self; + if (@available(iOS 11.0, *)) { + tableView.insetsContentViewsToSafeArea = NO; + } if ([tableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)]) { tableView.cellLayoutMarginsFollowReadableWidth = NO; } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 85127a45..0293e860 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -21,23 +21,21 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { private var topViewBottomConstraint: NSLayoutConstraint? private var bottomViewTopConstraint: NSLayoutConstraint? - //MARK: - MVMCoreViewProtocol + //MARK: - MFViewController open override func updateViews() { super.updateViews() let width = view.bounds.width - MFStyler.setDefaultMarginsFor(contentView, size: width) if let topView = topView as? MVMCoreViewProtocol { topView.updateView(width) - showHeader() + showHeader(width) } if let bottomView = bottomView as? MVMCoreViewProtocol { bottomView.updateView(width) - showFooter() + showFooter(width) } self.tableView?.reloadData() } - //MARK: - MFViewController open override func newDataBuildScreen() { super.newDataBuildScreen() createViewForTableHeader() @@ -103,14 +101,15 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { currentSpaceForCompare = currentSpace * 2; } + let width = view.bounds.width if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 0.1) { if fillTop && fillBottom { // space both let half = newSpace / 2 topViewBottomConstraint?.constant = half bottomViewTopConstraint?.constant = half - showHeader() - showFooter() + showHeader(width) + showFooter(width) } else if fillTop { // Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one. if bottomViewOutsideOfScrollArea { @@ -118,11 +117,11 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { } else { topViewBottomConstraint?.constant = newSpace } - showHeader() + showHeader(width) } else if fillBottom { // Only bottom is spaced. bottomViewTopConstraint?.constant = newSpace - showFooter() + showFooter(width) } } } @@ -141,7 +140,7 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { topViewBottomConstraint = headerView.bottomAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceBelowTopView() ?? 0) topViewBottomConstraint?.isActive = true self.headerView = headerView - showHeader() + showHeader(nil) } /// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter. @@ -157,11 +156,11 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { footerView.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true footerView.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true self.footerView = footerView - showFooter() + showFooter(nil) } /// Takes the current headerView and adds it to the tableHeaderView - func showHeader() { + func showHeader(_ sizingWidth: CGFloat?) { headerView?.removeFromSuperview() tableView?.tableHeaderView = nil guard let headerView = headerView else { @@ -177,7 +176,7 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { } /// Takes the current footerView and adds it to the tableFooterView - func showFooter() { + func showFooter(_ sizingWidth: CGFloat?) { footerView?.removeFromSuperview() safeAreaView?.removeFromSuperview() guard let footerView = footerView, let tableView = tableView else { diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index 299cdf9b..dae861dd 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -22,7 +22,7 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { var bottomViewOutsideOfScroll = false private var safeAreaView: UIView? - private var heightConstraint: NSLayoutConstraint? + var heightConstraint: NSLayoutConstraint? open override func updateViews() { super.updateViews() diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m index 0fcc51a2..77472e11 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m @@ -303,8 +303,9 @@ } - (void)addViewToContentView:(UIView *)bottomView { - - self.bottomConstraint.active = YES; + if (![self bottomViewOutsideOfScroll]) { + self.bottomConstraint.active = YES; + } // Buttons will be at the bottom of the content view. [self.contentView addSubview:bottomView]; diff --git a/MVMCoreUI/Molecules/ActionDetailWithImage.swift b/MVMCoreUI/Molecules/ActionDetailWithImage.swift new file mode 100644 index 00000000..4792dbd6 --- /dev/null +++ b/MVMCoreUI/Molecules/ActionDetailWithImage.swift @@ -0,0 +1,186 @@ +// +// ActionDetailWithImage.swift +// MVMCoreUI +// +// Created by Christiano, Kevin on 6/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class ActionDetailWithImage: ViewConstrainingView { + //------------------------------------------------------ + // MARK: - Outlets + //------------------------------------------------------ + + let title = Label.commonLabelH3(true) + let message = Label.commonLabelB3(true) + let button = PrimaryButton.primaryTinyButton(false)! + let imageLoader = MFLoadImageView(pinnedEdges: .all) + let leftContainer = ViewConstrainingView.empty() + + //------------------------------------------------------ + // MARK: - Properties + //------------------------------------------------------ + + var bottomTitlePadding: CGFloat = PaddingOne + + //------------------------------------------------------ + // MARK: - Constraints + //------------------------------------------------------ + + var imageLeadingConstraint: NSLayoutConstraint? + var buttonTopConstraint: NSLayoutConstraint? + var messageTopConstraint: NSLayoutConstraint? + + //------------------------------------------------------ + // MARK: - Initialization + //------------------------------------------------------ + + public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + public convenience init(json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.init() + setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + //------------------------------------------------------ + // MARK: - View Lifecycle + //------------------------------------------------------ + + override open func setupView() { + super.setupView() + + guard subviews.isEmpty else { return } + + setDefaultState() + + addSubview(leftContainer) + addSubview(imageLoader) + leftContainer.addSubview(title) + leftContainer.addSubview(message) + leftContainer.addSubview(button) + + leftContainer.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + leftContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: leftContainer.bottomAnchor).isActive = true + let leftContainerBottom = leftContainer.bottomAnchor.constraint(equalTo: bottomAnchor) + leftContainerBottom.priority = UILayoutPriority(249) + leftContainerBottom.isActive = true + + title.topAnchor.constraint(equalTo: leftContainer.topAnchor).isActive = true + title.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true + leftContainer.trailingAnchor.constraint(equalTo: title.trailingAnchor).isActive = true + + messageTopConstraint = message.topAnchor.constraint(equalTo: title.bottomAnchor, constant: PaddingOne) + messageTopConstraint?.isActive = true + + message.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true + leftContainer.trailingAnchor.constraint(equalTo: message.trailingAnchor).isActive = true + + buttonTopConstraint = button.topAnchor.constraint(equalTo: message.bottomAnchor, constant: PaddingTwo) + buttonTopConstraint?.isActive = true + + button.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true + leftContainer.bottomAnchor.constraint(equalTo: button.bottomAnchor).isActive = true + leftContainer.trailingAnchor.constraint(greaterThanOrEqualTo: button.trailingAnchor).isActive = true + + imageLoader.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: imageLoader.trailingAnchor).isActive = true + imageLeadingConstraint = imageLoader.leadingAnchor.constraint(greaterThanOrEqualTo: leftContainer.trailingAnchor, constant: 16) + imageLeadingConstraint?.isActive = true + imageLoader.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true + + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: imageLoader.bottomAnchor).isActive = true + let imageloaderBottom = imageLoader.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + imageloaderBottom.priority = UILayoutPriority(249) + imageloaderBottom.isActive = true + } + + override open func updateView(_ size: CGFloat) { + super.updateView(size) + + title.updateView(size) + message.updateView(size) + button.updateView(size) + imageLoader.updateView(size) + leftContainer.updateView(size) + + + messageTopConstraint?.constant = title.hasText && message.hasText ? bottomTitlePadding : 0 + buttonTopConstraint?.constant = message.hasText || title.hasText ? PaddingTwo : 0 + } + + public override static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 197 + } + + //------------------------------------------------------ + // MARK: - Methods + //------------------------------------------------------ + + private func setDefaultState() { + + title.font = MFStyler.fontH3() + message.font = MFStyler.fontB3() + button.setAsSecondaryCustom() + button.isHidden = false + imageLoader.imageView.contentMode = .scaleAspectFit + imageLoader.addSizeConstraintsForAspectRatio = true + bottomTitlePadding = PaddingOne + } + + override open func reset() { + super.reset() + + title.reset() + message.reset() + button.reset() + imageLeadingConstraint?.constant = 16 + imageLoader.reset() + + setDefaultState() + } + + open override func setAsMolecule() { + super.setAsMolecule() + + title.setAsMolecule() + message.setAsMolecule() + button.setAsMolecule() + imageLoader.setAsMolecule() + + setDefaultState() + } + + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json else { return } + + if let padding = dictionary.optionalCGFloatForKey("bottomTitlePadding") { + bottomTitlePadding = padding + } + + title.setWithJSON(dictionary.optionalDictionaryForKey("title"), delegateObject: delegateObject, additionalData: additionalData) + message.setWithJSON(dictionary.optionalDictionaryForKey("message"), delegateObject: delegateObject, additionalData: additionalData) + imageLoader.setWithJSON(dictionary.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) + if let buttonDictionary = dictionary.optionalDictionaryForKey("button") { + button.setWithJSON(buttonDictionary, delegateObject: delegateObject, additionalData: additionalData) + } else { + button.isHidden = true + } + } +} diff --git a/MVMCoreUI/Molecules/Carousel.swift b/MVMCoreUI/Molecules/Carousel.swift index 082fcd6d..d0efcce3 100644 --- a/MVMCoreUI/Molecules/Carousel.swift +++ b/MVMCoreUI/Molecules/Carousel.swift @@ -62,7 +62,7 @@ open class Carousel: ViewConstrainingView { pinView(toSuperView: collectionView) collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) - collectionViewHeight?.isActive = true + collectionViewHeight?.isActive = false } open override func updateView(_ size: CGFloat) { @@ -81,12 +81,19 @@ open class Carousel: ViewConstrainingView { // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + collectionView.backgroundColor = backgroundColor + collectionView.layer.borderColor = backgroundColor?.cgColor + collectionView.layer.borderWidth = (json?.boolForKey("border") ?? false) ? 1 : 0 + backgroundColor = .white registerCells(with: json, delegateObject: delegateObject) setupLayout(with: json) prepareMolecules(with: json) itemWidthPercent = (json?.optionalCGFloatForKey("itemWidthPercent") ?? 100) / 100 setAlignment(with: json?.optionalStringForKey("itemAlignment")) - collectionViewHeight?.constant = json?.optionalCGFloatForKey("height") ?? 300 + if let height = json?.optionalCGFloatForKey("height") { + collectionViewHeight?.constant = height + collectionViewHeight?.isActive = true + } setupPagingMolecule(json: json?.optionalDictionaryForKey("pagingMolecule"), delegateObject: delegateObject) collectionView.reloadData() } @@ -248,6 +255,10 @@ extension Carousel: UICollectionViewDelegateFlowLayout { let itemWidth = collectionView.bounds.width * itemWidthPercent return CGSize(width: itemWidth, height: collectionView.bounds.height) } + + public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + (cell as? MoleculeCollectionViewCell)?.setPeaking(false, animated: false) + } } extension Carousel: UICollectionViewDataSource { diff --git a/MVMCoreUI/Molecules/CornerLabels.swift b/MVMCoreUI/Molecules/CornerLabels.swift new file mode 100644 index 00000000..06917608 --- /dev/null +++ b/MVMCoreUI/Molecules/CornerLabels.swift @@ -0,0 +1,180 @@ +// +// ProgressBarView.swift +// MVMCoreUI +// +// Created by Panth Patel on 5/3/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class CornerLabels: ViewConstrainingView { + + let topLeftLabel = Label.commonLabelB1(true) + let topRightLabel = Label.commonLabelB1(true) + let bottomLeftLabel = Label.commonLabelB3(true) + let bottomRightLabel = Label.commonLabelB3(true) + let topLabelsView = MVMCoreUICommonViewsUtility.commonView() + let bottomLabelsView = MVMCoreUICommonViewsUtility.commonView() + + /// The space between the molecule and top labels. Set to 0 if the top labels or molecule are not set. + var spaceAboveMolecule: CGFloat = 6.0 { + didSet { + if spaceAboveMolecule != oldValue { + topLabelToMoleculeConstraint?.constant = spaceAboveMolecule + } + } + } + + /// The space between the molecule and bottom labels. Set to 0 if the bottom labels or molecule are not set. + var spaceBelowMolecule: CGFloat = 6.0 { + didSet { + if spaceBelowMolecule != oldValue { + bottomLabelToMoleculeConstraint?.constant = spaceBelowMolecule + } + } + } + + var topLabelToMoleculeConstraint: NSLayoutConstraint? + var bottomLabelToMoleculeConstraint: NSLayoutConstraint? + + public override func addMolecule(_ molecule: UIView) { + insertSubview(molecule, at: 0) + topLabelToMoleculeConstraint?.isActive = false + bottomLabelToMoleculeConstraint?.isActive = false + + molecule.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true + layoutMarginsGuide.rightAnchor.constraint(equalTo: molecule.rightAnchor).isActive = true + + topLabelToMoleculeConstraint = molecule.topAnchor.constraint(equalTo: topLabelsView.bottomAnchor, constant: spaceAboveMolecule) + topLabelToMoleculeConstraint?.isActive = true + bottomLabelToMoleculeConstraint = bottomLabelsView.topAnchor.constraint(equalTo: molecule.bottomAnchor, constant: spaceBelowMolecule) + bottomLabelToMoleculeConstraint?.isActive = true + } + + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + topLeftLabel.updateView(size) + topRightLabel.updateView(size) + bottomLeftLabel.updateView(size) + bottomRightLabel.updateView(size) + } + + public override func setupView() { + super.setupView() + shouldSetupMoleculeFromJSON = true + + guard topLeftLabel.superview == nil else { + return + } + + topRightLabel.textAlignment = .right + bottomRightLabel.textAlignment = .right + + addSubview(topLabelsView) + addSubview(bottomLabelsView) + topLabelsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + topLabelsView.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true + layoutMarginsGuide.rightAnchor.constraint(equalTo: topLabelsView.rightAnchor).isActive = true + + topLabelToMoleculeConstraint = bottomLabelsView.topAnchor.constraint(equalTo: topLabelsView.bottomAnchor, constant: 0) + topLabelToMoleculeConstraint?.isActive = true + bottomLabelToMoleculeConstraint = topLabelToMoleculeConstraint + + bottomLabelsView.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true + layoutMarginsGuide.rightAnchor.constraint(equalTo: bottomLabelsView.rightAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: bottomLabelsView.bottomAnchor).isActive = true + + topLabelsView.addSubview(topLeftLabel) + topLabelsView.addSubview(topRightLabel) + setCompetitionConstraints(topLeftLabel,topRightLabel) + + bottomLabelsView.addSubview(bottomLeftLabel) + bottomLabelsView.addSubview(bottomRightLabel) + setCompetitionConstraints(bottomLeftLabel,bottomRightLabel) + + topLeftLabel.topAnchor.constraint(equalTo: topLabelsView.topAnchor).isActive = true + topLeftLabel.leftAnchor.constraint(equalTo: topLabelsView.leftAnchor).isActive = true + topLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: topLeftLabel.bottomAnchor).isActive = true + topRightLabel.topAnchor.constraint(equalTo: topLabelsView.topAnchor).isActive = true + topLabelsView.rightAnchor.constraint(equalTo: topRightLabel.rightAnchor).isActive = true + topLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: topRightLabel.bottomAnchor).isActive = true + + var constraint = topLabelsView.bottomAnchor.constraint(equalTo: topLeftLabel.bottomAnchor) + constraint.priority = topLeftLabel.contentHuggingPriority(for: .vertical) - 10 + constraint.isActive = true + + constraint = topLabelsView.bottomAnchor.constraint(equalTo: topRightLabel.bottomAnchor) + constraint.priority = topRightLabel.contentHuggingPriority(for: .vertical) - 10 + constraint.isActive = true + + topRightLabel.leftAnchor.constraint(equalTo: topLeftLabel.rightAnchor, constant: 16).isActive = true + + bottomLeftLabel.topAnchor.constraint(equalTo: bottomLabelsView.topAnchor).isActive = true + bottomLeftLabel.leftAnchor.constraint(equalTo: bottomLabelsView.leftAnchor).isActive = true + bottomLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: bottomLeftLabel.bottomAnchor).isActive = true + bottomRightLabel.topAnchor.constraint(equalTo: bottomLabelsView.topAnchor).isActive = true + bottomRightLabel.rightAnchor.constraint(equalTo: bottomLabelsView.rightAnchor).isActive = true + bottomLabelsView.bottomAnchor.constraint(greaterThanOrEqualTo: bottomRightLabel.bottomAnchor).isActive = true + + constraint = bottomLabelsView.bottomAnchor.constraint(equalTo: bottomLeftLabel.bottomAnchor) + constraint.priority = bottomLeftLabel.contentHuggingPriority(for: .vertical) - 10 + constraint.isActive = true + + constraint = bottomLabelsView.bottomAnchor.constraint(equalTo: bottomRightLabel.bottomAnchor) + constraint.priority = bottomRightLabel.contentHuggingPriority(for: .vertical) - 10 + constraint.isActive = true + + bottomRightLabel.leftAnchor.constraint(greaterThanOrEqualTo: bottomLeftLabel.rightAnchor, constant: 16).isActive = true + } + + /// Sets the rules for the labels if they were to collide. The first view will take priority, but the second view has a minimum width when competing. + func setCompetitionConstraints(_ view1: UIView,_ view2: UIView) { + view1.setContentHuggingPriority(UILayoutPriority(rawValue: 700), for: .horizontal) + view2.setContentHuggingPriority(UILayoutPriority(rawValue: 750), for: .horizontal) + view1.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 350), for: .horizontal) + view2.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 300), for: .horizontal) + let width = view2.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.40) + width.priority = UILayoutPriority(rawValue: 400) + width.isActive = true + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + topLeftLabel.setWithJSON(json?.optionalDictionaryForKey("topLeftLabel"), delegateObject: delegateObject, additionalData: additionalData) + topRightLabel.setWithJSON(json?.optionalDictionaryForKey("topRightLabel"), delegateObject: delegateObject, additionalData: additionalData) + bottomLeftLabel.setWithJSON(json?.optionalDictionaryForKey("bottomLeftLabel"), delegateObject: delegateObject, additionalData: additionalData) + bottomRightLabel.setWithJSON(json?.optionalDictionaryForKey("bottomRightLabel"), delegateObject: delegateObject, additionalData: additionalData) + + topLabelToMoleculeConstraint?.constant = (molecule != nil && (topLeftLabel.hasText || topRightLabel.hasText)) ? spaceAboveMolecule : 0 + bottomLabelToMoleculeConstraint?.constant = (molecule != nil && (bottomLeftLabel.hasText || bottomRightLabel.hasText)) ? spaceBelowMolecule : 0 + } + + public override func setAsMolecule() { + super.setAsMolecule() + styleDefault() + } + + public override func reset() { + super.reset() + + styleDefault() + spaceAboveMolecule = 6.0 + spaceBelowMolecule = 6.0 + + molecule?.reset?() + } + + func styleDefault() { + topLeftLabel.styleB1(true) + topRightLabel.styleB1(true) + bottomLeftLabel.styleB3(true) + bottomRightLabel.styleB3(true) + } + + public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 34 + } +} diff --git a/MVMCoreUI/Molecules/HeadlineBody.swift b/MVMCoreUI/Molecules/HeadlineBody.swift index 9eaa602f..d3c37261 100644 --- a/MVMCoreUI/Molecules/HeadlineBody.swift +++ b/MVMCoreUI/Molecules/HeadlineBody.swift @@ -11,6 +11,7 @@ import UIKit open class HeadlineBody: ViewConstrainingView { let headlineLabel = Label.commonLabelH2(true) let messageLabel = Label.commonLabelB2(true) + var spaceBetweenLabelsConstant = PaddingTwo var spaceBetweenLabels: NSLayoutConstraint? var leftConstraintTitle: NSLayoutConstraint? var rightConstraintTitle: NSLayoutConstraint? @@ -44,7 +45,7 @@ open class HeadlineBody: ViewConstrainingView { topPin = headlineLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0) topPin?.isActive = true - spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: PaddingTwo) + spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: spaceBetweenLabelsConstant) spaceBetweenLabels?.isActive = true leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor) @@ -66,7 +67,7 @@ open class HeadlineBody: ViewConstrainingView { // MARK: - Constraining public func setSpacing() { if headlineLabel.hasText && messageLabel.hasText { - spaceBetweenLabels?.constant = PaddingTwo + spaceBetweenLabels?.constant = spaceBetweenLabelsConstant } else { spaceBetweenLabels?.constant = 0 } @@ -96,6 +97,7 @@ open class HeadlineBody: ViewConstrainingView { super.reset() headlineLabel.styleH2(true) messageLabel.styleB2(true) + spaceBetweenLabelsConstant = PaddingTwo } public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Molecules/ImageHeadlineBody.swift b/MVMCoreUI/Molecules/ImageHeadlineBody.swift new file mode 100644 index 00000000..f1e295fe --- /dev/null +++ b/MVMCoreUI/Molecules/ImageHeadlineBody.swift @@ -0,0 +1,85 @@ +// +// ImageHeadlineBody.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 8/1/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class ImageHeadlineBody: ViewConstrainingView { + let headlineBody = HeadlineBody(frame: .zero) + let imageView = MFLoadImageView() + var constraintBetweenImageLabelsConstant: CGFloat = 16 + var constraintBetweenImageLabels: NSLayoutConstraint? + + // MARK: - MFViewProtocol + + open override func setupView() { + 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) + 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) + constraint.priority = UILayoutPriority(rawValue: 200) + constraint.isActive = true + constraint = NSLayoutConstraint.pinViewTop(toSuperview: imageView, useMargins: true, constant: 0) + constraint.priority = UILayoutPriority(rawValue: 200) + constraint.isActive = true + + constraintBetweenImageLabels = headlineBody.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: constraintBetweenImageLabelsConstant) + constraintBetweenImageLabels?.isActive = true + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + imageView.updateView(size) + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) + + let imageJSON = json?.optionalDictionaryForKey("image") + imageView.setWithJSON(imageJSON, delegateObject: delegateObject, additionalData: additionalData) + constraintBetweenImageLabels?.constant = imageJSON != nil ? constraintBetweenImageLabelsConstant : 0 + } + + open override func setAsMolecule() { + super.setAsMolecule() + headlineBody.setAsMolecule() + imageView.setAsMolecule() + } + + open override func reset() { + super.reset() + headlineBody.reset() + headlineBody.headlineLabel.styleB1(true) + headlineBody.spaceBetweenLabelsConstant = 0 + imageView.reset() + } + + public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 95 + } +} diff --git a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m index 16783ca9..6648b513 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m +++ b/MVMCoreUI/Molecules/MVMCoreUIPageControl.m @@ -297,7 +297,9 @@ static CGFloat const IndicatorRectangleHeight = 4; if (selectedIndex != NSNotFound) { self.currentPage = selectedIndex; [self sendActionsForControlEvents:UIControlEventValueChanged]; - self.pagingTouchBlock(self); + if (self.pagingTouchBlock) { + self.pagingTouchBlock(self); + } } } } @@ -351,7 +353,9 @@ static CGFloat const IndicatorRectangleHeight = 4; if ((index < self.numberOfPages && index >= 0) || self.alwaysSendingControlEvent) { [self setCurrentPage:index animated:NO]; [self sendActionsForControlEvents:UIControlEventValueChanged]; - self.pagingTouchBlock(self); + if (self.pagingTouchBlock) { + self.pagingTouchBlock(self); + } } } diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift index 6afec1bc..42a5e5cd 100644 --- a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift @@ -37,6 +37,11 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi } isAccessibilityElement = false contentView.isAccessibilityElement = false + if #available(iOS 11.0, *) { + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false + } // Covers the card when peaking. peakingCover.backgroundColor = .white diff --git a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift index 492e7d4d..c1054e33 100644 --- a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift @@ -80,6 +80,11 @@ import UIKit public func setupView() { selectionStyle = .none + if #available(iOS 11.0, *) { + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false + } } // MARK: - MVMCoreUIMoleculeViewProtocol @@ -98,7 +103,7 @@ import UIKit } // Add the caret if there is an action and it's not declared hidden. - if let _ = json?.optionalDictionaryForKey("actionMap"), json!.boolForKey("hideArrow") { + if let _ = json?.optionalDictionaryForKey("actionMap"), !json!.boolForKey("hideArrow") { addCaretViewAccessory() } else { accessoryView = nil @@ -118,11 +123,6 @@ import UIKit contentView.addSubview(moleculeView) let standardConstraints = (moleculeView as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: standardConstraints).values)) - if standardConstraints { - let constraint = contentView.heightAnchor.constraint(equalToConstant: 80) - constraint.priority = .defaultLow - constraint.isActive = true - } molecule = moleculeView } } else { @@ -147,7 +147,7 @@ import UIKit guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) else { return 80 } - return max(80, height) + return max(2 * PaddingDefaultVerticalSpacing3, height) } public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { diff --git a/MVMCoreUI/Molecules/ProgressBar.swift b/MVMCoreUI/Molecules/ProgressBar.swift index a9d7ad48..5416f0a7 100644 --- a/MVMCoreUI/Molecules/ProgressBar.swift +++ b/MVMCoreUI/Molecules/ProgressBar.swift @@ -8,41 +8,73 @@ import Foundation -public class ProgressBar: UIProgressView { - - var isRounded = Bool() - var thickness : Float = 0.0 { +@objcMembers open class ProgressBar: UIProgressView, MVMCoreUIMoleculeViewProtocol, MVMCoreViewProtocol { + var isRounded = false + var thickness: CGFloat = 8.0 { willSet(newValue) { - heightAnchor.constraint(equalToConstant: CGFloat(newValue)).isActive = true - switch isRounded { - case true: - layer.cornerRadius = CGFloat(newValue/2) - clipsToBounds = true - default: + heightAnchor.constraint(equalToConstant: newValue).isActive = true + if isRounded { + layer.cornerRadius = newValue/2.0 + } else { progressViewStyle = .bar } } } + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + init() { + super.init(frame: .zero) + setupView() + } + + // MARK: - MVMCoreViewProtocol + public func setupView() { + clipsToBounds = true + translatesAutoresizingMaskIntoConstraints = false + reset() + } + + public func updateView(_ size: CGFloat) { + } + + // MARK: - MVMCoreUIMoleculeViewProtocol public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - isRounded = json?.optionalBoolForKey("roundedRect") ?? false - thickness = json?.floatFromStringForKey("thickness") ?? Float(0.0) - let percentage = json?.floatFromStringForKey("percent") - progress = (percentage ?? Float(0.0))/100 - progressTintColor = UIColor.mfBattleshipGrey() - trackTintColor = UIColor.mfLighterGray() - - if let progresscolor = json?.optionalStringForKey("progressColor") { - if !progresscolor.isEmpty { - progressTintColor = UIColor.mfGet(forHex: progresscolor) - } + if let isRounded = json?.optionalBoolForKey("roundedRect") { + self.isRounded = isRounded } - - if let backgroundcolor = json?.optionalStringForKey("backgroundColor") { - if !backgroundcolor.isEmpty { - trackTintColor = UIColor.mfGet(forHex: backgroundcolor) - } + if let thickness = json?.optionalCGFloatForKey("thickness") { + self.thickness = thickness } + if let percentage = json?["percent"] as? Float { + progress = percentage/100.0 + } + if let progressColor = json?.optionalStringForKey("progressColor") { + progressTintColor = UIColor.mfGet(forHex: progressColor) + } + if let backgroundColor = json?.optionalStringForKey("backgroundColor") { + trackTintColor = UIColor.mfGet(forHex: backgroundColor) + } + } + + public func reset() { + isRounded = false + thickness = 8 + progress = 0 + progressTintColor = UIColor.mfCerulean() + trackTintColor = UIColor.mfSilver() + } + + public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 8 } } diff --git a/MVMCoreUI/Molecules/ProgressBarWithLabel.swift b/MVMCoreUI/Molecules/ProgressBarWithLabel.swift deleted file mode 100644 index 9740e1ba..00000000 --- a/MVMCoreUI/Molecules/ProgressBarWithLabel.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// ProgressBarView.swift -// MVMCoreUI -// -// Created by Panth Patel on 5/3/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -@objcMembers public class ProgressBarWithLabel: ViewConstrainingView { - - var progress = ProgressBar() - var topleftlabel = Label() - var toprightlabel = Label() - var bottomleftlabel = Label() - var bottomrightlabel = Label() - - open override func needsToBeConstrained() -> Bool { - return true - } - - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - let progressbarjson = json?.optionalDictionaryForKey("progressbar") - progress.setWithJSON(progressbarjson, delegateObject: delegateObject, additionalData: additionalData) - - let topleftlabeljson = json?.optionalDictionaryForKey("label") - let toprightlabeljson = json?.optionalDictionaryForKey("toprightlabel") - let bottomleftlabeljson = json?.optionalDictionaryForKey("bottomleftlabel") - let bottomrightlabeljson = json?.optionalDictionaryForKey("bottomrightlabel") - - topleftlabel.setWithJSON(topleftlabeljson, delegateObject: delegateObject, additionalData: additionalData) - toprightlabel.setWithJSON(toprightlabeljson, delegateObject: delegateObject, additionalData: additionalData) - bottomleftlabel.setWithJSON(bottomleftlabeljson, delegateObject: delegateObject, additionalData: additionalData) - bottomrightlabel.setWithJSON(bottomrightlabeljson, delegateObject: delegateObject, additionalData: additionalData) - } - - override open func setupView() { - super.setupView() - addSubview(topleftlabel) - addSubview(toprightlabel) - addSubview(bottomleftlabel) - addSubview(bottomrightlabel) - addSubview(progress) - - progress.translatesAutoresizingMaskIntoConstraints = false - - topleftlabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true - topleftlabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true - let topleftwidthconstraint = NSLayoutConstraint(item: topleftlabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.5, constant: 0.0) - topleftwidthconstraint.priority = UILayoutPriority(100) - topleftwidthconstraint.isActive = true - topleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .horizontal) - topleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .vertical) - - NSLayoutConstraint(item: toprightlabel, attribute: .leading, relatedBy: .equal, toItem: topleftlabel, attribute: .trailing, multiplier: 1.0, constant: PaddingTwo).isActive = true - toprightlabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true - toprightlabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true - toprightlabel.textAlignment = .right - toprightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .horizontal) - toprightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .vertical) - - var topconstraint = progress.topAnchor.constraint(equalTo: topleftlabel.bottomAnchor, constant: PaddingTwo) - topconstraint.priority = UILayoutPriority(249) - topconstraint.isActive = true - progress.topAnchor.constraint(greaterThanOrEqualTo: topleftlabel.bottomAnchor, constant: PaddingTwo).isActive = true - topconstraint = progress.topAnchor.constraint(equalTo: toprightlabel.bottomAnchor, constant: PaddingTwo) - topconstraint.priority = UILayoutPriority(249) - topconstraint.isActive = true - progress.topAnchor.constraint(greaterThanOrEqualTo: toprightlabel.bottomAnchor, constant: PaddingTwo).isActive = true - - progress.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true - progress.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true - - bottomleftlabel.topAnchor.constraint(equalTo: progress.bottomAnchor, constant: PaddingTwo).isActive = true - bottomleftlabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true - let bottomleftwidthconstraint = NSLayoutConstraint(item: bottomleftlabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.5, constant: 0.0) - bottomleftwidthconstraint.priority = UILayoutPriority(100) - bottomleftwidthconstraint.isActive = true - bottomleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .horizontal) - bottomleftlabel.setContentHuggingPriority(UILayoutPriority(801), for: .vertical) - - NSLayoutConstraint(item: bottomrightlabel, attribute: .leading, relatedBy: .equal, toItem: bottomleftlabel, attribute: .trailing, multiplier: 1.0, constant: PaddingTwo).isActive = true - bottomrightlabel.topAnchor.constraint(equalTo: progress.bottomAnchor, constant: PaddingTwo).isActive = true - bottomrightlabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true - bottomrightlabel.textAlignment = .right - bottomrightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .horizontal) - bottomrightlabel.setContentHuggingPriority(UILayoutPriority(802), for: .vertical) - - var bottomconstraint = bottomAnchor.constraint(equalTo: bottomleftlabel.bottomAnchor, constant: PaddingTwo) - bottomconstraint.priority = UILayoutPriority(249) - bottomconstraint.isActive = true - bottomAnchor.constraint(greaterThanOrEqualTo: bottomleftlabel.bottomAnchor, constant: PaddingTwo).isActive = true - bottomconstraint = bottomAnchor.constraint(equalTo: bottomrightlabel.bottomAnchor, constant: PaddingTwo) - bottomconstraint.priority = UILayoutPriority(249) - bottomconstraint.isActive = true - bottomAnchor.constraint(greaterThanOrEqualTo: bottomrightlabel.bottomAnchor, constant: PaddingTwo).isActive = true - } -} diff --git a/MVMCoreUI/Molecules/Scroller.swift b/MVMCoreUI/Molecules/Scroller.swift new file mode 100644 index 00000000..cd2949d8 --- /dev/null +++ b/MVMCoreUI/Molecules/Scroller.swift @@ -0,0 +1,44 @@ +// +// Scroller.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 7/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class Scroller: ViewConstrainingView { + public let scrollView = UIScrollView(frame: .zero) + public let contentView = MVMCoreUICommonViewsUtility.commonView() + + override open func setupView() { + super.setupView() + guard scrollView.superview == nil else { + return + } + translatesAutoresizingMaskIntoConstraints = false + scrollView.translatesAutoresizingMaskIntoConstraints = false + addConstrainedView(scrollView) + scrollView.addSubview(contentView) + NSLayoutConstraint.constraintPinSubview(toSuperview: contentView) + let constraint = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0) + constraint.priority = UILayoutPriority(rawValue: 999) + constraint.isActive = true + } + + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { + return + } + if molecule == nil { + if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { + contentView.addSubview(moleculeView) + pinView(toSuperView: moleculeView) + molecule = moleculeView + } + } else { + molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) + } + } +} diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/StandardHeaderView.swift index 0e6c6776..093a2e31 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/StandardHeaderView.swift @@ -15,11 +15,6 @@ public class StandardHeaderView: ViewConstrainingView { open override func updateView(_ size: CGFloat) { super.updateView(size) separatorView?.updateView(size) - if separatorView?.isHidden ?? true { - let margins = MVMCoreUIUtility.getMarginsFor(self) - MVMCoreUIUtility.setMarginsFor(self, leading: margins.left, top: margins.top, trailing: margins.right, bottom: 0) - bottomPin?.constant = 0 - } } public override func setupView() { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 50c18d3b..27112b1a 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -39,7 +39,7 @@ @"textField" : MFTextField.class, @"digitTextField" : MFDigitTextField.class, @"checkbox" : MVMCoreUICheckBox.class, - @"progressBarWithLabel" : ProgressBarWithLabel.class, + @"cornerLabels" : CornerLabels.class, @"progressBar": ProgressBar.class, @"textField": MFTextField.class, @"checkbox": MVMCoreUICheckBox.class, @@ -47,13 +47,16 @@ @"listItem": MoleculeTableViewCell.class, @"switchLineItem": SwitchLineItem.class, @"switch": Switch.class, - @"image": MFLoadImageView.class, @"leftRightLabelView": LeftRightLabelView.class, + @"actionDetailWithImage": ActionDetailWithImage.class, + @"image": MFLoadImageView.class, @"moduleMolecule": ModuleMolecule.class, @"headlineBody": HeadlineBody.class, @"carousel": Carousel.class, @"carouselItem": MoleculeCollectionViewCell.class, @"barsPager": MVMCoreUIPageControl.class, + @"scroller": Scroller.class, + @"imageHeadlineBody": ImageHeadlineBody.class } mutableCopy]; }); return mapping; diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m index 4e33da75..e770ede5 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m @@ -22,7 +22,9 @@ @"textFieldListForm" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[TextFieldListFormViewController class]], @"moleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackTemplate class]], @"centerMoleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackCenteredTemplate class]], - @"moleculeList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]] + @"moleculeList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]], + @"threeLayer" : + [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ThreeLayerTemplate class]] } mutableCopy]; }); return viewControllerMapping; diff --git a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h index 0d027386..fa91e0fc 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h +++ b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h @@ -12,4 +12,8 @@ /// returns a module for the corresponding module name. - (nullable NSDictionary *)getModuleWithName:(nullable NSString *)name; +@optional +/// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. +- (void)moleculeLayoutUpdated:(nonnull UIView *)molecule; + @end diff --git a/MVMCoreUI/Styles/MFStyler.h b/MVMCoreUI/Styles/MFStyler.h index f5632dca..ea2d4afa 100644 --- a/MVMCoreUI/Styles/MFStyler.h +++ b/MVMCoreUI/Styles/MFStyler.h @@ -29,6 +29,7 @@ typedef NS_ENUM(NSUInteger, MFTimeFormatUnit) { extern CGFloat const PaddingDefault; extern CGFloat const PaddingDefaultHorizontalSpacing; extern CGFloat const PaddingDefaultVerticalSpacing; +extern CGFloat const PaddingDefaultVerticalSpacing3; extern CGFloat const PaddingBetweenFields; extern CGFloat const PaddingHorizontalHeadlineWhiteView; extern CGFloat const PaddingHorizontalLarge; diff --git a/MVMCoreUI/Styles/MFStyler.m b/MVMCoreUI/Styles/MFStyler.m index b2fdf2fe..6dda12a1 100644 --- a/MVMCoreUI/Styles/MFStyler.m +++ b/MVMCoreUI/Styles/MFStyler.m @@ -18,6 +18,7 @@ CGFloat const PaddingDefault = 24; CGFloat const PaddingDefaultHorizontalSpacing = 32; CGFloat const PaddingDefaultVerticalSpacing = 32; +CGFloat const PaddingDefaultVerticalSpacing3 = 24; CGFloat const PaddingBetweenFields = 24; CGFloat const PaddingHorizontalHeadlineWhiteView = 60; CGFloat const PaddingHorizontalLarge = 72; @@ -98,7 +99,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; + (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size horizontal:(BOOL)horizontal vertical:(BOOL)vertical { [MVMCoreDispatchUtility performBlockOnMainThread:^{ CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0; - CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing : 0; + CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing3 : 0; [MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:verticalPadding trailing:horizontalPadding bottom:verticalPadding]; }]; } diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/Contents.json new file mode 100644 index 00000000..d7771f8a --- /dev/null +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "closeXBlack.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "closeXBlack@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "closeXBlack@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack.png b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack.png new file mode 100644 index 00000000..5f31bd28 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@2x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@2x.png new file mode 100644 index 00000000..d966cb74 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@2x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@3x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@3x.png new file mode 100644 index 00000000..59847565 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/closeXBlack.imageset/closeXBlack@3x.png differ diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index f3a4f310..abec1b01 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -106,6 +106,17 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { return loadObject?.requestParameters?.modules } + // MARK: - MoleculeDelegateProtocol + open override func moleculeLayoutUpdated(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) { + if let tableView = tableView { + let point = molecule.convert(molecule.bounds.origin, to: tableView) + if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { + tableView.beginUpdates() + tableView.endUpdates() + } + } + } + // MARK: - Convenience /// Returns the (identifier, class) of the molecule for the given map. func getMoleculeInfo(with molecule: [AnyHashable: Any]?) -> (identifier: String, class: AnyClass, molecule: [AnyHashable: Any])? { diff --git a/MVMCoreUI/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Templates/MoleculeStackTemplate.swift index 54b751e0..8515f9a4 100644 --- a/MVMCoreUI/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeStackTemplate.swift @@ -8,7 +8,7 @@ import UIKit -public class MoleculeStackTemplate: ThreeLayerViewController { +open class MoleculeStackTemplate: ThreeLayerViewController { var observer: NSKeyValueObservation? open override var loadObject: MVMCoreLoadObject? { @@ -25,18 +25,18 @@ self?.updateRequiredModules() } } -public override func spaceBetweenTopAndMiddle() -> CGFloat? { +open override func spaceBetweenTopAndMiddle() -> CGFloat? { return 0 } -public override func viewForTop() -> UIView? { +open override func viewForTop() -> UIView? { guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { return nil } return molecule } -public override func viewForMiddle() -> UIView? { +open override func viewForMiddle() -> UIView? { guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("moleculeStack") else { return nil } @@ -46,7 +46,7 @@ stack.setWithJSON(moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDe return stack } -override public func viewForBottom() -> UIView? { +override open func viewForBottom() -> UIView? { guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { return nil } @@ -54,14 +54,14 @@ return molecule } // MARK: - cache handling -public override func pageTypesToListenFor() -> [Any]? { +open override func pageTypesToListenFor() -> [Any]? { guard let pageType = self.pageType else { return super.pageTypesToListenFor() } return [pageType] } -public override func modulesToListenFor() -> [Any]? { +open override func modulesToListenFor() -> [Any]? { return loadObject?.requestParameters?.modules } diff --git a/MVMCoreUI/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Templates/ThreeLayerTemplate.swift new file mode 100644 index 00000000..20475a6b --- /dev/null +++ b/MVMCoreUI/Templates/ThreeLayerTemplate.swift @@ -0,0 +1,52 @@ +// +// ThreeLayerTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 7/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController { + + override open func viewDidLoad() { + super.viewDidLoad() + bottomViewOutsideOfScroll = true + // Do any additional setup after loading the view. + } + + open override func newDataBuildScreen() { + super.newDataBuildScreen() + heightConstraint?.isActive = true + } + + open override func viewForTop() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { + return nil + } + return molecule + } + + open override func viewForMiddle() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("middle"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { + return nil + } + return molecule + } + + override open func viewForBottom() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { + return nil + } + return molecule + } + + open override func spaceBetweenMiddleAndBottom() -> CGFloat? { + return 0 + } + + open override func spaceBetweenTopAndMiddle() -> CGFloat? { + return 0 + } +} diff --git a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m index 487a7b51..41237640 100644 --- a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m @@ -181,7 +181,7 @@ static const CGFloat VertialShadowOffset = 6; + (nonnull MFCustomButton *)addCloseButtonToView:(UIView *)view action:(ButtonTapBlock)actionBlock verticalCentered:(BOOL)verticalCentered { MFCustomButton *button = [[MFCustomButton alloc] initWithFrame:CGRectZero]; button.translatesAutoresizingMaskIntoConstraints = NO; - [button setTitle:@"✕" forState:UIControlStateNormal]; + [button setImage:[MVMCoreUIUtility imageNamed:@"closeXBlack"] forState:UIControlStateNormal]; button.titleLabel.font = [MFStyler fontForHeadlineAlternative]; //accessibility diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.h b/MVMCoreUI/Utility/MVMCoreUIUtility.h index b6698537..9300de90 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.h +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.h @@ -71,8 +71,9 @@ NS_ASSUME_NONNULL_BEGIN // Gets the constraint height to be whatever space is left in the scroll view. + (CGFloat)getVariableConstraintHeight:(CGFloat)currentConstant inScrollView:(nonnull UIScrollView *)scrollView minimumHeight:(CGFloat)minimumHeight; -// Sets the view's frame according to constraint. +/// Sets the view's frame according to constraints. + (void)sizeViewToFit:(nullable UIView *)view; ++ (void)sizeViewToFit:(UIView *)view forWidth:(nullable NSNumber *)width; #pragma mark - Keyboard diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index 8201e999..e8523a61 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -119,7 +119,7 @@ #pragma mark - Sizing + (CGFloat)getWidth { - UIViewController *controller = [MVMCoreUISession sharedGlobal].splitViewController ?: [MVMCoreUISession sharedGlobal].navigationController ?: [UIApplication sharedApplication].keyWindow.rootViewController; + UIViewController *controller = [MVMCoreUISession sharedGlobal].splitViewController ?: [MVMCoreUISession sharedGlobal].navigationController; if (controller) { return CGRectGetWidth(controller.view.bounds); } else { @@ -138,7 +138,7 @@ } + (CGFloat)getHeightOfView:(nonnull UIView *)view forWidth:(nullable NSNumber *)width { - CGFloat floatWidth = (width ? width.floatValue : CGRectGetWidth([MVMCoreNavigationHandler sharedNavigationHandler].navigationController.view.bounds)); + CGFloat floatWidth = (width ? width.floatValue : [MVMCoreUIUtility getWidth]); return [view systemLayoutSizeFittingSize:CGSizeMake(floatWidth, UILayoutFittingCompressedSize.height) withHorizontalFittingPriority:1000 verticalFittingPriority:250].height; } @@ -192,6 +192,13 @@ view.frame = frame; } ++ (void)sizeViewToFit:(UIView *)view forWidth:(nullable NSNumber *)width { + CGFloat height = [MVMCoreUIUtility getHeightOfView:view forWidth:width]; + CGRect frame = view.frame; + frame.size.height = height; + view.frame = frame; +} + #pragma mark - Keyboard + (void)setScrollViewInsetForKeyboardShow:(nonnull NSNotification *)notification scrollView:(nonnull UIScrollView *)scrollView viewController:(nonnull UIViewController *)viewController rectToScrollTo:(nonnull CGRect (^)(void))rectToScrollTo {