diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index b4295986..a0b4ef65 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 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 */; }; D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -195,6 +196,7 @@ 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 = ""; }; D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = ""; }; @@ -648,6 +650,7 @@ D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */, DBC4391C2245232D001AB423 /* LabelWithInternalButton.swift */, DB891E822253FA8500022516 /* Label.swift */, + 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */, 0198F7A02256A80A0066C936 /* MFRadioButton.h */, 0198F7A22256A80A0066C936 /* MFRadioButton.m */, ); @@ -1011,6 +1014,7 @@ 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 */, diff --git a/MVMCoreUI/Atoms/Views/ActionDetailWithImage.swift b/MVMCoreUI/Atoms/Views/ActionDetailWithImage.swift new file mode 100644 index 00000000..4792dbd6 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/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/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index f46916b3..dff48826 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -336,6 +336,7 @@ } - (void)reset { + [super reset]; self.updateViewHorizontalDefaults = NO; self.updateViewVerticalDefaults = NO; if ([self.molecule respondsToSelector:@selector(alignment)]) { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 41513bf0..cd829697 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -46,8 +46,9 @@ @"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, diff --git a/MVMCoreUI/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Templates/MoleculeStackTemplate.swift index 064c910c..d6f78b05 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 @@ public class MoleculeStackTemplate: ThreeLayerViewController { } } - 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 @@ public class MoleculeStackTemplate: ThreeLayerViewController { 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 @@ public class MoleculeStackTemplate: ThreeLayerViewController { } // 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 }