From 3b8fd4167c523137b8465046fceef7159a19933b Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Wed, 1 Apr 2020 19:27:18 +0530 Subject: [PATCH 01/38] initial commit --- MVMCoreUI.xcodeproj/project.pbxproj | 16 +++ .../Views/RadioBoxCollectionViewCell.swift | 87 +++++++++++++ .../Atomic/Atoms/Views/RadioBoxModel.swift | 54 ++++++++ MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 122 ++++++++++++++++++ .../Atomic/Atoms/Views/RadioBoxesModel.swift | 57 ++++++++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 2 + 6 files changed, 338 insertions(+) create mode 100644 MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 03335a4d..df815789 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -173,8 +173,12 @@ AA11A42123F15D7000D7962F /* ListRightVariablePaymentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11A42023F15D7000D7962F /* ListRightVariablePaymentsModel.swift */; }; AAA74A172410C04600080241 /* HeadersH2NoButtonsBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */; }; AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */; }; + BB2C969424331C46006FF80C /* RadioBoxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2C969324331C46006FF80C /* RadioBoxes.swift */; }; + BB2C969624331C74006FF80C /* RadioBoxesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2C969524331C74006FF80C /* RadioBoxesModel.swift */; }; + BB2C969824331F8E006FF80C /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2C969724331F8E006FF80C /* RadioBoxModel.swift */; }; BB47A586241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB47A585241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift */; }; BB47A588241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB47A587241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift */; }; + BB54C519243459280038326C /* RadioBoxCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB54C518243459280038326C /* RadioBoxCollectionViewCell.swift */; }; BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABE242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift */; }; BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */; }; BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */; }; @@ -556,8 +560,12 @@ AA11A42023F15D7000D7962F /* ListRightVariablePaymentsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePaymentsModel.swift; sourceTree = ""; }; AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyText.swift; sourceTree = ""; }; AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyTextModel.swift; sourceTree = ""; }; + BB2C969324331C46006FF80C /* RadioBoxes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxes.swift; sourceTree = ""; }; + BB2C969524331C74006FF80C /* RadioBoxesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxesModel.swift; sourceTree = ""; }; + BB2C969724331F8E006FF80C /* RadioBoxModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = ""; }; BB47A585241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextDividerSubsectionModel.swift; sourceTree = ""; }; BB47A587241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextDividerSubsection.swift; sourceTree = ""; }; + BB54C518243459280038326C /* RadioBoxCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxCollectionViewCell.swift; sourceTree = ""; }; BB6C6ABE242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTallModel.swift; sourceTree = ""; }; BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTall.swift; sourceTree = ""; }; BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShort.swift; sourceTree = ""; }; @@ -1499,6 +1507,10 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + BB2C969324331C46006FF80C /* RadioBoxes.swift */, + BB54C518243459280038326C /* RadioBoxCollectionViewCell.swift */, + BB2C969524331C74006FF80C /* RadioBoxesModel.swift */, + BB2C969724331F8E006FF80C /* RadioBoxModel.swift */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */, 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, @@ -2079,8 +2091,10 @@ D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, AAA74A172410C04600080241 /* HeadersH2NoButtonsBodyText.swift in Sources */, 522679C223FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift in Sources */, + BB2C969424331C46006FF80C /* RadioBoxes.swift in Sources */, 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */, 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */, + BB2C969624331C74006FF80C /* RadioBoxesModel.swift in Sources */, 8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, @@ -2132,6 +2146,7 @@ 011D95A1240453D0000E3791 /* RuleEqualsModel.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 011D95892404249B000E3791 /* FormHolderModelProtocol.swift in Sources */, + BB2C969824331F8E006FF80C /* RadioBoxModel.swift in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, 013F801923FB4A8E00AD8013 /* UIContentMode+Extension.swift in Sources */, 525239C22407BD1000454969 /* ListTwoColumnPriceDetails.swift in Sources */, @@ -2160,6 +2175,7 @@ 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */, D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */, + BB54C519243459280038326C /* RadioBoxCollectionViewCell.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, 014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift new file mode 100644 index 00000000..532ff34f --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift @@ -0,0 +1,87 @@ +// +// RadioBoxCollectionViewCell.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 01/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtocol { + + public let bodyLabel = Label.commonLabelB2(true) + public let lineView = View(frame: .zero) + + + var bottomView = MVMCoreUICommonViewsUtility.commonView() + + public var lineViewHeight: NSLayoutConstraint? + + + open override var isSelected: Bool{ + didSet{ + lineViewHeight?.constant = isSelected ? 4.0 : 0 + bottomView.layer.borderColor = isSelected ? UIColor.mfGet(forHex: "#0000").cgColor:UIColor.mfGet(forHex: "#747676").cgColor + + } + } + public override init(frame: CGRect) { + super.init(frame: .zero) + setupView() + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + public func setupView() { + guard bottomView.superview == nil else { + return + } + isAccessibilityElement = false + contentView.isAccessibilityElement = false + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false + + contentView.addSubview(bottomView) + NSLayoutConstraint.constraintPinSubview(toSuperview: bottomView) + + lineView.translatesAutoresizingMaskIntoConstraints = false + bottomView.addSubview(lineView) + NSLayoutConstraint.constraintPinSubview(lineView, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true) + lineViewHeight = lineView.heightAnchor.constraint(equalToConstant: 0) + lineViewHeight?.isActive = true + + + bodyLabel.translatesAutoresizingMaskIntoConstraints = false + bottomView.addSubview(bodyLabel) + NSLayoutConstraint.constraintPinSubview(bodyLabel, pinTop: false, topConstant:0 , pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 12, pinRight: true, rightConstant: 12) + bodyLabel.topAnchor.constraint(equalTo: lineView.bottomAnchor, constant: 12).isActive = true + bodyLabel.bottomAnchor.constraint(greaterThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true + + bottomView.layer.borderWidth = 1.0 + bottomView.layer.borderColor = UIColor.mfGet(forHex: "#747676").cgColor + + + + } + + + public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let collectionModel = model as? RadioBoxModel else { return } + + if let backgroundColor = collectionModel.backgroundColor { + self.bottomView.backgroundColor = backgroundColor.uiColor + } +// self.bottomView.backgroundColor = .red + lineView.backgroundColor = collectionModel.selectedAccentColor?.uiColor + bodyLabel.text = collectionModel.text + + + + } + + +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift new file mode 100644 index 00000000..21adcb13 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -0,0 +1,54 @@ +// +// RadioBoxModel.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 31/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers public class RadioBoxModel: MoleculeModelProtocol { + public static var identifier: String = "radioBox" + public var text: String + public var backgroundColor: Color? = Color(uiColor: .white) + public var selectedAccentColor: Color? = Color(uiColor: .red) + public var selected: Bool? = false + public var fieldValue: String? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case text + case selectedAccentColor + case backgroundColor + case selected + case fieldValue + + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + text = try typeContainer.decode(String.self, forKey: .text) + + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) { + selectedAccentColor = color + } + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { + backgroundColor = color + } + if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { + selected = isSelected + } + fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) + + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(selectedAccentColor, forKey: .selectedAccentColor) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(selected, forKey: .selected) + try container.encodeIfPresent(fieldValue, forKey: .fieldValue) + + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift new file mode 100644 index 00000000..9cc6ae5f --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -0,0 +1,122 @@ +// +// RadioBoxes.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 31/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +open class RadioBoxes: View { + + public let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + + /// The models for the molecules. + var molecules: [MoleculeModelProtocol]? + + + /// The height of the carousel. Default is 300. + public var collectionViewHeight: NSLayoutConstraint? + + public var delegateObject: MVMCoreUIDelegateObject? + + + + // MARK: - MVMCoreViewProtocol + open override func setupView() { + super.setupView() + guard collectionView.superview == nil else { + return + } + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.dataSource = self + collectionView.delegate = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = .clear + collectionView.isAccessibilityElement = false + addSubview(collectionView) + + collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) + collectionViewHeight?.isActive = true + NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) + + } + + + + // MARK: - MoleculeViewProtocol + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + self.delegateObject = delegateObject + super.set(with: model, delegateObject, additionalData) + guard let radioBoxesModel = model as? RadioBoxesModel else { return } + backgroundColor = .white + + registerCells() + setupLayout(with: radioBoxesModel) + prepareMolecules(with: radioBoxesModel) + collectionView.reloadData() + } + + // MARK: - JSON Setters + /// Updates the layout being used + + func setupLayout(with carouselModel: RadioBoxesModel?) { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 10 + layout.minimumInteritemSpacing = 10 + collectionView.collectionViewLayout = layout + } + + func prepareMolecules(with radioBoxesModel: RadioBoxesModel?) { + guard let newMolecules = radioBoxesModel?.boxes else { + molecules = nil + return + } + molecules = newMolecules + collectionView.reloadData() + + } + + + /// Registers the cells with the collection view + func registerCells() { + + collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell") + } + + + // MARK: - Convenience + /// Returns the (identifier, class) of the molecule for the given map. + func getMoleculeInfo(with molecule: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? { + guard let className = MoleculeObjectMapping.shared()?.getMoleculeClass(molecule) else { + return nil + } + return (className.nameForReuse(with: molecule, delegateObject) ?? molecule.moleculeName, className, molecule) + } + + +} +extension RadioBoxes: UICollectionViewDelegateFlowLayout { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let itemWidth = (collectionView.bounds.width - 30)/2 + return CGSize(width: itemWidth, height: 64) + } + + +} + +extension RadioBoxes: UICollectionViewDataSource { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return molecules?.count ?? 0 + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let molecule = molecules?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { + return UICollectionViewCell() + } + cell.set(with: molecule, delegateObject, nil) + (cell as? MVMCoreViewProtocol)?.updateView(collectionView.bounds.width) + return cell + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift new file mode 100644 index 00000000..80425bde --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift @@ -0,0 +1,57 @@ +// +// RadioBoxesModel.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 31/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers public class RadioBoxesModel: MoleculeModelProtocol { + public static var identifier: String = "radioBoxes" + public var backgroundColor: Color? = Color(uiColor: .white) + public var selectedAccentColor: Color? = Color(uiColor: .red) + public var enabled: Bool? = true + public var boxes: [RadioBoxModel] + public var fieldKey: String? + public var groupName: String? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case selectedAccentColor + case backgroundColor + case enabled + case boxes + case fieldKey + case groupName + + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) { + selectedAccentColor = color + } + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { + backgroundColor = color + } + if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + enabled = isEnabled + } + boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) + + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(selectedAccentColor, forKey: .selectedAccentColor) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(enabled, forKey: .enabled) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(groupName, forKey: .groupName) + + } +} diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index bfa6db01..f0ea8a63 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -67,6 +67,8 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: DateDropdownEntryField.self, viewModelClass: DateDropdownEntryFieldModel.self) // Other Atoms + MoleculeObjectMapping.shared()?.register(viewClass: RadioBoxes.self, viewModelClass: RadioBoxesModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MultiProgress.self, viewModelClass: MultiProgressBarModel.self) MoleculeObjectMapping.shared()?.register(viewClass: CaretView.self, viewModelClass: CaretViewModel.self) From 96115b7a5da4f8ef79d07ff76c0f8981a18629d1 Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Fri, 3 Apr 2020 12:45:32 +0530 Subject: [PATCH 02/38] border changes updated --- .../Views/RadioBoxCollectionViewCell.swift | 82 +++++++++++++------ .../Atomic/Atoms/Views/RadioBoxModel.swift | 8 +- MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 73 ++++++++--------- .../Atomic/Atoms/Views/RadioBoxesModel.swift | 1 - 4 files changed, 96 insertions(+), 68 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift index 532ff34f..c709cf4b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift @@ -8,28 +8,37 @@ import Foundation open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtocol { - + public let bodyLabel = Label.commonLabelB2(true) public let lineView = View(frame: .zero) - - var bottomView = MVMCoreUICommonViewsUtility.commonView() - public var lineViewHeight: NSLayoutConstraint? + public var fieldValue: String? open override var isSelected: Bool{ didSet{ - lineViewHeight?.constant = isSelected ? 4.0 : 0 - bottomView.layer.borderColor = isSelected ? UIColor.mfGet(forHex: "#0000").cgColor:UIColor.mfGet(forHex: "#747676").cgColor - + self.lineViewHeight?.constant = self.isSelected ? 4.0 : 0 + self.bottomView.layer.sublayers?.filter({$0.name == "border"}).forEach({$0.removeFromSuperlayer()}) + if(self.isSelected){ + self.bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) + self.bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) + self.bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) + } + else { + self.addBordertoView() + } } } public override init(frame: CGRect) { super.init(frame: .zero) setupView() } - + open override func layoutSubviews() { + super.layoutSubviews() + addBordertoView() + + } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() @@ -44,7 +53,6 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco insetsLayoutMarginsFromSafeArea = false contentView.insetsLayoutMarginsFromSafeArea = false contentView.preservesSuperviewLayoutMargins = false - contentView.addSubview(bottomView) NSLayoutConstraint.constraintPinSubview(toSuperview: bottomView) @@ -54,34 +62,58 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco lineViewHeight = lineView.heightAnchor.constraint(equalToConstant: 0) lineViewHeight?.isActive = true - bodyLabel.translatesAutoresizingMaskIntoConstraints = false + bodyLabel.numberOfLines = 0 bottomView.addSubview(bodyLabel) NSLayoutConstraint.constraintPinSubview(bodyLabel, pinTop: false, topConstant:0 , pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 12, pinRight: true, rightConstant: 12) bodyLabel.topAnchor.constraint(equalTo: lineView.bottomAnchor, constant: 12).isActive = true - bodyLabel.bottomAnchor.constraint(greaterThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true - - bottomView.layer.borderWidth = 1.0 - bottomView.layer.borderColor = UIColor.mfGet(forHex: "#747676").cgColor - - + bodyLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true } - - + private func addBordertoView(){ + bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) + bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) + bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) + bottomView.layer.addBorder(edge: .top, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) + } + + public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let collectionModel = model as? RadioBoxModel else { return } - if let backgroundColor = collectionModel.backgroundColor { self.bottomView.backgroundColor = backgroundColor.uiColor } -// self.bottomView.backgroundColor = .red lineView.backgroundColor = collectionModel.selectedAccentColor?.uiColor bodyLabel.text = collectionModel.text - - - + isSelected = collectionModel.selected ?? false + fieldValue = collectionModel.fieldValue } - - +} +extension CALayer { + + func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) { + + let border = CALayer() + border.name = "border" + switch edge { + case UIRectEdge.top: + border.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: thickness) + break + case UIRectEdge.bottom: + border.frame = CGRect(x: 0, y: self.frame.height - thickness, width: self.frame.width, height: thickness) + break + case UIRectEdge.left: + border.frame = CGRect(x: 0, y: 0, width: thickness, height: self.frame.height) + break + case UIRectEdge.right: + border.frame = CGRect(x: self.frame.width - thickness, y: 0, width: thickness, height: self.frame.height) + break + default: + break + } + + border.backgroundColor = color.cgColor; + self.addSublayer(border) + } + } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift index 21adcb13..0e72f6e5 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -11,8 +11,9 @@ import Foundation public static var identifier: String = "radioBox" public var text: String public var backgroundColor: Color? = Color(uiColor: .white) - public var selectedAccentColor: Color? = Color(uiColor: .red) + public var selectedAccentColor: Color? = try? Color(colorString: "#D52B1E") public var selected: Bool? = false + public var strikethrough: Bool? = false public var fieldValue: String? private enum CodingKeys: String, CodingKey { @@ -21,6 +22,7 @@ import Foundation case selectedAccentColor case backgroundColor case selected + case strikethrough case fieldValue } @@ -38,6 +40,9 @@ import Foundation if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { selected = isSelected } + if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { + strikethrough = isStrikeTrough + } fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) } @@ -48,6 +53,7 @@ import Foundation try container.encode(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(selected, forKey: .selected) + try container.encodeIfPresent(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift index 9cc6ae5f..39f03c40 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -7,21 +7,23 @@ // import Foundation -open class RadioBoxes: View { +open class RadioBoxes: View { + public let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) /// The models for the molecules. - var molecules: [MoleculeModelProtocol]? + var boxes: [RadioBoxModel]? + public var fieldKey: String? + public var groupName: String? + public var enabled: Bool? - - /// The height of the carousel. Default is 300. public var collectionViewHeight: NSLayoutConstraint? - - public var delegateObject: MVMCoreUIDelegateObject? + private let boxWidth: Double = 151.0 + private let boxHeight: Double = 64.0 + private let itemSpacing: Double = 10.0 + private let leadingSpacing: Double = 0 - - // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() @@ -35,22 +37,21 @@ open class RadioBoxes: View { collectionView.backgroundColor = .clear collectionView.isAccessibilityElement = false addSubview(collectionView) - + NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) collectionViewHeight?.isActive = true - NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) - + } - // MARK: - MoleculeViewProtocol public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - self.delegateObject = delegateObject super.set(with: model, delegateObject, additionalData) guard let radioBoxesModel = model as? RadioBoxesModel else { return } - backgroundColor = .white - + backgroundColor = radioBoxesModel.backgroundColor?.uiColor + fieldKey = radioBoxesModel.fieldKey + groupName = radioBoxesModel.groupName + enabled = radioBoxesModel.enabled registerCells() setupLayout(with: radioBoxesModel) prepareMolecules(with: radioBoxesModel) @@ -59,64 +60,54 @@ open class RadioBoxes: View { // MARK: - JSON Setters /// Updates the layout being used - + func setupLayout(with carouselModel: RadioBoxesModel?) { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .vertical + layout.sectionInset = UIEdgeInsets.init(top: CGFloat(leadingSpacing), left: CGFloat(leadingSpacing), bottom: CGFloat(leadingSpacing), right: CGFloat(leadingSpacing)) layout.minimumLineSpacing = 10 layout.minimumInteritemSpacing = 10 collectionView.collectionViewLayout = layout } - + func prepareMolecules(with radioBoxesModel: RadioBoxesModel?) { guard let newMolecules = radioBoxesModel?.boxes else { - molecules = nil + boxes = nil return } - molecules = newMolecules - collectionView.reloadData() - + boxes = newMolecules + let height = Double(round(Double((boxes?.count ?? Int(0.0)))/2.0))*(boxHeight+10.0) + collectionViewHeight?.constant = CGFloat(height) + collectionViewHeight?.isActive = true } - - + /// Registers the cells with the collection view func registerCells() { collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell") } - - - // MARK: - Convenience - /// Returns the (identifier, class) of the molecule for the given map. - func getMoleculeInfo(with molecule: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? { - guard let className = MoleculeObjectMapping.shared()?.getMoleculeClass(molecule) else { - return nil - } - return (className.nameForReuse(with: molecule, delegateObject) ?? molecule.moleculeName, className, molecule) - } } extension RadioBoxes: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth = (collectionView.bounds.width - 30)/2 - return CGSize(width: itemWidth, height: 64) + let itemWidth = (Double(collectionView.bounds.width) - itemSpacing)/2 + return CGSize(width: CGFloat(150.5), height: CGFloat(boxHeight)) } - - } extension RadioBoxes: UICollectionViewDataSource { open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return molecules?.count ?? 0 + return boxes?.count ?? 0 } open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let molecule = molecules?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { - return UICollectionViewCell() + guard let molecule = boxes?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { + return UICollectionViewCell() } - cell.set(with: molecule, delegateObject, nil) + cell.set(with: molecule, nil, nil) (cell as? MVMCoreViewProtocol)?.updateView(collectionView.bounds.width) + cell.layoutIfNeeded() return cell } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift index 80425bde..ab2b4728 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift @@ -24,7 +24,6 @@ import Foundation case boxes case fieldKey case groupName - } required public init(from decoder: Decoder) throws { From ade0e043a4822cb3ca28cc8087cc09d5c5a1fbfb Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Tue, 7 Apr 2020 07:51:44 +0530 Subject: [PATCH 03/38] fixed box sizes issues --- .../Views/RadioBoxCollectionViewCell.swift | 54 ++++++++++++++++--- MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 18 +++++-- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift index c709cf4b..f29c6bb1 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift @@ -14,11 +14,14 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco var bottomView = MVMCoreUICommonViewsUtility.commonView() public var lineViewHeight: NSLayoutConstraint? public var fieldValue: String? - + open override var isSelected: Bool{ didSet{ self.lineViewHeight?.constant = self.isSelected ? 4.0 : 0 + UIView.animate(withDuration: 0.5) { + self.layoutIfNeeded() + } self.bottomView.layer.sublayers?.filter({$0.name == "border"}).forEach({$0.removeFromSuperlayer()}) if(self.isSelected){ self.bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) @@ -30,15 +33,23 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco } } } + var isOutofStock: Bool = false{ + didSet{ + DispatchQueue.main.async { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.bottomView.layer.sublayers?.filter({$0.name == "outofstock"}).forEach({$0.removeFromSuperlayer()}) + if(strongSelf.isOutofStock){ + strongSelf.addOutofstockLine() + } + } + } + } public override init(frame: CGRect) { super.init(frame: .zero) setupView() } - open override func layoutSubviews() { - super.layoutSubviews() - addBordertoView() - - } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() @@ -53,6 +64,7 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco insetsLayoutMarginsFromSafeArea = false contentView.insetsLayoutMarginsFromSafeArea = false contentView.preservesSuperviewLayoutMargins = false + bottomView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(bottomView) NSLayoutConstraint.constraintPinSubview(toSuperview: bottomView) @@ -70,25 +82,51 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco bodyLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true } + public func updateView(_ size: CGFloat) { + self.setNeedsLayout() + self.bottomView.setNeedsLayout() + self.bottomView.layer.sublayers?.filter({$0.name == "border"}).forEach({$0.removeFromSuperlayer()}) + DispatchQueue.main.async { [weak self] in + self?.addBordertoView() + } + + } + + + private func addBordertoView(){ bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) bottomView.layer.addBorder(edge: .top, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) } - + private func addOutofstockLine(){ + + let path = UIBezierPath() + path.move(to: CGPoint(x: 0, y: bottomView.bounds.height)) + path.addLine(to: CGPoint(x: bottomView.bounds.width, y: 0)) + + let shapeLayer = CAShapeLayer() + shapeLayer.name = "outofstock" + shapeLayer.path = path.cgPath + shapeLayer.strokeColor = UIColor.darkGray.cgColor + shapeLayer.lineWidth = 0.5 + bottomView.layer.addSublayer(shapeLayer) + } public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let collectionModel = model as? RadioBoxModel else { return } if let backgroundColor = collectionModel.backgroundColor { - self.bottomView.backgroundColor = backgroundColor.uiColor + bottomView.backgroundColor = backgroundColor.uiColor } lineView.backgroundColor = collectionModel.selectedAccentColor?.uiColor bodyLabel.text = collectionModel.text isSelected = collectionModel.selected ?? false fieldValue = collectionModel.fieldValue + isOutofStock = collectionModel.strikethrough ?? false } } + extension CALayer { func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) { diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift index 39f03c40..7dcd2acc 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -57,11 +57,23 @@ open class RadioBoxes: View { prepareMolecules(with: radioBoxesModel) collectionView.reloadData() } + @objc override open func updateView(_ size: CGFloat) { + + collectionView.collectionViewLayout.invalidateLayout() + + DispatchQueue.main.async { [weak self] in + self?.setNeedsDisplay() + self?.collectionView.layoutIfNeeded() + self?.collectionView.reloadData() + + } + } + // MARK: - JSON Setters /// Updates the layout being used - func setupLayout(with carouselModel: RadioBoxesModel?) { + func setupLayout(with radioBoxesModel: RadioBoxesModel?) { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .vertical layout.sectionInset = UIEdgeInsets.init(top: CGFloat(leadingSpacing), left: CGFloat(leadingSpacing), bottom: CGFloat(leadingSpacing), right: CGFloat(leadingSpacing)) @@ -92,7 +104,7 @@ open class RadioBoxes: View { extension RadioBoxes: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let itemWidth = (Double(collectionView.bounds.width) - itemSpacing)/2 - return CGSize(width: CGFloat(150.5), height: CGFloat(boxHeight)) + return CGSize(width: CGFloat(itemWidth), height: CGFloat(boxHeight)) } } @@ -106,7 +118,7 @@ extension RadioBoxes: UICollectionViewDataSource { return UICollectionViewCell() } cell.set(with: molecule, nil, nil) - (cell as? MVMCoreViewProtocol)?.updateView(collectionView.bounds.width) + cell.updateView(collectionView.bounds.width) cell.layoutIfNeeded() return cell } From ced8dd54e194f57e1cfae378c97d0ef7c20939df Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Tue, 7 Apr 2020 15:30:42 +0530 Subject: [PATCH 04/38] file paths added --- MVMCoreUI.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index c0a56ebc..65c8b408 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -192,6 +192,10 @@ BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */; }; BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */; }; BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */; }; + BBA326A9243C85550011D1F6 /* RadioBoxesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA326A5243C85540011D1F6 /* RadioBoxesModel.swift */; }; + BBA326AA243C85550011D1F6 /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA326A6243C85540011D1F6 /* RadioBoxModel.swift */; }; + BBA326AB243C85550011D1F6 /* RadioBoxCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA326A7243C85540011D1F6 /* RadioBoxCollectionViewCell.swift */; }; + BBA326AC243C85550011D1F6 /* RadioBoxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA326A8243C85550011D1F6 /* RadioBoxes.swift */; }; C003506123AA94CD00B6AC29 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C003506023AA94CD00B6AC29 /* Button.swift */; }; C07065C42395677300FBF997 /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07065C32395677300FBF997 /* Link.swift */; }; C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */; }; @@ -590,6 +594,10 @@ BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTall.swift; sourceTree = ""; }; BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShort.swift; sourceTree = ""; }; BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShortModel.swift; sourceTree = ""; }; + BBA326A5243C85540011D1F6 /* RadioBoxesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxesModel.swift; sourceTree = ""; }; + BBA326A6243C85540011D1F6 /* RadioBoxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = ""; }; + BBA326A7243C85540011D1F6 /* RadioBoxCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxCollectionViewCell.swift; sourceTree = ""; }; + BBA326A8243C85550011D1F6 /* RadioBoxes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxes.swift; sourceTree = ""; }; C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; C07065C32395677300FBF997 /* Link.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Link.swift; sourceTree = ""; }; C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnOrderedListModel.swift; sourceTree = ""; }; @@ -1566,6 +1574,10 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + BBA326A5243C85540011D1F6 /* RadioBoxesModel.swift */, + BBA326A8243C85550011D1F6 /* RadioBoxes.swift */, + BBA326A6243C85540011D1F6 /* RadioBoxModel.swift */, + BBA326A7243C85540011D1F6 /* RadioBoxCollectionViewCell.swift */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */, 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, @@ -1986,6 +1998,7 @@ D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, 011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */, D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */, + BBA326AC243C85550011D1F6 /* RadioBoxes.swift in Sources */, 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, 942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */, @@ -2038,6 +2051,7 @@ D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */, 8D084AD22410BF7600951227 /* ListOneColumnFullWidthTextBodyText.swift in Sources */, + BBA326AB243C85550011D1F6 /* RadioBoxCollectionViewCell.swift in Sources */, 94C0150C2421564A005811A9 /* ActionCollapseNotificationModel.swift in Sources */, 014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, @@ -2085,6 +2099,7 @@ D2B18B812360945C00A9AEDC /* View.swift in Sources */, C6FA7D5423C77A4A00A3614A /* NumberedList.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, + BBA326A9243C85550011D1F6 /* RadioBoxesModel.swift in Sources */, 525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */, 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */, 94FB966323D797DA003D482B /* MFTextButton.m in Sources */, @@ -2243,6 +2258,7 @@ D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, 014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, + BBA326AA243C85550011D1F6 /* RadioBoxModel.swift in Sources */, 011D959D2404536F000E3791 /* RuleAnyValueChangedModel.swift in Sources */, D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */, BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */, From f251b550047d6085754a5868503471e90484efa0 Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Tue, 7 Apr 2020 19:15:10 +0530 Subject: [PATCH 05/38] alignment changes --- .../Views/RadioBoxCollectionViewCell.swift | 39 ++++++++----------- .../Atomic/Atoms/Views/RadioBoxModel.swift | 16 ++++---- MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 14 +------ .../Atomic/Atoms/Views/RadioBoxesModel.swift | 10 +---- 4 files changed, 29 insertions(+), 50 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift index f29c6bb1..67068c85 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift @@ -15,7 +15,6 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco public var lineViewHeight: NSLayoutConstraint? public var fieldValue: String? - open override var isSelected: Bool{ didSet{ self.lineViewHeight?.constant = self.isSelected ? 4.0 : 0 @@ -33,23 +32,24 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco } } } - var isOutofStock: Bool = false{ + + var isOutOfStock: Bool = false { didSet{ DispatchQueue.main.async { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.bottomView.layer.sublayers?.filter({$0.name == "outofstock"}).forEach({$0.removeFromSuperlayer()}) - if(strongSelf.isOutofStock){ - strongSelf.addOutofstockLine() + guard let self = self else { return } + self.bottomView.layer.sublayers?.filter({$0.name == "outofstock"}).forEach({$0.removeFromSuperlayer()}) + if(self.isOutOfStock) { + self.addOutOfStockLine() } } } } + public override init(frame: CGRect) { super.init(frame: .zero) setupView() } + public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() @@ -68,20 +68,18 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco contentView.addSubview(bottomView) NSLayoutConstraint.constraintPinSubview(toSuperview: bottomView) - lineView.translatesAutoresizingMaskIntoConstraints = false bottomView.addSubview(lineView) NSLayoutConstraint.constraintPinSubview(lineView, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true) lineViewHeight = lineView.heightAnchor.constraint(equalToConstant: 0) lineViewHeight?.isActive = true - bodyLabel.translatesAutoresizingMaskIntoConstraints = false - bodyLabel.numberOfLines = 0 + bodyLabel.numberOfLines = 1 bottomView.addSubview(bodyLabel) NSLayoutConstraint.constraintPinSubview(bodyLabel, pinTop: false, topConstant:0 , pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 12, pinRight: true, rightConstant: 12) bodyLabel.topAnchor.constraint(equalTo: lineView.bottomAnchor, constant: 12).isActive = true bodyLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true - } + public func updateView(_ size: CGFloat) { self.setNeedsLayout() self.bottomView.setNeedsLayout() @@ -89,23 +87,19 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco DispatchQueue.main.async { [weak self] in self?.addBordertoView() } - } - - - + private func addBordertoView(){ bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) bottomView.layer.addBorder(edge: .top, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) } - private func addOutofstockLine(){ - + + private func addOutOfStockLine(){ let path = UIBezierPath() path.move(to: CGPoint(x: 0, y: bottomView.bounds.height)) path.addLine(to: CGPoint(x: bottomView.bounds.width, y: 0)) - let shapeLayer = CAShapeLayer() shapeLayer.name = "outofstock" shapeLayer.path = path.cgPath @@ -119,11 +113,13 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco if let backgroundColor = collectionModel.backgroundColor { bottomView.backgroundColor = backgroundColor.uiColor } + self.isUserInteractionEnabled = collectionModel.enabled lineView.backgroundColor = collectionModel.selectedAccentColor?.uiColor bodyLabel.text = collectionModel.text - isSelected = collectionModel.selected ?? false + isSelected = collectionModel.selected fieldValue = collectionModel.fieldValue - isOutofStock = collectionModel.strikethrough ?? false + isOutOfStock = collectionModel.strikethrough + } } @@ -153,5 +149,4 @@ extension CALayer { border.backgroundColor = color.cgColor; self.addSublayer(border) } - } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift index 0e72f6e5..31f58e53 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -12,8 +12,9 @@ import Foundation public var text: String public var backgroundColor: Color? = Color(uiColor: .white) public var selectedAccentColor: Color? = try? Color(colorString: "#D52B1E") - public var selected: Bool? = false - public var strikethrough: Bool? = false + public var selected: Bool = false + public var enabled: Bool = true + public var strikethrough: Bool = false public var fieldValue: String? private enum CodingKeys: String, CodingKey { @@ -22,15 +23,14 @@ import Foundation case selectedAccentColor case backgroundColor case selected + case enabled case strikethrough case fieldValue - } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) text = try typeContainer.decode(String.self, forKey: .text) - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) { selectedAccentColor = color } @@ -40,21 +40,23 @@ import Foundation if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { selected = isSelected } + if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + enabled = isEnabled + } if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { strikethrough = isStrikeTrough } fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) - } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) - try container.encode(selectedAccentColor, forKey: .selectedAccentColor) + try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(selected, forKey: .selected) + try container.encodeIfPresent(enabled, forKey: .enabled) try container.encodeIfPresent(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) - } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift index 7dcd2acc..90a0d684 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -16,8 +16,6 @@ open class RadioBoxes: View { var boxes: [RadioBoxModel]? public var fieldKey: String? public var groupName: String? - public var enabled: Bool? - public var collectionViewHeight: NSLayoutConstraint? private let boxWidth: Double = 151.0 private let boxHeight: Double = 64.0 @@ -40,10 +38,8 @@ open class RadioBoxes: View { NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) collectionViewHeight?.isActive = true - } - // MARK: - MoleculeViewProtocol public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) @@ -51,25 +47,21 @@ open class RadioBoxes: View { backgroundColor = radioBoxesModel.backgroundColor?.uiColor fieldKey = radioBoxesModel.fieldKey groupName = radioBoxesModel.groupName - enabled = radioBoxesModel.enabled registerCells() setupLayout(with: radioBoxesModel) prepareMolecules(with: radioBoxesModel) collectionView.reloadData() } + @objc override open func updateView(_ size: CGFloat) { - collectionView.collectionViewLayout.invalidateLayout() - DispatchQueue.main.async { [weak self] in self?.setNeedsDisplay() self?.collectionView.layoutIfNeeded() self?.collectionView.reloadData() - } } - // MARK: - JSON Setters /// Updates the layout being used @@ -95,12 +87,10 @@ open class RadioBoxes: View { /// Registers the cells with the collection view func registerCells() { - collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell") } - - } + extension RadioBoxes: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let itemWidth = (Double(collectionView.bounds.width) - itemSpacing)/2 diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift index ab2b4728..4e690ffa 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift @@ -11,7 +11,6 @@ import Foundation public static var identifier: String = "radioBoxes" public var backgroundColor: Color? = Color(uiColor: .white) public var selectedAccentColor: Color? = Color(uiColor: .red) - public var enabled: Bool? = true public var boxes: [RadioBoxModel] public var fieldKey: String? public var groupName: String? @@ -20,7 +19,6 @@ import Foundation case moleculeName case selectedAccentColor case backgroundColor - case enabled case boxes case fieldKey case groupName @@ -34,23 +32,17 @@ import Foundation if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { backgroundColor = color } - if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - enabled = isEnabled - } boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) - } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) - try container.encode(selectedAccentColor, forKey: .selectedAccentColor) + try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(enabled, forKey: .enabled) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) - } } From b0be1dbce3de6a9f8f0e979dfd9ebd79619c4a9d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 7 Apr 2020 10:08:07 -0400 Subject: [PATCH 06/38] Collection changes --- MVMCoreUI.xcodeproj/project.pbxproj | 32 +- .../Molecules/Items/CarouselItemModel.swift | 12 +- .../Items/CollectionItemModelProtocol.swift | 13 + .../Items/MoleculeCollectionItemModel.swift | 66 ++++ .../Items/MoleculeCollectionViewCell.swift | 133 ++------ MVMCoreUI/Atomic/Organisms/Carousel.swift | 30 +- .../Atomic/Organisms/CarouselModel.swift | 14 +- .../CarouselPagingModelProtocol.swift | 2 +- .../Atomic/Templates/CollectionTemplate.swift | 15 + .../Templates/CollectionTemplateModel.swift | 67 ++++ .../Atomic/Templates/ThreeLayerTemplate.swift | 4 - .../BaseClasses/CollectionViewCell.swift | 77 +++++ .../ThreeLayerCollectionViewController.swift | 289 ++++++++++++++++++ .../MVMCoreUIViewControllerMappingObject.m | 3 +- 14 files changed, 613 insertions(+), 144 deletions(-) create mode 100644 MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift create mode 100644 MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift create mode 100644 MVMCoreUI/Atomic/Templates/CollectionTemplate.swift create mode 100644 MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift create mode 100644 MVMCoreUI/BaseClasses/CollectionViewCell.swift create mode 100644 MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index c0a56ebc..e1310f0d 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -210,6 +210,10 @@ D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; + D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */; }; + D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */; }; + D21B7F75243BAC8900051ABF /* CarouselItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F74243BAC8900051ABF /* CarouselItem.swift */; }; + D21B7F77243BB70700051ABF /* MoleculeCollectionItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F76243BB70700051ABF /* MoleculeCollectionItemModel.swift */; }; D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */; }; D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479892314445E003FCCF9 /* LabelToggle.swift */; }; D224798C231450C8003FCCF9 /* HeadlineBodyToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224798B231450C8003FCCF9 /* HeadlineBodyToggle.swift */; }; @@ -242,6 +246,9 @@ D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */; }; + D264FA8E243BCD9A00D98315 /* CollectionTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */; }; + D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */; }; D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; @@ -608,6 +615,10 @@ D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; + D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = ""; }; + D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemModelProtocol.swift; sourceTree = ""; }; + D21B7F74243BAC8900051ABF /* CarouselItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItem.swift; sourceTree = ""; }; + D21B7F76243BB70700051ABF /* MoleculeCollectionItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeCollectionItemModel.swift; sourceTree = ""; }; D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraintAxis+Extension.swift"; sourceTree = ""; }; D22479892314445E003FCCF9 /* LabelToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelToggle.swift; sourceTree = ""; }; D224798B231450C8003FCCF9 /* HeadlineBodyToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyToggle.swift; sourceTree = ""; }; @@ -640,6 +651,9 @@ D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPageControl.h; sourceTree = ""; }; D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; + D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplateModel.swift; sourceTree = ""; }; + D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplate.swift; sourceTree = ""; }; + D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCollectionViewController.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; @@ -1236,8 +1250,6 @@ D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */, 01EB368923609801006832FA /* MoleculeListItemModel.swift */, 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, - 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */, - D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */, @@ -1249,6 +1261,11 @@ D260105E23D0BFFC00764D80 /* StackItem.swift */, 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */, D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */, + D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */, + D21B7F76243BB70700051ABF /* MoleculeCollectionItemModel.swift */, + D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, + 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */, + D21B7F74243BAC8900051ABF /* CarouselItem.swift */, ); path = Items; sourceTree = ""; @@ -1403,6 +1420,8 @@ 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */, 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, + D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */, + D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */, ); path = Templates; sourceTree = ""; @@ -1458,6 +1477,7 @@ D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */, D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */, D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */, + D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */, D2C521A823EDE79E00CA2634 /* ViewController.swift */, D2A92881241AAB67004E01C6 /* ScrollingViewController.swift */, D2A92883241ACB25004E01C6 /* ProgrammaticScrollViewController.swift */, @@ -1757,6 +1777,7 @@ D2B18B802360945C00A9AEDC /* View.swift */, 0AE14F63238315D2005417F8 /* TextField.swift */, D2755D7A23689C7500485468 /* TableViewCell.swift */, + D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */, 0A5D59C323AD488600EFD9E9 /* Protocols */, ); path = BaseClasses; @@ -2005,6 +2026,7 @@ D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */, + D21B7F77243BB70700051ABF /* MoleculeCollectionItemModel.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, 011D9602240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift in Sources */, D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, @@ -2022,6 +2044,7 @@ 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */, + D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, C7F8012323E846C300396FBD /* ListRVWheelModel.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, @@ -2076,6 +2099,7 @@ D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, 5248BFEC23F12E350059236A /* ListThreeColumnPlanDataDivider.swift in Sources */, 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, + D264FA8E243BCD9A00D98315 /* CollectionTemplate.swift in Sources */, 0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */, 94F217B723E0BF6100A47C06 /* PrimaryButtonView.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */, @@ -2203,6 +2227,7 @@ D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, + D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */, 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */, D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */, 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, @@ -2229,10 +2254,12 @@ 52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */, C003506123AA94CD00B6AC29 /* Button.swift in Sources */, DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */, + D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, + D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */, C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -2262,6 +2289,7 @@ EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */, + D21B7F75243BAC8900051ABF /* CarouselItem.swift in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, C695A69823C990C200BFB94E /* DoughnutChartView.swift in Sources */, 8D3BA9BD2433787000D341BA /* ListThreeColumnInternationalDataDividerModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index 9fc218a7..ab6a1cbd 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -9,22 +9,20 @@ import Foundation -@objcMembers public class CarouselItemModel: MoleculeContainerModel, CarouselItemModelProtocol { - public static var identifier: String = "carouselItem" - public var backgroundColor: Color? +@objcMembers public class CarouselItemModel: MoleculeCollectionItemModel, CarouselItemModelProtocol { + public override class var identifier: String { + return "carouselItem" + } public var peakingUI: Bool? public var peakingArrowColor: Color? private enum CodingKeys: String, CodingKey { - case moleculeName - case backgroundColor case peakingUI case peakingArrowColor } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) peakingUI = try typeContainer.decodeIfPresent(Bool.self, forKey: .peakingUI) peakingArrowColor = try typeContainer.decodeIfPresent(Color.self, forKey: .peakingArrowColor) try super.init(from: decoder) @@ -33,8 +31,6 @@ import Foundation public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(peakingUI, forKey: .peakingUI) try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) } diff --git a/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift b/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift new file mode 100644 index 00000000..c8bfb7d7 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift @@ -0,0 +1,13 @@ +// +// CollectionItemModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol CollectionItemModelProtocol: ContainerModelProtocol { + +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift new file mode 100644 index 00000000..8d236f4b --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -0,0 +1,66 @@ +// +// MoleculeCollectionItemModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +@objcMembers open class MoleculeCollectionItemModel: CollectionItemModelProtocol, MoleculeModelProtocol { + open class var identifier: String { + return "collectionItem" + } + open var molecule: MoleculeModelProtocol + public var backgroundColor: Color? + + public var horizontalAlignment: UIStackView.Alignment? + public var verticalAlignment: UIStackView.Alignment? + public var useHorizontalMargins: Bool? + public var useVerticalMargins: Bool? + public var topMarginPadding: CGFloat? + public var bottomMarginPadding: CGFloat? + + /// Defaults to set + open func setDefaults() { + if useHorizontalMargins == nil { + useHorizontalMargins = true + } + if useVerticalMargins == nil { + useVerticalMargins = true + } + if topMarginPadding == nil { + topMarginPadding = PaddingDefault + } + if bottomMarginPadding == nil { + bottomMarginPadding = PaddingDefault + } + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case molecule + case backgroundColor + } + + public init(with moleculeModel: MoleculeModelProtocol) { + molecule = moleculeModel + setDefaults() + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecule = try typeContainer.decodeModel(codingKey: .molecule) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + setDefaults() + } + + open func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeModel(molecule, forKey: .molecule) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift index 219617ec..0ae515f5 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift @@ -8,90 +8,15 @@ import UIKit -open class MoleculeCollectionViewCell: UICollectionViewCell, MoleculeViewProtocol { - - open var molecule: MoleculeViewProtocol? - public let containerHelper = ContainerHelper() - - // In updateView, will set padding to default. - open var updateViewHorizontalDefaults = true - open var updateViewVerticalDefaults = true - - open var allowsPeaking = false - var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) - var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) - var peakingCover = MVMCoreUICommonViewsUtility.commonView() - - public override init(frame: CGRect) { - super.init(frame: .zero) - setupView() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupView() - } - - public func setupView() { - guard peakingCover.superview == nil else { - return - } - isAccessibilityElement = false - contentView.isAccessibilityElement = false - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - - // Covers the card when peaking. - peakingCover.backgroundColor = .white - peakingCover.alpha = 0 - contentView.addSubview(peakingCover) - NSLayoutConstraint.constraintPinSubview(toSuperview: peakingCover) - - // A small arrow on the next card for when peaking. - let ratio: CGFloat = 0.015 - peakingLeftArrow.translatesAutoresizingMaskIntoConstraints = false - peakingLeftArrow.alpha = 0 - peakingLeftArrow.tintColor = .black - contentView.addSubview(peakingLeftArrow) - peakingLeftArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true - NSLayoutConstraint.scalingPinViewLeft(toSuper: peakingLeftArrow, ratio: ratio, anchor: contentView.widthAnchor) - - peakingRightArrow.translatesAutoresizingMaskIntoConstraints = false - peakingRightArrow.transform = CGAffineTransform(scaleX: -1, y: 1) // Flip - peakingRightArrow.alpha = 0 - peakingRightArrow.tintColor = .black - contentView.addSubview(peakingRightArrow) - peakingRightArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true - NSLayoutConstraint.scalingPinViewRight(toSuper: peakingRightArrow, ratio: ratio, anchor: contentView.widthAnchor) - } - - - public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let collectionModel = model as? CarouselItemModel else { return } - if let useHorizontalMargins = collectionModel.useHorizontalMargins { - updateViewHorizontalDefaults = useHorizontalMargins - } - if let useVerticalMargins = collectionModel.useVerticalMargins { - updateViewVerticalDefaults = useVerticalMargins - } - // Handles peaking. - allowsPeaking = collectionModel.peakingUI ?? false - if let peakingArrowColor = collectionModel.peakingArrowColor { - let color = peakingArrowColor.uiColor - peakingLeftArrow.tintColor = color - peakingRightArrow.tintColor = color - } - - if let backgroundColor = collectionModel.backgroundColor { - self.backgroundColor = backgroundColor.uiColor - } +open class MoleculeCollectionViewCell: CollectionViewCell { + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let collectionModel = model as? MoleculeCollectionItemModel else { return } if molecule == nil { if let moleculeView = MoleculeObjectMapping.shared()?.createMolecule(collectionModel.molecule, delegateObject: delegateObject, additionalData: additionalData) { - contentView.insertSubview(moleculeView, at: 0) - containerHelper.constrainView(moleculeView) - molecule = moleculeView + addMolecule(moleculeView) + contentView.sendSubviewToBack(moleculeView) } } else { molecule?.set(with: collectionModel.molecule, delegateObject, additionalData) @@ -102,39 +27,33 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco accessibilityElements = molecule.subviews } - public func reset() { + open override func reset() { + super.reset() molecule?.reset() - updateViewVerticalDefaults = true - updateViewHorizontalDefaults = true backgroundColor = .white } - public class func nameForReuse(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { - guard let molecule = (model as? CarouselItemModel)?.molecule, - let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(molecule) else { - return nil - } - return moleculeClass.nameForReuse(with: molecule, delegateObject) ?? molecule.moleculeName + open class func nameForReuse(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let moleculeModel = (model as? MoleculeCollectionItemModel)?.molecule else { return "\(self)<>" } + let className = MoleculeObjectMapping.shared()?.getMoleculeClass(moleculeModel) + let moleculeName = className?.nameForReuse(with: moleculeModel, delegateObject) ?? moleculeModel.moleculeName + return "\(self)<\(moleculeName)>" } - public func updateView(_ size: CGFloat) { - (molecule as? MVMCoreViewProtocol)?.updateView(size) - MFStyler.setDefaultMarginsFor(contentView, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults) + open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let moleculeModel = (model as? MoleculeCollectionItemModel)?.molecule, + let theClass = MoleculeObjectMapping.shared()?.getMoleculeClass(moleculeModel) + else { return nil } + + return theClass.requiredModules(with: moleculeModel, delegateObject, error: error) } - public func setPeaking(_ peaking: Bool, animated: Bool) { - guard allowsPeaking else { - return - } - let animation = {() in - self.peakingRightArrow.alpha = peaking ? 1 : 0 - self.peakingLeftArrow.alpha = peaking ? 1 : 0 - self.peakingCover.alpha = peaking ? 0.5 : 0 - } - if animated { - UIView.animate(withDuration: 0.4, animations: animation) - } else { - animation() - } + open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + guard let model = model as? MoleculeCollectionItemModel, + let classType = MoleculeObjectMapping.shared()?.getMoleculeClass(model.molecule), + let height = classType.estimatedHeight(with: model.molecule, delegateObject) + else { return 100 } + + return height + (model.topMarginPadding ?? 0) + (model.bottomMarginPadding ?? 0) } } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index a7117e33..05a28a51 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -52,12 +52,11 @@ open class Carousel: View { public var delegateObject: MVMCoreUIDelegateObject? + private var size: CGFloat? + // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() - guard collectionView.superview == nil else { - return - } collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.dataSource = self collectionView.delegate = self @@ -73,6 +72,7 @@ open class Carousel: View { open override func updateView(_ size: CGFloat) { super.updateView(size) + self.size = size collectionView.collectionViewLayout.invalidateLayout() showPeaking(false) @@ -143,7 +143,7 @@ open class Carousel: View { } /// Sets up the paging molecule - open func setupPagingMolecule(_ molecule: CarouselPagingModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) { + open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) { var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil if let molecule = molecule { pagingView = MoleculeObjectMapping.shared()?.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & MVMCoreUIPagingProtocol) @@ -208,15 +208,15 @@ open class Carousel: View { // Show overlay and arrow in peaking Cell let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row } if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex { - (collectionView.cellForItem(at: firstItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true) + (collectionView.cellForItem(at: firstItem) as? CarouselItem)?.setPeaking(true, animated: true) } if let lastItem = visibleItemsPaths.last, lastItem.row != currentIndex { - (collectionView.cellForItem(at: lastItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true) + (collectionView.cellForItem(at: lastItem) as? CarouselItem)?.setPeaking(true, animated: true) } } else { // Hide peaking. for item in collectionView.visibleCells { - (item as? MoleculeCollectionViewCell)?.setPeaking(false, animated: true) + (item as? CarouselItem)?.setPeaking(false, animated: true) } } } @@ -229,10 +229,12 @@ open class Carousel: View { cell.accessibilityElementsHidden = false var array = cell.accessibilityElements - if let acc = pagingView?.accessibilityElements { - array?.append(contentsOf: acc) - } else { - array?.append(pagingView!) + if let pagingView = pagingView { + if let acc = pagingView.accessibilityElements { + array?.append(contentsOf: acc) + } else { + array?.append(pagingView) + } } self.accessibilityElements = array @@ -244,12 +246,12 @@ open class Carousel: View { extension Carousel: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) + let itemWidth = (size ?? collectionView.bounds.width) * CGFloat(itemWidthPercent) return CGSize(width: itemWidth, height: collectionView.bounds.height) } open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - (cell as? MoleculeCollectionViewCell)?.setPeaking(false, animated: false) + (cell as? CarouselItem)?.setPeaking(false, animated: false) } } @@ -268,7 +270,7 @@ extension Carousel: UICollectionViewDataSource { protocolCell.reset() protocolCell.set(with: moleculeInfo.molecule, delegateObject, nil) } - (cell as? MVMCoreViewProtocol)?.updateView(collectionView.bounds.width) + (cell as? MVMCoreViewProtocol)?.updateView(size ?? collectionView.bounds.width) setAccessiblity(cell, index: indexPath.row) return cell } diff --git a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift index 8b987a73..9fb4fe3b 100644 --- a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift @@ -19,7 +19,7 @@ import UIKit public var height: Float? public var itemWidthPercent: Float? public var itemAlignment: UICollectionView.ScrollPosition? - public var pagingMolecule: CarouselPagingModelProtocol? + public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)? public init(molecules: [CarouselItemModel]){ self.molecules = molecules @@ -42,12 +42,12 @@ import UIKit let typeContainer = try decoder.container(keyedBy: CodingKeys.self) self.molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - self.spacing = try typeContainer.decode(Float.self, forKey: .spacing) - self.border = try typeContainer.decode(Bool.self, forKey: .border) - self.loop = try typeContainer.decode(Bool.self, forKey: .loop) - self.height = try typeContainer.decode(Float.self, forKey: .height) - self.itemWidthPercent = try typeContainer.decode(Float.self, forKey: .itemWidthPercent) - self.itemAlignment = try typeContainer.decode(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) + self.spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing) + self.border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border) + self.loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop) + self.height = try typeContainer.decodeIfPresent(Float.self, forKey: .height) + self.itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent) + self.itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) self.pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule) } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift index 94b1277a..72138290 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift @@ -9,6 +9,6 @@ import Foundation -public protocol CarouselPagingModelProtocol: MoleculeModelProtocol { +public protocol CarouselPagingModelProtocol { var position: Float? { get } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift new file mode 100644 index 00000000..ce135631 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -0,0 +1,15 @@ +// +// CollectionTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc open class CollectionTemplate: ThreeLayerCollectionViewController, TemplateProtocol { + public typealias TemplateModel = CollectionTemplateModel + public var templateModel: CollectionTemplateModel? + +} diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift new file mode 100644 index 00000000..56c56456 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift @@ -0,0 +1,67 @@ +// +// CollectionTemplateModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class CollectionTemplateModel: TemplateModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { + return "collection" + } + public var header: MoleculeModelProtocol? + public var molecules: [CollectionItemModelProtocol & MoleculeModelProtocol]? + public var footer: MoleculeModelProtocol? + public var columns: Int? + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(pageType: String, screenHeading: String?, molecules: [CollectionItemModelProtocol & MoleculeModelProtocol]) { + super.init(pageType: pageType) + self.screenHeading = screenHeading + self.molecules = molecules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case molecules + case header + case footer + case columns + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecules = try typeContainer.decodeModelsIfPresent(codingKey: .molecules) + header = try typeContainer.decodeModelIfPresent(codingKey: .header) + footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) + columns = try typeContainer.decodeIfPresent(Int.self, forKey: .columns) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeModelsIfPresent(molecules, forKey: .molecules) + try container.encodeModelIfPresent(header, forKey: .header) + try container.encodeModelIfPresent(footer, forKey: .footer) + try container.encodeIfPresent(columns, forKey: .columns) + } +} + diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift index 8f963f5d..96e85fad 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift @@ -51,10 +51,6 @@ import UIKit return molecule } - open override func spaceBetweenMiddleAndBottom() -> CGFloat? { - return 0 - } - open override func spaceBetweenTopAndMiddle() -> CGFloat? { return 0 } diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift new file mode 100644 index 00000000..50cece5d --- /dev/null +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -0,0 +1,77 @@ +// +// CollectionViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol { + + // Convenience helpers + open var molecule: MoleculeViewProtocol? + public let containerHelper = ContainerHelper() + open var model: CollectionItemModelProtocol? + + private var initialSetupPerformed = false + + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initialSetup() + } + + private func initialSetup() { + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } + + open func setupView() { + isAccessibilityElement = false + contentView.isAccessibilityElement = false + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false + MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) + } + + open func updateView(_ size: CGFloat) { + containerHelper.updateViewMargins(contentView, model: model, size: size) + DispatchQueue.main.async { + print("\(self.contentView.directionalLayoutMargins.leading)") + } + } + + open func reset() { + molecule?.reset() + backgroundColor = .white + } + + open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? CollectionItemModelProtocol else { return } + self.model = model + + if let moleculeModel = model as? MoleculeModelProtocol, + let backgroundColor = moleculeModel.backgroundColor { + self.backgroundColor = backgroundColor.uiColor + } + + // align if needed. + containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol) + } + + /// Convenience function. Adds the molecule to the view. + open func addMolecule(_ molecule: MoleculeViewProtocol) { + contentView.addSubview(molecule) + containerHelper.constrainView(molecule) + self.molecule = molecule + } +} diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift new file mode 100644 index 00000000..a7b39cfd --- /dev/null +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -0,0 +1,289 @@ +// +// ThreeLayerCollectionViewController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc open class ThreeLayerCollectionViewController: ScrollingViewController, UICollectionViewDataSource, UICollectionViewDelegate { + public var collectionView: UICollectionView? + + // The three main views + private var topView: UIView? + private var bottomView: UIView? + private var headerView: UIView? + private var footerView: UIView? + private var safeAreaView: UIView? + var useMargins: Bool = true + public var bottomViewOutsideOfScrollArea: Bool = false + private var topViewBottomConstraint: NSLayoutConstraint? + private var bottomViewTopConstraint: NSLayoutConstraint? + + //MARK: - MFViewController + open override func updateViews() { + super.updateViews() + let width = view.bounds.width + if let topView = topView as? MVMCoreViewProtocol { + topView.updateView(width) + //showHeader(width) + } + if let bottomView = bottomView as? MVMCoreViewProtocol { + bottomView.updateView(width) + //showFooter(width) + } + self.collectionView?.collectionViewLayout.invalidateLayout() + } + + open override func handleNewData() { + super.handleNewData() + //createViewForTableHeader() + //createViewForTableFooter() + collectionView?.reloadData() + } + + override open func viewDidLoad() { + let collection = createCollectionView() + collectionView = collection + view.addSubview(collection) + NSLayoutConstraint.constraintPinSubview(toSuperview: collection) + scrollView = collectionView + + registerCells() + + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + //MARK: - Spacing + // If both are subclassed to return a value, then the buttons will not be pinned towards the bottom because neither spacing would try to fill the screen. + /// Space between the top view and the table sections, nil to fill. 0 default + open func spaceBelowTopView() -> CGFloat? { + return 0 + } + + /// Space between the bottom view and the table sections, nil to fill. nil default + open func spaceAboveBottomView() -> CGFloat? { + return nil + } + + /// can override to return a minimum fill space. + open func minimumFillSpace() -> CGFloat { + return 0 + } + + /*open override func updateViewConstraints() { + super.updateViewConstraints() + + guard let tableView = collectionView else { return } + + let minimumSpace: CGFloat = minimumFillSpace() + var currentSpace: CGFloat = 0 + var totalMinimumSpace: CGFloat = 0 + + var fillTop = false + if spaceBelowTopView() == nil, headerView != nil { + fillTop = true + currentSpace += topViewBottomConstraint?.constant ?? 0 + totalMinimumSpace += minimumSpace + } + + var fillBottom = false + if spaceAboveBottomView() == nil, !bottomViewOutsideOfScrollArea, footerView != nil { + fillBottom = true + currentSpace += bottomViewTopConstraint?.constant ?? 0 + totalMinimumSpace += minimumSpace + } + + guard fillTop || fillBottom else { return } + + let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) + + // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. + var currentSpaceForCompare: CGFloat = currentSpace + if fillTop && bottomViewOutsideOfScrollArea { + 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(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 { + topViewBottomConstraint?.constant = newSpace / 2 + } else { + topViewBottomConstraint?.constant = newSpace + } + showHeader(width) + } else if fillBottom { + // Only bottom is spaced. + bottomViewTopConstraint?.constant = newSpace + showFooter(width) + } + } + } + + //MARK: - Header Footer + /// Gets the top view and adds it to a spacing view, headerView, and then calls showHeader. + open func createViewForTableHeader() { + let topView = viewForTop() + self.topView = topView + + let headerView = MVMCoreUICommonViewsUtility.commonView() + headerView.addSubview(topView) + topView.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true + topView.leftAnchor.constraint(equalTo: headerView.leftAnchor).isActive = true + headerView.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true + topViewBottomConstraint = headerView.bottomAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceBelowTopView() ?? 0) + topViewBottomConstraint?.isActive = true + self.headerView = headerView + showHeader(nil) + } + + /// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter. + open func createViewForTableFooter() { + let bottomView = viewForBottom() + self.bottomView = bottomView + + let footerView = MVMCoreUICommonViewsUtility.commonView() + footerView.addSubview(bottomView) + bottomViewTopConstraint = bottomView.topAnchor.constraint(equalTo: footerView.topAnchor, constant: spaceAboveBottomView() ?? 0) + bottomViewTopConstraint?.isActive = true + bottomView.leftAnchor.constraint(equalTo: footerView.leftAnchor).isActive = true + footerView.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true + footerView.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true + self.footerView = footerView + showFooter(nil) + } + + /// Takes the current headerView and adds it to the tableHeaderView + func showHeader(_ sizingWidth: CGFloat?) { + headerView?.removeFromSuperview() + tableView?.tableHeaderView = nil + guard let headerView = headerView else { + return + } + + // This extra view is needed because of the wonkiness of apple's table header. Things breaks if using autolayout. + headerView.setNeedsLayout() + headerView.layoutIfNeeded() + MVMCoreUIUtility.sizeView(toFit: headerView) + let tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: MVMCoreUIUtility.getWidth(), height: headerView.frame.height)) + tableHeaderView.addSubview(headerView) + NSLayoutConstraint.constraintPinSubview(toSuperview: headerView) + tableView?.tableHeaderView = tableHeaderView + } + + /// Takes the current footerView and adds it to the tableFooterView + func showFooter(_ sizingWidth: CGFloat?) { + footerView?.removeFromSuperview() + safeAreaView?.removeFromSuperview() + guard let footerView = footerView, let tableView = tableView else { + return + } + + if bottomViewOutsideOfScrollArea { + // put bottom view outside of scrolling area. + bottomConstraint?.isActive = false + view.addSubview(footerView) + footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true + footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true + view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true + safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) + safeAreaView?.backgroundColor = bottomView?.backgroundColor + } else { + bottomConstraint?.isActive = true + var y: CGFloat? + if let tableFooterView = tableView.tableFooterView { + // if footer already exists, use the same y location to avoid strange moving animation + y = tableFooterView.frame.minY + } + + // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. + MVMCoreUIUtility.sizeView(toFit: footerView) + let tableFooterView = UIView(frame: CGRect(x: 0, y: y ?? 0, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) + tableFooterView.addSubview(footerView) + NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) + tableView.tableFooterView = tableFooterView + } + }*/ + + //MARK: - Functions to subclass + /// Subclass for a top view. + open func viewForTop() -> UIView { + let view = MVMCoreUICommonViewsUtility.commonView() + // Small height is needed to stop apple from adding padding for grouped tables when no header. + view.heightAnchor.constraint(equalToConstant: 1).isActive = true + return view + } + + /// Subclass for a bottom view. + open func viewForBottom() -> UIView { + // Default spacing is standard when no buttons. + let view = MVMCoreUICommonViewsUtility.commonView() + view.heightAnchor.constraint(equalToConstant: PaddingDefaultVerticalSpacing).isActive = true + return view + } + + open func createCollectionView() -> UICollectionView { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 1 + layout.minimumInteritemSpacing = 0 + + let collection = UICollectionView(frame: .zero, collectionViewLayout: layout) + collection.translatesAutoresizingMaskIntoConstraints = false + collection.dataSource = self + collection.delegate = self + collection.showsHorizontalScrollIndicator = false + collection.backgroundColor = .white + collection.isAccessibilityElement = false + collection.contentInsetAdjustmentBehavior = .always + return collection + } + + deinit { + collectionView?.delegate = nil + collectionView?.dataSource = nil + } + + //MARK: - Collection + + public func registerCells() { + collectionView?.register(MoleculeCollectionViewCell.self, forCellWithReuseIdentifier: "collectionItem") + } + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 2 + } + + public func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionItem", for: indexPath) as! MoleculeCollectionViewCell + let labelModel = LabelModel(text: "hello") + let model = MoleculeCollectionItemModel(with: labelModel) + cell.set(with: model, delegateObjectIVar, nil) + return cell + } + + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: 200, height: 200) + } + + open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + (cell as? CarouselItem)?.setPeaking(false, animated: false) + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m index d3db2a2d..20fb924e 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m @@ -24,7 +24,8 @@ @"list" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]], @"threeLayer" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ThreeLayerTemplate class]], @"modalStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeStackTemplate class]], - @"modalList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeListTemplate class]] + @"modalList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeListTemplate class]], + @"collection" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[CollectionTemplate class]] } mutableCopy]; }); return viewControllerMapping; From d393098fcddc0e963a54737d63e44251d1fa9748 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 7 Apr 2020 10:08:47 -0400 Subject: [PATCH 07/38] collection changes --- .../Atomic/Molecules/Items/CarouselItem.swift | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift new file mode 100644 index 00000000..9cf2a246 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift @@ -0,0 +1,73 @@ +// +// CarouselItem.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class CarouselItem: MoleculeCollectionViewCell { + + open var allowsPeaking = false + var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) + var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) + var peakingCover = MVMCoreUICommonViewsUtility.commonView() + + open override func setupView() { + super.setupView() + + // Covers the card when peaking. + peakingCover.backgroundColor = .white + peakingCover.alpha = 0 + contentView.addSubview(peakingCover) + NSLayoutConstraint.constraintPinSubview(toSuperview: peakingCover) + + // A small arrow on the next card for when peaking. + let ratio: CGFloat = 0.015 + peakingLeftArrow.translatesAutoresizingMaskIntoConstraints = false + peakingLeftArrow.alpha = 0 + peakingLeftArrow.tintColor = .black + contentView.addSubview(peakingLeftArrow) + peakingLeftArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + NSLayoutConstraint.scalingPinViewLeft(toSuper: peakingLeftArrow, ratio: ratio, anchor: contentView.widthAnchor) + + peakingRightArrow.translatesAutoresizingMaskIntoConstraints = false + peakingRightArrow.transform = CGAffineTransform(scaleX: -1, y: 1) // Flip + peakingRightArrow.alpha = 0 + peakingRightArrow.tintColor = .black + contentView.addSubview(peakingRightArrow) + peakingRightArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + NSLayoutConstraint.scalingPinViewRight(toSuper: peakingRightArrow, ratio: ratio, anchor: contentView.widthAnchor) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let collectionModel = model as? CarouselItemModel else { return } + + // Handles peaking. + allowsPeaking = collectionModel.peakingUI ?? false + if let peakingArrowColor = collectionModel.peakingArrowColor { + let color = peakingArrowColor.uiColor + peakingLeftArrow.tintColor = color + peakingRightArrow.tintColor = color + } + } + + public func setPeaking(_ peaking: Bool, animated: Bool) { + guard allowsPeaking else { + return + } + let animation = {() in + self.peakingRightArrow.alpha = peaking ? 1 : 0 + self.peakingLeftArrow.alpha = peaking ? 1 : 0 + self.peakingCover.alpha = peaking ? 0.5 : 0 + } + if animated { + UIView.animate(withDuration: 0.4, animations: animation) + } else { + animation() + } + } +} From d53b21224183a14fedb07a5c2debc271ce03d29e Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 7 Apr 2020 15:29:23 -0400 Subject: [PATCH 08/38] fiddling with three layer --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 4 +- .../Doughnut/DoughnutChartModel.swift | 7 + .../Atomic/Templates/CollectionTemplate.swift | 162 ++++++++++ .../ContainerCollectionReusableView.swift | 25 ++ .../ThreeLayerCollectionViewController.swift | 281 +++++++++--------- MVMCoreUI/Utility/MVMCoreUIUtility.m | 2 +- 7 files changed, 348 insertions(+), 137 deletions(-) create mode 100644 MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index e1310f0d..6db35f4e 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -249,6 +249,7 @@ D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */; }; D264FA8E243BCD9A00D98315 /* CollectionTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */; }; D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */; }; + D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */; }; D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; @@ -654,6 +655,7 @@ D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplateModel.swift; sourceTree = ""; }; D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplate.swift; sourceTree = ""; }; D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCollectionViewController.swift; sourceTree = ""; }; + D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerCollectionReusableView.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; @@ -1477,6 +1479,7 @@ D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */, D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */, D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */, + D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */, D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */, D2C521A823EDE79E00CA2634 /* ViewController.swift */, D2A92881241AAB67004E01C6 /* ScrollingViewController.swift */, @@ -2029,6 +2032,7 @@ D21B7F77243BB70700051ABF /* MoleculeCollectionItemModel.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, 011D9602240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift in Sources */, + D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */, D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, D2E2A99823D8D63C000B42E6 /* ActionDetailWithImageModel.swift in Sources */, D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index 94bbc61f..5ef0e20e 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -112,7 +112,9 @@ import Foundation // Other Items MoleculeObjectMapping.shared()?.register(viewClass: MoleculeStackItem.self, viewModelClass: MoleculeStackItemModel.self) MoleculeObjectMapping.shared()?.register(viewClass: StackItem.self, viewModelClass: StackItemModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: MoleculeCollectionViewCell.self, viewModelClass: CarouselItemModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: MoleculeCollectionViewCell.self, viewModelClass: MoleculeCollectionItemModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: CarouselItem.self, viewModelClass: CarouselItemModel.self) + // Other Container Molecules MoleculeObjectMapping.shared()?.register(viewClass: MoleculeHeaderView.self, viewModelClass: MoleculeHeaderModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift index ff9b1bf6..2b64d1ae 100644 --- a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift @@ -35,4 +35,11 @@ import Foundation self.color = color self.label = label } + + private enum CodingKeys: String, CodingKey { + case backgroundColor + case label + case percent + case color + } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index ce135631..11142532 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -11,5 +11,167 @@ import Foundation @objc open class CollectionTemplate: ThreeLayerCollectionViewController, TemplateProtocol { public typealias TemplateModel = CollectionTemplateModel public var templateModel: CollectionTemplateModel? + + public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? + + var observer: NSKeyValueObservation? + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + open override func parsePageJSON() throws { + try parseTemplate(json: loadObject?.pageJSON) + try super.parsePageJSON() + } + + open override var loadObject: MVMCoreLoadObject? { + didSet { + guard loadObject != oldValue else { return } + + updateRequiredModules() + observer?.invalidate() + if let newObject = loadObject { + observer = newObject.observe(\MVMCoreLoadObject.pageJSON, options: [.old, .new]) { [weak self] object, change in + self?.updateRequiredModules() + } + } + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + open override func viewForTop() -> UIView? { + guard let headerModel = templateModel?.header, + let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar) + else { return super.viewForTop() } + + // Temporary, Default the horizontal padding + if var container = templateModel?.header as? ContainerModelProtocol, container.useHorizontalMargins == nil { + container.useHorizontalMargins = true + } + + return molecule + } + + override open func viewForBottom() -> UIView? { + guard let footerModel = templateModel?.footer, + let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar) + else { return super.viewForBottom() } + + return molecule + } + + open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer) -> Bool { + + guard super.shouldFinishProcessingLoad(loadObject, error: error) else { return false } + + // This template requires atleast one of the three layers. + if templateModel?.header == nil, + templateModel?.molecules?.count ?? 0 == 0, + templateModel?.footer == nil, + let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), messageToLog: "Collection template requires atleast one of the following: header, footer, molecules", code: CoreUIErrorCode.ErrorCodeListMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) { + error.pointee = errorObject + return false + } + return true + } + + + open override func handleNewData() { + super.handleNewData() + setup() + registerCells() + } + + //-------------------------------------------------- + // MARK: - Collection + //-------------------------------------------------- + + open override func registerCells() { + super.registerCells() + guard let moleculesInfo = moleculesInfo else { return } + + for moleculeInfo in moleculesInfo { + collectionView?.register(moleculeInfo.class, forCellWithReuseIdentifier: moleculeInfo.identifier) + } + } + + open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return moleculesInfo?.count ?? 0 + } + + open override func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let moleculeInfo = moleculesInfo?[indexPath.row] + else { return UICollectionViewCell() } + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath) + (cell as? MoleculeViewProtocol)?.reset() + (cell as? MoleculeViewProtocol)?.set(with: moleculeInfo.molecule, delegateObjectIVar, nil) + (cell as? MVMCoreViewProtocol)?.updateView(view.bounds.width) + + // Neded to fix an apple defect where the cell is not the correct size on certain devices for certain cells + cell.layoutIfNeeded() + return cell + } + + //-------------------------------------------------- + // MARK: - Convenience + //-------------------------------------------------- + + /// Returns the (identifier, class) of the molecule for the given map. + func getMoleculeInfo(with item: (CollectionItemModelProtocol & MoleculeModelProtocol)?) -> (identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)? { + guard let item = item, + let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(item) else { return nil } + let moleculeName = moleculeClass.nameForReuse(with: item, delegateObjectIVar) ?? item.moleculeName + return (moleculeName, moleculeClass, item) + } + + /// Sets up the molecule list and ensures no errors loading all content. + func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? { + + var moleculeList: [(identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)] = [] + + if let molecules = templateModel?.molecules { + for molecule in molecules { + if let info = getMoleculeInfo(with: molecule) { + moleculeList.append(info) + } + } + } + + return moleculeList.count > 0 ? moleculeList : nil + } + + /// Sets up the header, footer, molecule list and ensures no errors loading all content. + func setup() { + moleculesInfo = getMoleculeInfoList() + } + + /// Adds modules from requiredModules() to the MVMCoreViewControllerMapping.requiredModules map. + open func updateRequiredModules() { + if let requiredModules = requiredModules(), let pageType = pageType { + MVMCoreViewControllerMappingObject.shared()?.addRequiredModules(toMapping: requiredModules, forPageType: pageType) + } + } + + /// Gets modules required by the loadObject.pageJSON. + open func requiredModules() -> [Any]? { + var modules: [String]? = [] + var errors: [MVMCoreErrorObject]? = nil + MoleculeObjectMapping.addRequiredModules(for: templateModel?.header, delegateObjectIVar, moduleList: &modules, errorList: &errors) + MoleculeObjectMapping.addRequiredModules(for: templateModel?.footer, delegateObjectIVar, moduleList: &modules, errorList: &errors) + + if let molecules = templateModel?.molecules { + for molecule in molecules { + MoleculeObjectMapping.addRequiredModules(for: molecule, delegateObjectIVar, moduleList: &modules, errorList: &errors) + } + } + + return modules + } } diff --git a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift new file mode 100644 index 00000000..c578c273 --- /dev/null +++ b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift @@ -0,0 +1,25 @@ +// +// ContainerCollectionReusableView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/7/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ContainerCollectionReusableView: UICollectionReusableView { + var view: UIView? + var topConstraint: NSLayoutConstraint? + var bottomConstraint: NSLayoutConstraint? + + public func addAndContain(view: UIView) { + self.view?.removeFromSuperview() + view.setContentCompressionResistancePriority(.required, for: .vertical) + addSubview(view) + self.view = view + let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: view) + topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint + bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint + } +} diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index a7b39cfd..74877925 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -8,19 +8,17 @@ import Foundation -@objc open class ThreeLayerCollectionViewController: ScrollingViewController, UICollectionViewDataSource, UICollectionViewDelegate { +@objc open class ThreeLayerCollectionViewController: ScrollingViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { public var collectionView: UICollectionView? // The three main views private var topView: UIView? private var bottomView: UIView? - private var headerView: UIView? - private var footerView: UIView? - private var safeAreaView: UIView? + private var headerView: ContainerCollectionReusableView? + private var footerView: ContainerCollectionReusableView? var useMargins: Bool = true - public var bottomViewOutsideOfScrollArea: Bool = false - private var topViewBottomConstraint: NSLayoutConstraint? - private var bottomViewTopConstraint: NSLayoutConstraint? + private let headerID = "header" + private let footerID = "footer" //MARK: - MFViewController open override func updateViews() { @@ -28,7 +26,7 @@ import Foundation let width = view.bounds.width if let topView = topView as? MVMCoreViewProtocol { topView.updateView(width) - //showHeader(width) + // showHeader(width) } if let bottomView = bottomView as? MVMCoreViewProtocol { bottomView.updateView(width) @@ -39,8 +37,8 @@ import Foundation open override func handleNewData() { super.handleNewData() - //createViewForTableHeader() - //createViewForTableFooter() + createViewForTableHeader() + createViewForTableFooter() collectionView?.reloadData() } @@ -74,11 +72,11 @@ import Foundation return 0 } - /*open override func updateViewConstraints() { + open override func updateViewConstraints() { super.updateViewConstraints() - + guard let tableView = collectionView else { return } - + let minimumSpace: CGFloat = minimumFillSpace() var currentSpace: CGFloat = 0 var totalMinimumSpace: CGFloat = 0 @@ -86,48 +84,43 @@ import Foundation var fillTop = false if spaceBelowTopView() == nil, headerView != nil { fillTop = true - currentSpace += topViewBottomConstraint?.constant ?? 0 + currentSpace += headerView?.bottomConstraint?.constant ?? 0 totalMinimumSpace += minimumSpace } - + var fillBottom = false - if spaceAboveBottomView() == nil, !bottomViewOutsideOfScrollArea, footerView != nil { + if spaceAboveBottomView() == nil, footerView != nil { fillBottom = true - currentSpace += bottomViewTopConstraint?.constant ?? 0 + currentSpace += footerView?.topConstraint?.constant ?? 0 totalMinimumSpace += minimumSpace } - + guard fillTop || fillBottom else { return } - + let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) - + // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. var currentSpaceForCompare: CGFloat = currentSpace - if fillTop && bottomViewOutsideOfScrollArea { + if fillTop { 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(width) - showFooter(width) + headerView?.bottomConstraint?.constant = half + footerView?.topConstraint?.constant = half + collectionView?.invalidateIntrinsicContentSize() } 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 { - topViewBottomConstraint?.constant = newSpace / 2 - } else { - topViewBottomConstraint?.constant = newSpace - } - showHeader(width) + headerView?.bottomConstraint?.constant = newSpace + collectionView?.invalidateIntrinsicContentSize() } else if fillBottom { // Only bottom is spaced. - bottomViewTopConstraint?.constant = newSpace - showFooter(width) + print("newSpace \(newSpace)") + footerView?.topConstraint?.constant = newSpace + collectionView?.invalidateIntrinsicContentSize() } } } @@ -135,113 +128,99 @@ import Foundation //MARK: - Header Footer /// Gets the top view and adds it to a spacing view, headerView, and then calls showHeader. open func createViewForTableHeader() { - let topView = viewForTop() + guard let topView = viewForTop() else { + self.topView = nil + self.headerView = nil + return + } self.topView = topView - - let headerView = MVMCoreUICommonViewsUtility.commonView() - headerView.addSubview(topView) - topView.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true - topView.leftAnchor.constraint(equalTo: headerView.leftAnchor).isActive = true - headerView.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true - topViewBottomConstraint = headerView.bottomAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceBelowTopView() ?? 0) - topViewBottomConstraint?.isActive = true - self.headerView = headerView - showHeader(nil) } /// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter. open func createViewForTableFooter() { - let bottomView = viewForBottom() + guard let bottomView = viewForBottom() else { + self.bottomView = nil + self.footerView = nil + return + } self.bottomView = bottomView - - let footerView = MVMCoreUICommonViewsUtility.commonView() - footerView.addSubview(bottomView) - bottomViewTopConstraint = bottomView.topAnchor.constraint(equalTo: footerView.topAnchor, constant: spaceAboveBottomView() ?? 0) - bottomViewTopConstraint?.isActive = true - bottomView.leftAnchor.constraint(equalTo: footerView.leftAnchor).isActive = true - footerView.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true - footerView.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true - self.footerView = footerView - showFooter(nil) } - /// Takes the current headerView and adds it to the tableHeaderView - func showHeader(_ sizingWidth: CGFloat?) { - headerView?.removeFromSuperview() - tableView?.tableHeaderView = nil - guard let headerView = headerView else { - return - } - - // This extra view is needed because of the wonkiness of apple's table header. Things breaks if using autolayout. - headerView.setNeedsLayout() - headerView.layoutIfNeeded() - MVMCoreUIUtility.sizeView(toFit: headerView) - let tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: MVMCoreUIUtility.getWidth(), height: headerView.frame.height)) - tableHeaderView.addSubview(headerView) - NSLayoutConstraint.constraintPinSubview(toSuperview: headerView) - tableView?.tableHeaderView = tableHeaderView - } - - /// Takes the current footerView and adds it to the tableFooterView - func showFooter(_ sizingWidth: CGFloat?) { - footerView?.removeFromSuperview() - safeAreaView?.removeFromSuperview() - guard let footerView = footerView, let tableView = tableView else { - return - } - - if bottomViewOutsideOfScrollArea { - // put bottom view outside of scrolling area. - bottomConstraint?.isActive = false - view.addSubview(footerView) - footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true - footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true - view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true - safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) - safeAreaView?.backgroundColor = bottomView?.backgroundColor - } else { - bottomConstraint?.isActive = true - var y: CGFloat? - if let tableFooterView = tableView.tableFooterView { - // if footer already exists, use the same y location to avoid strange moving animation - y = tableFooterView.frame.minY - } - - // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. - MVMCoreUIUtility.sizeView(toFit: footerView) - let tableFooterView = UIView(frame: CGRect(x: 0, y: y ?? 0, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) - tableFooterView.addSubview(footerView) - NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) - tableView.tableFooterView = tableFooterView - } - }*/ +// /// Takes the current headerView and adds it to the tableHeaderView +// func showHeader(_ sizingWidth: CGFloat?) { +// headerView?.removeFromSuperview() +// tableView?.tableHeaderView = nil +// guard let headerView = headerView else { +// return +// } +// +// // This extra view is needed because of the wonkiness of apple's table header. Things breaks if using autolayout. +// headerView.setNeedsLayout() +// headerView.layoutIfNeeded() +// MVMCoreUIUtility.sizeView(toFit: headerView) +// let tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: MVMCoreUIUtility.getWidth(), height: headerView.frame.height)) +// tableHeaderView.addSubview(headerView) +// NSLayoutConstraint.constraintPinSubview(toSuperview: headerView) +// tableView?.tableHeaderView = tableHeaderView +// } +// +// /// Takes the current footerView and adds it to the tableFooterView +// func showFooter(_ sizingWidth: CGFloat?) { +// footerView?.removeFromSuperview() +// safeAreaView?.removeFromSuperview() +// guard let footerView = footerView, let tableView = tableView else { +// return +// } +// +// if bottomViewOutsideOfScrollArea { +// // put bottom view outside of scrolling area. +// bottomConstraint?.isActive = false +// view.addSubview(footerView) +// footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true +// footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true +// view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true +// view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true +// safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) +// safeAreaView?.backgroundColor = bottomView?.backgroundColor +// } else { +// bottomConstraint?.isActive = true +// var y: CGFloat? +// if let tableFooterView = tableView.tableFooterView { +// // if footer already exists, use the same y location to avoid strange moving animation +// y = tableFooterView.frame.minY +// } +// +// // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. +// MVMCoreUIUtility.sizeView(toFit: footerView) +// let tableFooterView = UIView(frame: CGRect(x: 0, y: y ?? 0, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) +// tableFooterView.addSubview(footerView) +// NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) +// tableView.tableFooterView = tableFooterView +// } +// } //MARK: - Functions to subclass /// Subclass for a top view. - open func viewForTop() -> UIView { - let view = MVMCoreUICommonViewsUtility.commonView() - // Small height is needed to stop apple from adding padding for grouped tables when no header. - view.heightAnchor.constraint(equalToConstant: 1).isActive = true - return view + open func viewForTop() -> UIView? { + return nil } /// Subclass for a bottom view. - open func viewForBottom() -> UIView { - // Default spacing is standard when no buttons. - let view = MVMCoreUICommonViewsUtility.commonView() - view.heightAnchor.constraint(equalToConstant: PaddingDefaultVerticalSpacing).isActive = true - return view + open func viewForBottom() -> UIView? { + return nil + } + + open func createCollectionViewLayout() -> UICollectionViewLayout { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 0 + layout.minimumInteritemSpacing = 0 + layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + return layout } open func createCollectionView() -> UICollectionView { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .vertical - layout.minimumLineSpacing = 1 - layout.minimumInteritemSpacing = 0 - - let collection = UICollectionView(frame: .zero, collectionViewLayout: layout) + let collection = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) collection.translatesAutoresizingMaskIntoConstraints = false collection.dataSource = self collection.delegate = self @@ -259,19 +238,20 @@ import Foundation //MARK: - Collection - public func registerCells() { - collectionView?.register(MoleculeCollectionViewCell.self, forCellWithReuseIdentifier: "collectionItem") + open func registerCells() { + collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID) + collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: footerID) } - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return 2 + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 0 } - public func numberOfSections(in collectionView: UICollectionView) -> Int { + open func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } - public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionItem", for: indexPath) as! MoleculeCollectionViewCell let labelModel = LabelModel(text: "hello") let model = MoleculeCollectionItemModel(with: labelModel) @@ -279,11 +259,42 @@ import Foundation return cell } - open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize(width: 200, height: 200) + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + guard let view = headerView ?? topView, + section == 0 else { return .zero } + + // Use this view to calculate the optimal size based on the collection view's width + return view.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), + withHorizontalFittingPriority: .required, // Width is fixed + verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed } - open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - (cell as? CarouselItem)?.setPeaking(false, animated: false) + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { + guard let view = footerView ?? bottomView, + section == numberOfSections(in: collectionView) - 1 else { return .zero } + + // Use this view to calculate the optimal size based on the collection view's width + let size = view.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), + withHorizontalFittingPriority: .required, // Width is fixed + verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed + print("SIZEEE \(size.height) \(String(describing: footerView?.topConstraint?.constant))") + return size + } + + open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + if (kind == UICollectionView.elementKindSectionFooter) { + let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as! ContainerCollectionReusableView + footerView.addAndContain(view: bottomView!) + footerView.topConstraint?.constant = spaceAboveBottomView() ?? 0 + self.footerView = footerView + return footerView + } else if (kind == UICollectionView.elementKindSectionHeader) { + let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as! ContainerCollectionReusableView + headerView.addAndContain(view: topView!) + headerView.bottomConstraint?.constant = spaceBelowTopView() ?? 0 + self.headerView = headerView + return headerView + } + fatalError() } } diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index bcead7fe..59c55cc6 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -164,7 +164,7 @@ CGFloat topInset = scrollview.contentInset.top; CGFloat bottomInset = scrollview.contentInset.bottom; - if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { + if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic || scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAlways) { topInset = scrollview.adjustedContentInset.top; bottomInset = scrollview.adjustedContentInset.bottom; } From 646dfd6a10df871831ad0dfe2f20255bfda3c544 Mon Sep 17 00:00:00 2001 From: "Suresh, Kamlesh" Date: Tue, 7 Apr 2020 15:49:58 -0400 Subject: [PATCH 09/38] selction fixes and reuse fixes --- MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift | 9 +++++++++ MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift | 4 +--- .../Atoms/Buttons/RadioButtonSelectionHelper.swift | 9 ++++++--- MVMCoreUI/FormUIHelpers/FormValidator.swift | 3 ++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift index 98532549..3d52e63b 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift @@ -19,6 +19,12 @@ import UIKit } } + public override var isSelected: Bool { + didSet { + radioModel?.state = isSelected + } + } + public var enabledColor: UIColor = .black public var disabledColor: UIColor = .mfSilver() public var delegateObject: MVMCoreUIDelegateObject? @@ -128,6 +134,9 @@ import UIKit self.delegateObject = delegateObject isSelected = model.state + if model.state { + print("log") + } RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift index 579c0ed9..99e15280 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift @@ -47,7 +47,6 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { public init(_ state: Bool) { self.state = state - baseValue = state } //-------------------------------------------------- @@ -55,7 +54,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- public func formFieldValue() -> AnyHashable? { - return state + return fieldValue } //-------------------------------------------------- @@ -75,7 +74,6 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - baseValue = state fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift index 3decadb5..dbc3f7fe 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift @@ -16,8 +16,11 @@ import UIKit private var fieldGroupName: String? public var baseValue: AnyHashable? - init(_ fieldKey: String?) { - self.fieldKey = fieldKey + init(_ radioButtonModel: RadioButtonModel?) { + self.fieldKey = radioButtonModel?.fieldKey + if radioButtonModel?.state ?? false { + self.baseValue = radioButtonModel?.formFieldValue() + } } public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton, delegateObject: MVMCoreUIDelegateObject?) { @@ -26,7 +29,7 @@ import UIKit return } - let radioButtonSelectionHelper = formValidator.radioButtonsModelByGroup[groupName] ?? RadioButtonSelectionHelper(radioButtonModel.fieldKey) + let radioButtonSelectionHelper = formValidator.radioButtonsModelByGroup[groupName] ?? RadioButtonSelectionHelper(radioButtonModel) radioButtonSelectionHelper.fieldGroupName = radioButtonModel.fieldKey formValidator.radioButtonsModelByGroup[groupName] = radioButtonSelectionHelper diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 592132f5..661036d8 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -66,7 +66,8 @@ import MVMCore return valid } for group in formRules { - valid = valid && validateGroup(group) + let groupValid = validateGroup(group) + valid = valid && groupValid } return valid } From 88defbc75c32ad22eb63027860f0c547b119790d Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Wed, 8 Apr 2020 10:16:33 +0530 Subject: [PATCH 10/38] file paths added --- MVMCoreUI.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 9516bdd3..d5abeb54 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -196,6 +196,10 @@ BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */; }; BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */; }; BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */; }; + BBAA4F02243D8E3B005AAD5F /* RadioBoxCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4EFE243D8E3A005AAD5F /* RadioBoxCollectionViewCell.swift */; }; + BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */; }; + BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */; }; + BBAA4F05243D8E3B005AAD5F /* RadioBoxesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */; }; BBBBC87C24374A4900B0F079 /* ListThreeColumnBillChangesDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBC87A24374A4900B0F079 /* ListThreeColumnBillChangesDivider.swift */; }; BBBBC87D24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBC87B24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift */; }; C003506123AA94CD00B6AC29 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C003506023AA94CD00B6AC29 /* Button.swift */; }; @@ -600,6 +604,10 @@ BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTall.swift; sourceTree = ""; }; BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShort.swift; sourceTree = ""; }; BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShortModel.swift; sourceTree = ""; }; + BBAA4EFE243D8E3A005AAD5F /* RadioBoxCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxCollectionViewCell.swift; sourceTree = ""; }; + BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxes.swift; sourceTree = ""; }; + BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = ""; }; + BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxesModel.swift; sourceTree = ""; }; BBBBC87A24374A4900B0F079 /* ListThreeColumnBillChangesDivider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListThreeColumnBillChangesDivider.swift; sourceTree = ""; }; BBBBC87B24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListThreeColumnBillChangesDividerModel.swift; sourceTree = ""; }; C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; @@ -1584,6 +1592,10 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */, + BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */, + BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */, + BBAA4EFE243D8E3A005AAD5F /* RadioBoxCollectionViewCell.swift */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */, 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, @@ -2041,6 +2053,7 @@ D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */, 014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */, 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */, + BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, @@ -2058,6 +2071,7 @@ 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */, + BBAA4F02243D8E3B005AAD5F /* RadioBoxCollectionViewCell.swift in Sources */, 8D084AD22410BF7600951227 /* ListOneColumnFullWidthTextBodyText.swift in Sources */, 94C0150C2421564A005811A9 /* ActionCollapseNotificationModel.swift in Sources */, 014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */, @@ -2092,6 +2106,7 @@ 011D959B240451E3000E3791 /* RuleRequiredModel.swift in Sources */, 526A265C240D1FF700B0D828 /* ListTwoColumnCompareChangesModel.swift in Sources */, D2A92886241ACD99004E01C6 /* ProgrammaticTableViewController.swift in Sources */, + BBAA4F05243D8E3B005AAD5F /* RadioBoxesModel.swift in Sources */, 01509D952327ED1900EF99AA /* HeadlineBodyLinkToggle.swift in Sources */, 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, @@ -2183,6 +2198,7 @@ 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */, 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */, 8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */, + BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, From 870658b4c1d9ddd6512a331e96563bf52ed6a9bf Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Wed, 8 Apr 2020 17:18:05 +0530 Subject: [PATCH 11/38] subtext label implemented --- .../Views/RadioBoxCollectionViewCell.swift | 47 +++++++++++++------ .../Atomic/Atoms/Views/RadioBoxModel.swift | 5 ++ MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 12 +++-- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift index 67068c85..d959285d 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift @@ -9,18 +9,20 @@ import Foundation open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtocol { - public let bodyLabel = Label.commonLabelB2(true) + public let bodyLabel = Label(frame: .zero) + public let subTextLabel = Label(frame: .zero) public let lineView = View(frame: .zero) var bottomView = MVMCoreUICommonViewsUtility.commonView() public var lineViewHeight: NSLayoutConstraint? public var fieldValue: String? + var boxModel: RadioBoxModel! open override var isSelected: Bool{ didSet{ - self.lineViewHeight?.constant = self.isSelected ? 4.0 : 0 - UIView.animate(withDuration: 0.5) { - self.layoutIfNeeded() + if let shapeLayer = self.bottomView.layer.sublayers?.filter({$0.name == "outofstock"}).first as? CAShapeLayer { + shapeLayer.strokeColor = isSelected ? UIColor.black.cgColor : UIColor.mfGet(forHex: "#747676").cgColor } + self.lineViewHeight?.constant = self.isSelected ? 4.0 : 0 self.bottomView.layer.sublayers?.filter({$0.name == "border"}).forEach({$0.removeFromSuperlayer()}) if(self.isSelected){ self.bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) @@ -28,8 +30,11 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco self.bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) } else { - self.addBordertoView() + self.addBordertoView("#747676") } + UIView.animate(withDuration: 0.5) { + self.layoutIfNeeded() + } } } @@ -77,7 +82,18 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco bottomView.addSubview(bodyLabel) NSLayoutConstraint.constraintPinSubview(bodyLabel, pinTop: false, topConstant:0 , pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 12, pinRight: true, rightConstant: 12) bodyLabel.topAnchor.constraint(equalTo: lineView.bottomAnchor, constant: 12).isActive = true - bodyLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true + + subTextLabel.numberOfLines = 1 + bottomView.addSubview(subTextLabel) + NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0 , pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 12, pinRight: true, rightConstant: 12) + subTextLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true + subTextLabel.topAnchor.constraint(equalTo: bodyLabel.bottomAnchor, constant: 2).isActive = true + + subTextLabel.font = MFFonts.mfFontTXRegular(11.0) + bodyLabel.font = MFFonts.mfFontTXRegular(13.0) + subTextLabel.textColor = UIColor.mfGet(forHex: "#747676") + + } public func updateView(_ size: CGFloat) { @@ -85,15 +101,16 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco self.bottomView.setNeedsLayout() self.bottomView.layer.sublayers?.filter({$0.name == "border"}).forEach({$0.removeFromSuperlayer()}) DispatchQueue.main.async { [weak self] in - self?.addBordertoView() + guard let self = self else { return } + self.addBordertoView(self.boxModel.enabled ? "#747676" : "#D8DADA") } } - private func addBordertoView(){ - bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) - bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) - bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) - bottomView.layer.addBorder(edge: .top, color: UIColor.mfGet(forHex: "#747676"), thickness: 1.0) + private func addBordertoView(_ color:String){ + bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: color), thickness: 1.0) + bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: color), thickness: 1.0) + bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: color), thickness: 1.0) + bottomView.layer.addBorder(edge: .top, color: UIColor.mfGet(forHex: color), thickness: 1.0) } private func addOutOfStockLine(){ @@ -103,20 +120,22 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco let shapeLayer = CAShapeLayer() shapeLayer.name = "outofstock" shapeLayer.path = path.cgPath - shapeLayer.strokeColor = UIColor.darkGray.cgColor + shapeLayer.strokeColor = self.boxModel.enabled ? UIColor.black.cgColor : UIColor.mfGet(forHex: "#D8DADA").cgColor shapeLayer.lineWidth = 0.5 bottomView.layer.addSublayer(shapeLayer) } public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let collectionModel = model as? RadioBoxModel else { return } + boxModel = collectionModel if let backgroundColor = collectionModel.backgroundColor { bottomView.backgroundColor = backgroundColor.uiColor } self.isUserInteractionEnabled = collectionModel.enabled lineView.backgroundColor = collectionModel.selectedAccentColor?.uiColor bodyLabel.text = collectionModel.text - isSelected = collectionModel.selected + subTextLabel.text = collectionModel.subText +// isSelected = collectionModel.selected fieldValue = collectionModel.fieldValue isOutOfStock = collectionModel.strikethrough diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift index 31f58e53..4ce1e302 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -10,6 +10,7 @@ import Foundation @objcMembers public class RadioBoxModel: MoleculeModelProtocol { public static var identifier: String = "radioBox" public var text: String + public var subText: String? public var backgroundColor: Color? = Color(uiColor: .white) public var selectedAccentColor: Color? = try? Color(colorString: "#D52B1E") public var selected: Bool = false @@ -20,6 +21,7 @@ import Foundation private enum CodingKeys: String, CodingKey { case moleculeName case text + case subText case selectedAccentColor case backgroundColor case selected @@ -31,6 +33,7 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) text = try typeContainer.decode(String.self, forKey: .text) + subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText) if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) { selectedAccentColor = color } @@ -52,6 +55,8 @@ import Foundation public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(text, forKey: .text) + try container.encodeIfPresent(subText, forKey: .subText) try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(selected, forKey: .selected) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift index 90a0d684..ec0689bc 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -56,9 +56,15 @@ open class RadioBoxes: View { @objc override open func updateView(_ size: CGFloat) { collectionView.collectionViewLayout.invalidateLayout() DispatchQueue.main.async { [weak self] in - self?.setNeedsDisplay() - self?.collectionView.layoutIfNeeded() - self?.collectionView.reloadData() + guard let self = self else { return } + self.setNeedsDisplay() + self.collectionView.layoutIfNeeded() + self.collectionView.reloadData() + guard let firstSelectedIndex = self.boxes?.firstIndex(where: {$0.selected == true}) else { + return + } + self.collectionView.selectItem(at: IndexPath(item: firstSelectedIndex, section: 0), animated: true, scrollPosition: .centeredHorizontally) + } } From d66da2d2b831e92cc85d2e208125d3cf52a9fef5 Mon Sep 17 00:00:00 2001 From: "Suresh, Kamlesh" Date: Wed, 8 Apr 2020 11:16:41 -0400 Subject: [PATCH 12/38] fix --- MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift | 3 --- MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift index 3d52e63b..3ee1a9a2 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift @@ -134,9 +134,6 @@ import UIKit self.delegateObject = delegateObject isSelected = model.state - if model.state { - print("log") - } RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift index 99e15280..8863e9fa 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift @@ -47,6 +47,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { public init(_ state: Bool) { self.state = state + baseValue = state } //-------------------------------------------------- @@ -74,6 +75,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + baseValue = state fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName From 8c9ab45951a935c30d97bd055c8d34ed3af5116f Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 16:14:40 -0400 Subject: [PATCH 13/38] Fixes for sizing and base classes --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../Atomic/Templates/CollectionTemplate.swift | 11 +- .../BaseClasses/CollectionViewCell.swift | 2 +- .../ContainerCollectionReusableView.swift | 10 +- ...ProgrammaticCollectionViewController.swift | 80 ++++++ .../ProgrammaticTableViewController.swift | 5 + .../ThreeLayerCollectionViewController.swift | 249 ++++++------------ .../BaseControllers/ViewController.swift | 18 +- MVMCoreUI/Utility/MVMCoreUIUtility.m | 2 +- 9 files changed, 193 insertions(+), 188 deletions(-) create mode 100644 MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 6db35f4e..df6cdbf6 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -250,6 +250,7 @@ D264FA8E243BCD9A00D98315 /* CollectionTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */; }; D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */; }; D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */; }; + D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */; }; D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; @@ -656,6 +657,7 @@ D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplate.swift; sourceTree = ""; }; D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCollectionViewController.swift; sourceTree = ""; }; D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerCollectionReusableView.swift; sourceTree = ""; }; + D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgrammaticCollectionViewController.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; @@ -1485,6 +1487,7 @@ D2A92881241AAB67004E01C6 /* ScrollingViewController.swift */, D2A92883241ACB25004E01C6 /* ProgrammaticScrollViewController.swift */, D2A92885241ACD99004E01C6 /* ProgrammaticTableViewController.swift */, + D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */, ); path = BaseControllers; sourceTree = ""; @@ -2162,6 +2165,7 @@ C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, 94AF4A3F23E9D13900676048 /* MFCaretButton.m in Sources */, + D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index 11142532..d8812119 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -64,7 +64,6 @@ import Foundation } open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer) -> Bool { - guard super.shouldFinishProcessingLoad(loadObject, error: error) else { return false } // This template requires atleast one of the three layers. @@ -103,8 +102,8 @@ import Foundation } open override func numberOfSections(in collectionView: UICollectionView) -> Int { - return 1 - } + return 1 + } open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let moleculeInfo = moleculesInfo?[indexPath.row] @@ -124,7 +123,7 @@ import Foundation //-------------------------------------------------- /// Returns the (identifier, class) of the molecule for the given map. - func getMoleculeInfo(with item: (CollectionItemModelProtocol & MoleculeModelProtocol)?) -> (identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)? { + open func getMoleculeInfo(with item: (CollectionItemModelProtocol & MoleculeModelProtocol)?) -> (identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)? { guard let item = item, let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(item) else { return nil } let moleculeName = moleculeClass.nameForReuse(with: item, delegateObjectIVar) ?? item.moleculeName @@ -132,7 +131,7 @@ import Foundation } /// Sets up the molecule list and ensures no errors loading all content. - func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? { + open func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? { var moleculeList: [(identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)] = [] @@ -148,7 +147,7 @@ import Foundation } /// Sets up the header, footer, molecule list and ensures no errors loading all content. - func setup() { + open func setup() { moleculesInfo = getMoleculeInfoList() } diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index 50cece5d..fb5b1a5e 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -46,7 +46,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo open func updateView(_ size: CGFloat) { containerHelper.updateViewMargins(contentView, model: model, size: size) DispatchQueue.main.async { - print("\(self.contentView.directionalLayoutMargins.leading)") + print("leading \(self.contentView.directionalLayoutMargins.leading)") } } diff --git a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift index c578c273..a0e80f21 100644 --- a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift +++ b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift @@ -14,12 +14,16 @@ public class ContainerCollectionReusableView: UICollectionReusableView { var bottomConstraint: NSLayoutConstraint? public func addAndContain(view: UIView) { + guard self.view != view else { return } self.view?.removeFromSuperview() view.setContentCompressionResistancePriority(.required, for: .vertical) addSubview(view) self.view = view - let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: view) - topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint - bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint + topConstraint = view.topAnchor.constraint(equalTo: topAnchor) + topConstraint?.isActive = true + bottomConstraint = bottomAnchor.constraint(equalTo: view.bottomAnchor) + bottomConstraint?.isActive = true + rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + view.leftAnchor.constraint(equalTo: leftAnchor).isActive = true } } diff --git a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift new file mode 100644 index 00000000..0dd473e2 --- /dev/null +++ b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift @@ -0,0 +1,80 @@ +// +// ProgrammaticCollectionViewController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/8/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc open class ProgrammaticCollectionViewController: ScrollingViewController { + + public var collectionView: UICollectionView? + + open override func loadView() { + let view = UIView() + view.backgroundColor = .white + + let collection = createCollectionView() + view.addSubview(collection) + NSLayoutConstraint.constraintPinSubview(toSuperview: collection) + + collectionView = collection + scrollView = collectionView + self.view = view + } + + /// A place to register cells with the collectionView + open func registerCells() {} + + open override func viewDidLoad() { + super.viewDidLoad() + registerCells() + } + + /// Creates the layout for the collection. + open func createCollectionViewLayout() -> UICollectionViewLayout { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 0 + layout.minimumInteritemSpacing = 0 + layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + return layout + } + + /// Creates the collection view. + open func createCollectionView() -> UICollectionView { + let collection = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) + collection.translatesAutoresizingMaskIntoConstraints = false + collection.dataSource = self + collection.delegate = self + collection.showsHorizontalScrollIndicator = false + collection.backgroundColor = .white + collection.isAccessibilityElement = false + collection.contentInsetAdjustmentBehavior = .always + return collection + } + + deinit { + collectionView?.delegate = nil + collectionView?.dataSource = nil + } +} + +extension ProgrammaticCollectionViewController: UICollectionViewDataSource { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 0 + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + return UICollectionViewCell() + } + + open func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } +} + +extension ProgrammaticCollectionViewController: UICollectionViewDelegate { +} diff --git a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift index 4e262398..b27845b3 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift @@ -42,6 +42,11 @@ open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UI self.view = view } + open override func viewDidLoad() { + super.viewDidLoad() + registerWithTable() + } + /// This class should create the table view that will be used here. Subclass this for different table styles. open func createTableView() -> UITableView { let tableView = UITableView(frame: .zero, style: .grouped) diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index 74877925..62c47d6e 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -8,73 +8,18 @@ import Foundation -@objc open class ThreeLayerCollectionViewController: ScrollingViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { - public var collectionView: UICollectionView? +@objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout { // The three main views private var topView: UIView? private var bottomView: UIView? private var headerView: ContainerCollectionReusableView? private var footerView: ContainerCollectionReusableView? - var useMargins: Bool = true private let headerID = "header" private let footerID = "footer" - //MARK: - MFViewController - open override func updateViews() { - super.updateViews() - let width = view.bounds.width - if let topView = topView as? MVMCoreViewProtocol { - topView.updateView(width) - // showHeader(width) - } - if let bottomView = bottomView as? MVMCoreViewProtocol { - bottomView.updateView(width) - //showFooter(width) - } - self.collectionView?.collectionViewLayout.invalidateLayout() - } - - open override func handleNewData() { - super.handleNewData() - createViewForTableHeader() - createViewForTableFooter() - collectionView?.reloadData() - } - - override open func viewDidLoad() { - let collection = createCollectionView() - collectionView = collection - view.addSubview(collection) - NSLayoutConstraint.constraintPinSubview(toSuperview: collection) - scrollView = collectionView - - registerCells() - - super.viewDidLoad() - // Do any additional setup after loading the view. - } - - //MARK: - Spacing - // If both are subclassed to return a value, then the buttons will not be pinned towards the bottom because neither spacing would try to fill the screen. - /// Space between the top view and the table sections, nil to fill. 0 default - open func spaceBelowTopView() -> CGFloat? { - return 0 - } - - /// Space between the bottom view and the table sections, nil to fill. nil default - open func spaceAboveBottomView() -> CGFloat? { - return nil - } - - /// can override to return a minimum fill space. - open func minimumFillSpace() -> CGFloat { - return 0 - } - - open override func updateViewConstraints() { - super.updateViewConstraints() - + /// Updates the padding for flexible space (header or footer) + private func updateFlexibleSpace() { guard let tableView = collectionView else { return } let minimumSpace: CGFloat = minimumFillSpace() @@ -105,29 +50,70 @@ import Foundation currentSpaceForCompare = currentSpace * 2; } - if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 0.1) { + if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 2) { if fillTop && fillBottom { // space both let half = newSpace / 2 headerView?.bottomConstraint?.constant = half footerView?.topConstraint?.constant = half - collectionView?.invalidateIntrinsicContentSize() + collectionView?.collectionViewLayout.invalidateLayout() } 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. headerView?.bottomConstraint?.constant = newSpace - collectionView?.invalidateIntrinsicContentSize() + collectionView?.collectionViewLayout.invalidateLayout() } else if fillBottom { // Only bottom is spaced. - print("newSpace \(newSpace)") footerView?.topConstraint?.constant = newSpace - collectionView?.invalidateIntrinsicContentSize() + collectionView?.collectionViewLayout.invalidateLayout() } } } + + //MARK: - MFViewController + open override func updateViews() { + super.updateViews() + let width = view.bounds.width + if let topView = topView as? MVMCoreViewProtocol { + topView.updateView(width) + } + if let bottomView = bottomView as? MVMCoreViewProtocol { + bottomView.updateView(width) + } + invalidateCollectionLayout() + } + + open override func handleNewData() { + super.handleNewData() + createViewForHeader() + createViewForFooter() + reloadCollectionData() + } + + override open func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + //MARK: - Spacing + // If both are subclassed to return a value, then the buttons will not be pinned towards the bottom because neither spacing would try to fill the screen. + /// Space between the top view and the collection rows, nil to fill. 0 default + open func spaceBelowTopView() -> CGFloat? { + return 0 + } + + /// Space between the bottom view and the collection rows, nil to fill. nil default + open func spaceAboveBottomView() -> CGFloat? { + return nil + } + + /// can override to return a minimum fill space. + open func minimumFillSpace() -> CGFloat { + return 0 + } //MARK: - Header Footer - /// Gets the top view and adds it to a spacing view, headerView, and then calls showHeader. - open func createViewForTableHeader() { + /// Creates the top view. + open func createViewForHeader() { guard let topView = viewForTop() else { self.topView = nil self.headerView = nil @@ -136,8 +122,8 @@ import Foundation self.topView = topView } - /// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter. - open func createViewForTableFooter() { + /// Creates the footer + open func createViewForFooter() { guard let bottomView = viewForBottom() else { self.bottomView = nil self.footerView = nil @@ -145,60 +131,7 @@ import Foundation } self.bottomView = bottomView } - -// /// Takes the current headerView and adds it to the tableHeaderView -// func showHeader(_ sizingWidth: CGFloat?) { -// headerView?.removeFromSuperview() -// tableView?.tableHeaderView = nil -// guard let headerView = headerView else { -// return -// } -// -// // This extra view is needed because of the wonkiness of apple's table header. Things breaks if using autolayout. -// headerView.setNeedsLayout() -// headerView.layoutIfNeeded() -// MVMCoreUIUtility.sizeView(toFit: headerView) -// let tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: MVMCoreUIUtility.getWidth(), height: headerView.frame.height)) -// tableHeaderView.addSubview(headerView) -// NSLayoutConstraint.constraintPinSubview(toSuperview: headerView) -// tableView?.tableHeaderView = tableHeaderView -// } -// -// /// Takes the current footerView and adds it to the tableFooterView -// func showFooter(_ sizingWidth: CGFloat?) { -// footerView?.removeFromSuperview() -// safeAreaView?.removeFromSuperview() -// guard let footerView = footerView, let tableView = tableView else { -// return -// } -// -// if bottomViewOutsideOfScrollArea { -// // put bottom view outside of scrolling area. -// bottomConstraint?.isActive = false -// view.addSubview(footerView) -// footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true -// footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true -// view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true -// view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true -// safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) -// safeAreaView?.backgroundColor = bottomView?.backgroundColor -// } else { -// bottomConstraint?.isActive = true -// var y: CGFloat? -// if let tableFooterView = tableView.tableFooterView { -// // if footer already exists, use the same y location to avoid strange moving animation -// y = tableFooterView.frame.minY -// } -// -// // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. -// MVMCoreUIUtility.sizeView(toFit: footerView) -// let tableFooterView = UIView(frame: CGRect(x: 0, y: y ?? 0, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) -// tableFooterView.addSubview(footerView) -// NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) -// tableView.tableFooterView = tableFooterView -// } -// } - + //MARK: - Functions to subclass /// Subclass for a top view. open func viewForTop() -> UIView? { @@ -210,85 +143,65 @@ import Foundation return nil } - open func createCollectionViewLayout() -> UICollectionViewLayout { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .vertical - layout.minimumLineSpacing = 0 - layout.minimumInteritemSpacing = 0 - layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - return layout - } - - open func createCollectionView() -> UICollectionView { - let collection = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) - collection.translatesAutoresizingMaskIntoConstraints = false - collection.dataSource = self - collection.delegate = self - collection.showsHorizontalScrollIndicator = false - collection.backgroundColor = .white - collection.isAccessibilityElement = false - collection.contentInsetAdjustmentBehavior = .always - return collection - } - - deinit { - collectionView?.delegate = nil - collectionView?.dataSource = nil - } - //MARK: - Collection - open func registerCells() { + /// Should be used to refresh the layout of the collection view. Updates flexible padding. + open func invalidateCollectionLayout() { + self.collectionView?.collectionViewLayout.invalidateLayout() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { + self.updateFlexibleSpace() + }) + } + + /// Should be used to reload the data of the collection view. Updates flexible padding. + open func reloadCollectionData() { + collectionView?.reloadData() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { + self.updateFlexibleSpace() + }) + } + + open override func registerCells() { + super.registerCells() collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID) collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: footerID) } - open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 0 } - open func numberOfSections(in collectionView: UICollectionView) -> Int { - return 1 - } - - open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionItem", for: indexPath) as! MoleculeCollectionViewCell - let labelModel = LabelModel(text: "hello") - let model = MoleculeCollectionItemModel(with: labelModel) - cell.set(with: model, delegateObjectIVar, nil) - return cell - } - open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { - guard let view = headerView ?? topView, + guard let _ = topView, section == 0 else { return .zero } + let header = headerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: section)) // Use this view to calculate the optimal size based on the collection view's width - return view.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), + return header.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, // Width is fixed verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { - guard let view = footerView ?? bottomView, + guard let _ = bottomView, section == numberOfSections(in: collectionView) - 1 else { return .zero } + let footer = footerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: section)) // Use this view to calculate the optimal size based on the collection view's width - let size = view.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), + let size = footer.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, // Width is fixed verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed - print("SIZEEE \(size.height) \(String(describing: footerView?.topConstraint?.constant))") return size } open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { - if (kind == UICollectionView.elementKindSectionFooter) { + if kind == UICollectionView.elementKindSectionFooter { let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as! ContainerCollectionReusableView footerView.addAndContain(view: bottomView!) footerView.topConstraint?.constant = spaceAboveBottomView() ?? 0 self.footerView = footerView return footerView - } else if (kind == UICollectionView.elementKindSectionHeader) { + } else if kind == UICollectionView.elementKindSectionHeader { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as! ContainerCollectionReusableView headerView.addAndContain(view: topView!) headerView.bottomConstraint?.constant = spaceBelowTopView() ?? 0 diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 5bc66109..4e4d289d 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -24,7 +24,7 @@ import UIKit public var formValidator: FormValidator? - public var needsUpdateUI = true + public var needsUpdateUI = false private var observingForResponses = false private var initialLoadFinished = false private var previousScreenSize = CGSize.zero @@ -155,8 +155,8 @@ import UIKit /// Calls processNewData and then sets the ui to update with updateView open func handleNewDataAndUpdateUI() { handleNewData() - self.needsUpdateUI = true - self.view.setNeedsLayout() + needsUpdateUI = true + view.setNeedsLayout() } /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, @@ -170,9 +170,9 @@ import UIKit navigationModel.line = LineModel(type: .none) } pageModel?.navigationItem = navigationModel - if self.formValidator == nil { + if formValidator == nil { let rules = pageModel?.formRules - self.formValidator = FormValidator(rules) + formValidator = FormValidator(rules) } } @@ -268,10 +268,10 @@ import UIKit initialLoad() } - // Handle data on load - handleNewData() - - view.setNeedsLayout() + // Handle data for first load. Dispatched to allow subclasses to finish their view did load implementations. + DispatchQueue.main.async { + self.handleNewDataAndUpdateUI() + } } open override func viewDidLayoutSubviews() { diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index 59c55cc6..b502467d 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -168,8 +168,8 @@ topInset = scrollview.adjustedContentInset.top; bottomInset = scrollview.adjustedContentInset.bottom; } - CGFloat remainingSpace = frameHeight - contentSizeHeight - topInset - bottomInset; + NSLog(@"scc SPACCEEE remaining: %f frame: %f, content: %f, insets:(%f,%f)",remainingSpace,frameHeight,contentSizeHeight,topInset,bottomInset); return remainingSpace - 1; } From 9178385e6d9ab2d85aa1bb400412efb76eb08b19 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 16:16:16 -0400 Subject: [PATCH 14/38] comments --- .../BaseControllers/ThreeLayerCollectionViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index 62c47d6e..af9fce4c 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -148,6 +148,7 @@ import Foundation /// Should be used to refresh the layout of the collection view. Updates flexible padding. open func invalidateCollectionLayout() { self.collectionView?.collectionViewLayout.invalidateLayout() + // TODO: Improve this workaround (autolayout for cells happens async so contentSize isn't accurate immediately) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { self.updateFlexibleSpace() }) @@ -156,6 +157,7 @@ import Foundation /// Should be used to reload the data of the collection view. Updates flexible padding. open func reloadCollectionData() { collectionView?.reloadData() + // TODO: Improve this workaround (autolayout for cells happens async so contentSize isn't accurate immediately) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { self.updateFlexibleSpace() }) From e4a6a1ddf0816a0f610c646a95109fb3affaa0ad Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 16:18:05 -0400 Subject: [PATCH 15/38] remove code for kevin pr --- .../Atomic/Molecules/Doughnut/DoughnutChartModel.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift index 2b64d1ae..ff9b1bf6 100644 --- a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift @@ -35,11 +35,4 @@ import Foundation self.color = color self.label = label } - - private enum CodingKeys: String, CodingKey { - case backgroundColor - case label - case percent - case color - } } From 8a9d85a83c8523cd9df2dadcdd3dfd72e78a17fe Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 16:24:17 -0400 Subject: [PATCH 16/38] comments --- .../BaseControllers/ContainerCollectionReusableView.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift index a0e80f21..d7fb0ed4 100644 --- a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift +++ b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift @@ -8,10 +8,11 @@ import Foundation +/// CollectionReusableView that can contains any other view. public class ContainerCollectionReusableView: UICollectionReusableView { - var view: UIView? - var topConstraint: NSLayoutConstraint? - var bottomConstraint: NSLayoutConstraint? + public var view: UIView? + public var topConstraint: NSLayoutConstraint? + public var bottomConstraint: NSLayoutConstraint? public func addAndContain(view: UIView) { guard self.view != view else { return } From c6b8eacff48343620b23ac6a5bc4c8b9ce24bfbd Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 16:36:46 -0400 Subject: [PATCH 17/38] update fix --- MVMCoreUI/BaseClasses/CollectionViewCell.swift | 4 +--- .../ThreeLayerCollectionViewController.swift | 11 ++++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index fb5b1a5e..e7bb7893 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -45,9 +45,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo open func updateView(_ size: CGFloat) { containerHelper.updateViewMargins(contentView, model: model, size: size) - DispatchQueue.main.async { - print("leading \(self.contentView.directionalLayoutMargins.leading)") - } + (molecule as? MVMCoreViewProtocol)?.updateView(size) } open func reset() { diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index af9fce4c..bf5520b9 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -8,9 +8,9 @@ import Foundation +/// A view controller that has three main layers, a header, collection rows, and a footer. The header is added as a supplement header to the first section, and the footer is added as a supplement footer to the last section. This view controller allows for flexible space between the three layers to fit the screeen. @objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout { - // The three main views private var topView: UIView? private var bottomView: UIView? private var headerView: ContainerCollectionReusableView? @@ -69,7 +69,7 @@ import Foundation } } - //MARK: - MFViewController + //MARK: - ViewController open override func updateViews() { super.updateViews() let width = view.bounds.width @@ -79,6 +79,11 @@ import Foundation if let bottomView = bottomView as? MVMCoreViewProtocol { bottomView.updateView(width) } + if let cells = collectionView?.visibleCells { + for cell in cells { + (cell as? MVMCoreViewProtocol)?.updateView(width) + } + } invalidateCollectionLayout() } @@ -106,7 +111,7 @@ import Foundation return nil } - /// can override to return a minimum fill space. + /// can override to return a minimum fill space. 0 default open func minimumFillSpace() -> CGFloat { return 0 } From 4941c51be3627eb2f5fa8da8a2c0f36524b9efdd Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 16:47:20 -0400 Subject: [PATCH 18/38] update to make collection less restrictive --- .../Items/CollectionItemModelProtocol.swift | 2 +- .../Items/MoleculeCollectionItemModel.swift | 2 +- .../BaseClasses/CollectionViewCell.swift | 9 +++++-- ...ProgrammaticCollectionViewController.swift | 1 + .../ThreeLayerCollectionViewController.swift | 25 ++++++++----------- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift b/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift index c8bfb7d7..9361ab1e 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift @@ -8,6 +8,6 @@ import Foundation -public protocol CollectionItemModelProtocol: ContainerModelProtocol { +public protocol CollectionItemModelProtocol { } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index 8d236f4b..b3d2aba6 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -9,7 +9,7 @@ import Foundation -@objcMembers open class MoleculeCollectionItemModel: CollectionItemModelProtocol, MoleculeModelProtocol { +@objcMembers open class MoleculeCollectionItemModel: CollectionItemModelProtocol, ContainerModelProtocol, MoleculeModelProtocol { open class var identifier: String { return "collectionItem" } diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index e7bb7893..e9857808 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -8,6 +8,7 @@ import Foundation +/// A base collection view cell with basic mvm functionality. open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol { // Convenience helpers @@ -44,7 +45,9 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo } open func updateView(_ size: CGFloat) { - containerHelper.updateViewMargins(contentView, model: model, size: size) + if let model = model as? ContainerModelProtocol { + containerHelper.updateViewMargins(contentView, model: model, size: size) + } (molecule as? MVMCoreViewProtocol)?.updateView(size) } @@ -63,7 +66,9 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo } // align if needed. - containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol) + if let model = model as? ContainerModelProtocol { + containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol) + } } /// Convenience function. Adds the molecule to the view. diff --git a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift index 0dd473e2..9d279d79 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift @@ -8,6 +8,7 @@ import Foundation +/// A base view controller with a collection view. @objc open class ProgrammaticCollectionViewController: ScrollingViewController { public var collectionView: UICollectionView? diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index bf5520b9..82dda260 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -181,35 +181,30 @@ import Foundation open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { guard let _ = topView, section == 0 else { return .zero } + + // Calculate the height of the header since apple doesn't support autolayout. Width is fixed, height is tall as content. let header = headerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: section)) - - // Use this view to calculate the optimal size based on the collection view's width - return header.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), - withHorizontalFittingPriority: .required, // Width is fixed - verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed + return header.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { guard let _ = bottomView, section == numberOfSections(in: collectionView) - 1 else { return .zero } + + // Calculate the height of the footr since apple doesn't support autolayout. Width is fixed, height is tall as content. let footer = footerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: section)) - - // Use this view to calculate the optimal size based on the collection view's width - let size = footer.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), - withHorizontalFittingPriority: .required, // Width is fixed - verticalFittingPriority: .fittingSizeLevel) // Height can be as large as needed - return size + return footer.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) } open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { - if kind == UICollectionView.elementKindSectionFooter { - let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as! ContainerCollectionReusableView + if kind == UICollectionView.elementKindSectionFooter, + let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as? ContainerCollectionReusableView { footerView.addAndContain(view: bottomView!) footerView.topConstraint?.constant = spaceAboveBottomView() ?? 0 self.footerView = footerView return footerView - } else if kind == UICollectionView.elementKindSectionHeader { - let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as! ContainerCollectionReusableView + } else if kind == UICollectionView.elementKindSectionHeader, + let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? ContainerCollectionReusableView { headerView.addAndContain(view: topView!) headerView.bottomConstraint?.constant = spaceBelowTopView() ?? 0 self.headerView = headerView From ce8cc3d430e7ae7f0161d66fcfe9aa22ac3dcde4 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 16:51:30 -0400 Subject: [PATCH 19/38] comments --- MVMCoreUI.xcodeproj/project.pbxproj | 2 +- .../Atomic/Molecules/Items/MoleculeCollectionItemModel.swift | 2 +- .../Atomic/Molecules/Items/MoleculeCollectionViewCell.swift | 1 + MVMCoreUI/BaseClasses/CollectionViewCell.swift | 5 ++++- .../Protocols}/CollectionItemModelProtocol.swift | 0 5 files changed, 7 insertions(+), 3 deletions(-) rename MVMCoreUI/{Atomic/Molecules/Items => BaseClasses/Protocols}/CollectionItemModelProtocol.swift (100%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index df6cdbf6..9c49c057 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -914,6 +914,7 @@ 0A5D59C323AD488600EFD9E9 /* Protocols */ = { isa = PBXGroup; children = ( + D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */, 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */, ); path = Protocols; @@ -1265,7 +1266,6 @@ D260105E23D0BFFC00764D80 /* StackItem.swift */, 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */, D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */, - D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */, D21B7F76243BB70700051ABF /* MoleculeCollectionItemModel.swift */, D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */, diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index b3d2aba6..b9ac345b 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -8,7 +8,7 @@ import Foundation - +/// A model for a collection item that is a container for any molecule. @objcMembers open class MoleculeCollectionItemModel: CollectionItemModelProtocol, ContainerModelProtocol, MoleculeModelProtocol { open class var identifier: String { return "collectionItem" diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift index 0ae515f5..1f615732 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift @@ -8,6 +8,7 @@ import UIKit +/// A collection item that is a container for any molecule. open class MoleculeCollectionViewCell: CollectionViewCell { open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index e9857808..a559c93e 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -18,6 +18,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo private var initialSetupPerformed = false + // MARK: - Inits public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() @@ -35,6 +36,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo } } + // MARK: - MVMCoreViewProtocol open func setupView() { isAccessibilityElement = false contentView.isAccessibilityElement = false @@ -56,6 +58,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo backgroundColor = .white } + // MARK: - MoleculeViewProtocol open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let model = model as? CollectionItemModelProtocol else { return } self.model = model @@ -71,7 +74,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo } } - /// Convenience function. Adds the molecule to the view. + /// Convenience function. Adds a molecule to the view. open func addMolecule(_ molecule: MoleculeViewProtocol) { contentView.addSubview(molecule) containerHelper.constrainView(molecule) diff --git a/MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift b/MVMCoreUI/BaseClasses/Protocols/CollectionItemModelProtocol.swift similarity index 100% rename from MVMCoreUI/Atomic/Molecules/Items/CollectionItemModelProtocol.swift rename to MVMCoreUI/BaseClasses/Protocols/CollectionItemModelProtocol.swift From 40efdc4968e23493376aac7c9f0018e1a133ea3f Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 8 Apr 2020 17:24:38 -0400 Subject: [PATCH 20/38] re-order --- MVMCoreUI/Atomic/Templates/CollectionTemplate.swift | 4 ++-- MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index d8812119..0bbcd45f 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -79,9 +79,9 @@ import Foundation open override func handleNewData() { - super.handleNewData() setup() - registerCells() + registerCells() + super.handleNewData() } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 7575faf1..b236e332 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -81,9 +81,9 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } open override func handleNewData() { - super.handleNewData() setup() registerWithTable() + super.handleNewData() } //-------------------------------------------------- From 03438a116587d0ebfd7f6e7b7e269f2e4a5fccb4 Mon Sep 17 00:00:00 2001 From: "Suresh, Kamlesh" Date: Wed, 8 Apr 2020 22:33:14 -0400 Subject: [PATCH 21/38] reuse fixes --- .../Buttons/RadioButtonSelectionHelper.swift | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift index dbc3f7fe..2273785f 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift @@ -13,46 +13,50 @@ import UIKit public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName private var selectedRadioButton: RadioButton? - private var fieldGroupName: String? + private var selectedRadioButtonModel: RadioButtonModel? public var baseValue: AnyHashable? - init(_ radioButtonModel: RadioButtonModel?) { - self.fieldKey = radioButtonModel?.fieldKey - if radioButtonModel?.state ?? false { - self.baseValue = radioButtonModel?.formFieldValue() + public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) { + self.fieldKey = radioButtonModel.fieldKey + if radioButtonModel.state { + if self.baseValue == nil { + self.baseValue = radioButtonModel.fieldValue + } + selectedRadioButtonModel = radioButtonModel + + // Below code is needed for cell resuse scenario. + radioButton.isSelected = true + selectedRadioButton = radioButton + } else { + radioButton.isSelected = false } } - + public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton, delegateObject: MVMCoreUIDelegateObject?) { guard let groupName = radioButtonModel.fieldKey, - let formValidator = delegateObject?.formHolderDelegate?.formValidator else { - return + let formValidator = delegateObject?.formHolderDelegate?.formValidator else { + return } - - let radioButtonSelectionHelper = formValidator.radioButtonsModelByGroup[groupName] ?? RadioButtonSelectionHelper(radioButtonModel) - radioButtonSelectionHelper.fieldGroupName = radioButtonModel.fieldKey - formValidator.radioButtonsModelByGroup[groupName] = radioButtonSelectionHelper - if radioButtonModel.state { - radioButtonSelectionHelper.selectedRadioButton = radioButton - } + let radioButtonSelectionHelper = formValidator.radioButtonsModelByGroup[groupName] ?? RadioButtonSelectionHelper() + radioButtonSelectionHelper.set(radioButtonModel, radioButton) + formValidator.radioButtonsModelByGroup[groupName] = radioButtonSelectionHelper FormValidator.setupValidation(for: radioButtonSelectionHelper, delegate: delegateObject?.formHolderDelegate) } public func selected(_ radioButton: RadioButton) { selectedRadioButton?.isSelected = false + selectedRadioButtonModel?.state = false selectedRadioButton = radioButton + selectedRadioButton?.isSelected = true + selectedRadioButtonModel = selectedRadioButton?.radioModel } } // MARK: - FormValidationFormFieldProtocol -extension RadioButtonSelectionHelper { - public func formFieldGroupName() -> String? { - return selectedRadioButton?.formFieldGroupName() ?? self.fieldGroupName - } - +extension RadioButtonSelectionHelper { public func formFieldValue() -> AnyHashable? { - return selectedRadioButton?.formFieldValue() + return selectedRadioButtonModel?.fieldValue } } From 7cba93e9f571672d462328cc06ead3e82caab0bd Mon Sep 17 00:00:00 2001 From: Damodaram <> Date: Thu, 9 Apr 2020 15:24:08 +0530 Subject: [PATCH 22/38] confluence changes Updated --- .../Views/RadioBoxCollectionViewCell.swift | 1 - .../Atomic/Atoms/Views/RadioBoxModel.swift | 11 ++++++++++ MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 22 +++++++++++++++---- .../Atomic/Atoms/Views/RadioBoxesModel.swift | 9 +------- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift index d959285d..419f278c 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift @@ -135,7 +135,6 @@ open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco lineView.backgroundColor = collectionModel.selectedAccentColor?.uiColor bodyLabel.text = collectionModel.text subTextLabel.text = collectionModel.subText -// isSelected = collectionModel.selected fieldValue = collectionModel.fieldValue isOutOfStock = collectionModel.strikethrough diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift index 4ce1e302..0c119ed8 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -17,6 +17,8 @@ import Foundation public var enabled: Bool = true public var strikethrough: Bool = false public var fieldValue: String? + public var fieldKey: String? + public var groupName: String? private enum CodingKeys: String, CodingKey { case moleculeName @@ -28,6 +30,9 @@ import Foundation case enabled case strikethrough case fieldValue + case fieldKey + case groupName + } required public init(from decoder: Decoder) throws { @@ -50,6 +55,9 @@ import Foundation strikethrough = isStrikeTrough } fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) + } public func encode(to encoder: Encoder) throws { @@ -63,5 +71,8 @@ import Foundation try container.encodeIfPresent(enabled, forKey: .enabled) try container.encodeIfPresent(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(groupName, forKey: .groupName) + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift index ec0689bc..d0cae6b4 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -14,13 +14,18 @@ open class RadioBoxes: View { /// The models for the molecules. var boxes: [RadioBoxModel]? - public var fieldKey: String? - public var groupName: String? public var collectionViewHeight: NSLayoutConstraint? private let boxWidth: Double = 151.0 private let boxHeight: Double = 64.0 private let itemSpacing: Double = 10.0 private let leadingSpacing: Double = 0 + + public var selectedBox: RadioBoxModel? { + get{ + guard let selectedItem = collectionView.indexPathsForSelectedItems?.first else {return nil} + return boxes?[selectedItem.item] + } + } // MARK: - MVMCoreViewProtocol open override func setupView() { @@ -45,8 +50,6 @@ open class RadioBoxes: View { super.set(with: model, delegateObject, additionalData) guard let radioBoxesModel = model as? RadioBoxesModel else { return } backgroundColor = radioBoxesModel.backgroundColor?.uiColor - fieldKey = radioBoxesModel.fieldKey - groupName = radioBoxesModel.groupName registerCells() setupLayout(with: radioBoxesModel) prepareMolecules(with: radioBoxesModel) @@ -119,3 +122,14 @@ extension RadioBoxes: UICollectionViewDataSource { return cell } } +extension RadioBoxes: UICollectionViewDelegate { + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let boxItem = boxes?[indexPath.row] else { return } + boxItem.selected = true + } + public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + guard let boxItem = boxes?[indexPath.row] else { return } + boxItem.selected = false + } +} + diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift index 4e690ffa..d50978a1 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift @@ -12,16 +12,13 @@ import Foundation public var backgroundColor: Color? = Color(uiColor: .white) public var selectedAccentColor: Color? = Color(uiColor: .red) public var boxes: [RadioBoxModel] - public var fieldKey: String? - public var groupName: String? private enum CodingKeys: String, CodingKey { case moleculeName case selectedAccentColor case backgroundColor case boxes - case fieldKey - case groupName + } required public init(from decoder: Decoder) throws { @@ -33,8 +30,6 @@ import Foundation backgroundColor = color } boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) - fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) } public func encode(to encoder: Encoder) throws { @@ -42,7 +37,5 @@ import Foundation try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encodeIfPresent(groupName, forKey: .groupName) } } From db0b953bad1fc5af048cd939fb85f548e3584a54 Mon Sep 17 00:00:00 2001 From: "Suresh, Kamlesh" Date: Thu, 9 Apr 2020 10:35:58 -0400 Subject: [PATCH 23/38] fixes --- .../Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift index 2273785f..60c79465 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift @@ -19,7 +19,8 @@ import UIKit public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) { self.fieldKey = radioButtonModel.fieldKey if radioButtonModel.state { - if self.baseValue == nil { + if self.baseValue == nil, + let selected = radioButtonModel.baseValue as? Bool, selected { self.baseValue = radioButtonModel.fieldValue } selectedRadioButtonModel = radioButtonModel From b7edafc5d885a21f98754411d98f74dcf1e6746d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 9 Apr 2020 11:17:10 -0400 Subject: [PATCH 24/38] columns --- MVMCoreUI.xcodeproj/project.pbxproj | 4 +++ .../Atomic/Templates/CollectionTemplate.swift | 25 +++++++++++++-- .../CollectionTemplateItemProtocol.swift | 29 +++++++++++++++++ .../BaseClasses/CollectionViewCell.swift | 15 ++++++++- .../ThreeLayerCollectionViewController.swift | 32 ++++++++++++------- MVMCoreUI/Utility/MVMCoreUIUtility.m | 1 - 6 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 4dd0bb20..eeb8862a 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -257,6 +257,7 @@ D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */; }; D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */; }; D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */; }; + D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */; }; D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; @@ -670,6 +671,7 @@ D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCollectionViewController.swift; sourceTree = ""; }; D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerCollectionReusableView.swift; sourceTree = ""; }; D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgrammaticCollectionViewController.swift; sourceTree = ""; }; + D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplateItemProtocol.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; @@ -1442,6 +1444,7 @@ 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */, 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, + D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */, D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */, D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */, ); @@ -2237,6 +2240,7 @@ 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */, + D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */, D243859923A16B1800332775 /* Container.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index 0bbcd45f..1d2f07a6 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -83,11 +83,23 @@ import Foundation registerCells() super.handleNewData() } - + //-------------------------------------------------- // MARK: - Collection //-------------------------------------------------- + open override func update(cell: UICollectionViewCell, size: CGFloat) { + super.update(cell: cell, size: size) + + // Update the width for columns. + if let collectionView = collectionView, + let columns = templateModel?.columns, columns > 0, + let cell = cell as? CollectionTemplateItemProtocol { + let width = (size - collectionView.adjustedContentInset.left - collectionView.adjustedContentInset.right) / CGFloat(columns) + cell.set(width: width) + } + } + open override func registerCells() { super.registerCells() guard let moleculesInfo = moleculesInfo else { return } @@ -111,13 +123,20 @@ import Foundation let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath) (cell as? MoleculeViewProtocol)?.reset() (cell as? MoleculeViewProtocol)?.set(with: moleculeInfo.molecule, delegateObjectIVar, nil) - (cell as? MVMCoreViewProtocol)?.updateView(view.bounds.width) - + update(cell: cell, size: view.frame.width) // Neded to fix an apple defect where the cell is not the correct size on certain devices for certain cells cell.layoutIfNeeded() return cell } + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + (collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil) + } + + public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + (cell as? CollectionTemplateItemProtocol)?.willDisplay() + } + //-------------------------------------------------- // MARK: - Convenience //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift new file mode 100644 index 00000000..55db2c6d --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift @@ -0,0 +1,29 @@ +// +// CollectionTemplateItemProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/9/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// A protocol that items of the CollectionTemplate must conform to. +public protocol CollectionTemplateItemProtocol: UICollectionViewCell { + + /// Set the width of the item. Used for the columns functionality + func set(width: CGFloat) + + /// Handle action when cell is pressed + func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) + + /// Called when the cell will display. + func willDisplay() +} + +// Default implementation does nothing +extension CollectionTemplateItemProtocol { + public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {} + + public func willDisplay() {} +} diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index a559c93e..58bd06ff 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -9,12 +9,13 @@ import Foundation /// A base collection view cell with basic mvm functionality. -open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol { +open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol, CollectionTemplateItemProtocol { // Convenience helpers open var molecule: MoleculeViewProtocol? public let containerHelper = ContainerHelper() open var model: CollectionItemModelProtocol? + open var widthConstraint: NSLayoutConstraint? private var initialSetupPerformed = false @@ -56,6 +57,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo open func reset() { molecule?.reset() backgroundColor = .white + widthConstraint?.isActive = false } // MARK: - MoleculeViewProtocol @@ -80,4 +82,15 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo containerHelper.constrainView(molecule) self.molecule = molecule } + + // MARK: - CollectionTemplateItemProtocol + public func set(width: CGFloat) { + if let widthConstraint = widthConstraint { + widthConstraint.constant = width + widthConstraint.isActive = true + } else { + widthConstraint = contentView.widthAnchor.constraint(equalToConstant: width) + widthConstraint?.isActive = true + } + } } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index 82dda260..aa34f2f2 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -50,7 +50,7 @@ import Foundation currentSpaceForCompare = currentSpace * 2; } - if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 2) { + if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 1) { if fillTop && fillBottom { // space both let half = newSpace / 2 @@ -72,19 +72,22 @@ import Foundation //MARK: - ViewController open override func updateViews() { super.updateViews() - let width = view.bounds.width - if let topView = topView as? MVMCoreViewProtocol { - topView.updateView(width) - } - if let bottomView = bottomView as? MVMCoreViewProtocol { - bottomView.updateView(width) - } - if let cells = collectionView?.visibleCells { - for cell in cells { - (cell as? MVMCoreViewProtocol)?.updateView(width) + // Needed due to dispatch in reloadCollectionData. + DispatchQueue.main.async { + let width = self.view.bounds.width + if let topView = self.topView as? MVMCoreViewProtocol { + topView.updateView(width) } + if let bottomView = self.bottomView as? MVMCoreViewProtocol { + bottomView.updateView(width) + } + if let cells = self.collectionView?.visibleCells { + for cell in cells { + self.update(cell: cell, size: width) + } + } + self.invalidateCollectionLayout() } - invalidateCollectionLayout() } open override func handleNewData() { @@ -168,6 +171,11 @@ import Foundation }) } + /// Called in updateView, updates the cell. + open func update(cell: UICollectionViewCell, size: CGFloat) { + (cell as? MVMCoreViewProtocol)?.updateView(size) + } + open override func registerCells() { super.registerCells() collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID) diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index b502467d..0841e248 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -169,7 +169,6 @@ bottomInset = scrollview.adjustedContentInset.bottom; } CGFloat remainingSpace = frameHeight - contentSizeHeight - topInset - bottomInset; - NSLog(@"scc SPACCEEE remaining: %f frame: %f, content: %f, insets:(%f,%f)",remainingSpace,frameHeight,contentSizeHeight,topInset,bottomInset); return remainingSpace - 1; } From 4cac288e5ba420b71524db3d47cc17eaa34174d9 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 9 Apr 2020 11:29:05 -0400 Subject: [PATCH 25/38] carousel loop fix --- MVMCoreUI/Atomic/Organisms/Carousel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 05a28a51..18c2978a 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -294,7 +294,7 @@ extension Carousel: UIScrollViewDelegate { return } - let lastPageIndex = numberOfPages + 1 + let lastPageIndex = numberOfPages + 2 let goToIndex = {(index: Int) in self.goTo(index, animated: false) self.collectionView.layoutIfNeeded() From 4eb4cf97417d2ba2cb590afbe3000d74f483ee47 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 9 Apr 2020 11:37:22 -0400 Subject: [PATCH 26/38] temporary fix loop --- MVMCoreUI/Atomic/Organisms/Carousel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 18c2978a..54dc37e7 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -294,7 +294,7 @@ extension Carousel: UIScrollViewDelegate { return } - let lastPageIndex = numberOfPages + 2 + let lastPageIndex = numberOfPages + 1 let goToIndex = {(index: Int) in self.goTo(index, animated: false) self.collectionView.layoutIfNeeded() @@ -332,7 +332,7 @@ extension Carousel: UIScrollViewDelegate { open func scrollViewDidScroll(_ scrollView: UIScrollView) { // Check if the user is dragging the card even further past the next card. - checkForDraggingOutOfBounds(scrollView) + //checkForDraggingOutOfBounds(scrollView) // Let the pager know our progress if needed. pagingView?.scrollViewDidScroll?(collectionView) From f3224b371ed2a2b0528c59b69b915fa128d299fc Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 9 Apr 2020 12:54:17 -0400 Subject: [PATCH 27/38] comment fix --- .../Items/MoleculeCollectionItemModel.swift | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index b9ac345b..ed9467fd 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -9,22 +9,14 @@ import Foundation /// A model for a collection item that is a container for any molecule. -@objcMembers open class MoleculeCollectionItemModel: CollectionItemModelProtocol, ContainerModelProtocol, MoleculeModelProtocol { +@objcMembers public class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol, MoleculeModelProtocol { open class var identifier: String { return "collectionItem" } - open var molecule: MoleculeModelProtocol public var backgroundColor: Color? - - public var horizontalAlignment: UIStackView.Alignment? - public var verticalAlignment: UIStackView.Alignment? - public var useHorizontalMargins: Bool? - public var useVerticalMargins: Bool? - public var topMarginPadding: CGFloat? - public var bottomMarginPadding: CGFloat? - + /// Defaults to set - open func setDefaults() { + public func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } @@ -41,26 +33,25 @@ import Foundation private enum CodingKeys: String, CodingKey { case moleculeName - case molecule case backgroundColor } - public init(with moleculeModel: MoleculeModelProtocol) { - molecule = moleculeModel + public override init(with moleculeModel: MoleculeModelProtocol) { + super.init(with: moleculeModel) setDefaults() } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - molecule = try typeContainer.decodeModel(codingKey: .molecule) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + try super.init(from: decoder) setDefaults() } - open func encode(to encoder: Encoder) throws { + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeModel(molecule, forKey: .molecule) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } From 57a5dbb064b1f8c10eac1baf4ebf8ff87ba6fec0 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 9 Apr 2020 12:58:26 -0400 Subject: [PATCH 28/38] dress feedback --- MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift | 5 +++++ .../Atomic/Molecules/Items/MoleculeCollectionViewCell.swift | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift index 9cf2a246..3089596a 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift @@ -15,6 +15,11 @@ open class CarouselItem: MoleculeCollectionViewCell { var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) var peakingCover = MVMCoreUICommonViewsUtility.commonView() + open override func addMolecule(_ molecule: MoleculeViewProtocol) { + super.addMolecule(molecule) + contentView.sendSubviewToBack(molecule) + } + open override func setupView() { super.setupView() diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift index 1f615732..42af1a53 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift @@ -17,7 +17,6 @@ open class MoleculeCollectionViewCell: CollectionViewCell { if molecule == nil { if let moleculeView = MoleculeObjectMapping.shared()?.createMolecule(collectionModel.molecule, delegateObject: delegateObject, additionalData: additionalData) { addMolecule(moleculeView) - contentView.sendSubviewToBack(moleculeView) } } else { molecule?.set(with: collectionModel.molecule, delegateObject, additionalData) From 5715d7eb237ab818980a7b96a327131be5b2bde8 Mon Sep 17 00:00:00 2001 From: "Suresh, Kamlesh" Date: Thu, 9 Apr 2020 15:56:00 -0400 Subject: [PATCH 29/38] need groupName --- .../Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift index 60c79465..09936d87 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift @@ -18,6 +18,8 @@ import UIKit public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) { self.fieldKey = radioButtonModel.fieldKey + self.groupName = radioButtonModel.groupName + if radioButtonModel.state { if self.baseValue == nil, let selected = radioButtonModel.baseValue as? Bool, selected { @@ -56,7 +58,7 @@ import UIKit } // MARK: - FormValidationFormFieldProtocol -extension RadioButtonSelectionHelper { +extension RadioButtonSelectionHelper { public func formFieldValue() -> AnyHashable? { return selectedRadioButtonModel?.fieldValue } From 072951f91269c6c1bb74f8086058de537db37b06 Mon Sep 17 00:00:00 2001 From: "Suresh, Kamlesh" Date: Thu, 9 Apr 2020 17:22:43 -0400 Subject: [PATCH 30/38] fixes --- .../Atoms/Buttons/RadioButtonSelectionHelper.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift index 09936d87..6c1da830 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift @@ -19,7 +19,7 @@ import UIKit public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) { self.fieldKey = radioButtonModel.fieldKey self.groupName = radioButtonModel.groupName - + if radioButtonModel.state { if self.baseValue == nil, let selected = radioButtonModel.baseValue as? Bool, selected { @@ -46,12 +46,16 @@ import UIKit formValidator.radioButtonsModelByGroup[groupName] = radioButtonSelectionHelper FormValidator.setupValidation(for: radioButtonSelectionHelper, delegate: delegateObject?.formHolderDelegate) } - + public func selected(_ radioButton: RadioButton) { - selectedRadioButton?.isSelected = false - selectedRadioButtonModel?.state = false + // Checks because the view could be reused + if selectedRadioButton?.radioModel === selectedRadioButtonModel { + selectedRadioButton?.isSelected = false + } else { + selectedRadioButtonModel?.state = false + } + selectedRadioButton = radioButton - selectedRadioButton?.isSelected = true selectedRadioButtonModel = selectedRadioButton?.radioModel } From 185efa547e98df67bd8fab1c1a07e09d3f7bbc9f Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 09:38:00 -0400 Subject: [PATCH 31/38] Feedback push --- .../BaseClasses/CollectionViewCell.swift | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index 58bd06ff..e11b350c 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -15,7 +15,9 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo open var molecule: MoleculeViewProtocol? public let containerHelper = ContainerHelper() open var model: CollectionItemModelProtocol? - open var widthConstraint: NSLayoutConstraint? + + /// The width, used for establishing columns + open var width: CGFloat? private var initialSetupPerformed = false @@ -57,7 +59,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo open func reset() { molecule?.reset() backgroundColor = .white - widthConstraint?.isActive = false + width = nil } // MARK: - MoleculeViewProtocol @@ -85,12 +87,18 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo // MARK: - CollectionTemplateItemProtocol public func set(width: CGFloat) { - if let widthConstraint = widthConstraint { - widthConstraint.constant = width - widthConstraint.isActive = true - } else { - widthConstraint = contentView.widthAnchor.constraint(equalToConstant: width) - widthConstraint?.isActive = true - } + self.width = width } + + // Column logic, set width. + override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) + guard let width = width else { return autoLayoutAttributes } + + let targetSize = CGSize(width: width, height: 0) + let newSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.defaultLow) + let newFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: newSize) + autoLayoutAttributes.frame = newFrame + return autoLayoutAttributes + } } From 8da03356e416acad252fa685ced0e920ddfcff8c Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 11:15:55 -0400 Subject: [PATCH 32/38] Fixing reuse and sizing issues. --- MVMCoreUI/Atomic/Organisms/Carousel.swift | 70 +++++++++++++------ .../Atomic/Organisms/CarouselModel.swift | 22 +++--- .../ThreeLayerTableViewController.swift | 2 +- .../BaseControllers/ViewController.swift | 3 +- 4 files changed, 63 insertions(+), 34 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 54dc37e7..1ed2ecd2 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -54,6 +54,32 @@ open class Carousel: View { private var size: CGFloat? + // Updates the model and index. + public func updateModelIndex() { + (model as? CarouselModel)?.index = pageIndex + } + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + DispatchQueue.main.async { + self.layoutCollection() + } + } + + /// Invalidates the layout and ensures we are paged to the correct cell. + open func layoutCollection() { + collectionView.collectionViewLayout.invalidateLayout() + showPeaking(false) + + // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. + DispatchQueue.main.async { + self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) + self.collectionView.layoutIfNeeded() + self.showPeaking(true) + } + } + // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() @@ -73,15 +99,12 @@ open class Carousel: View { open override func updateView(_ size: CGFloat) { super.updateView(size) self.size = size - collectionView.collectionViewLayout.invalidateLayout() - showPeaking(false) - - // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. - DispatchQueue.main.async { - self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) - self.collectionView.layoutIfNeeded() - self.showPeaking(true) + + // Update cells and re-layout. + for cell in collectionView.visibleCells { + (cell as? MVMCoreViewProtocol)?.updateView(size) } + layoutCollection() } // MARK: - MoleculeViewProtocol @@ -108,6 +131,9 @@ open class Carousel: View { } setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject) + + pageIndex = carouselModel.index + pagingView?.setPage(carouselModel.index) collectionView.reloadData() } @@ -197,6 +223,7 @@ open class Carousel: View { } let currentPage = pager.currentPage() localSelf.pageIndex = currentPage + localSelf.updateModelIndex() localSelf.goTo(localSelf.currentIndex, animated: !UIAccessibility.isVoiceOverRunning) }) } @@ -246,7 +273,7 @@ open class Carousel: View { extension Carousel: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth = (size ?? collectionView.bounds.width) * CGFloat(itemWidthPercent) + let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) return CGSize(width: itemWidth, height: collectionView.bounds.height) } @@ -280,19 +307,18 @@ extension Carousel: UIScrollViewDelegate { func goTo(_ index: Int, animated: Bool) { showPeaking(false) - setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) - self.currentIndex = index - self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: animated) - if let cell = collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) { - setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index) + currentIndex = index + updateModelIndex() + collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: animated) + if let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) { + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index) UIAccessibility.post(notification: .layoutChanged, argument: cell) } } func handleUserOnBufferCell() { - guard loop else { - return - } + guard loop else { return } let lastPageIndex = numberOfPages + 1 let goToIndex = {(index: Int) in @@ -320,9 +346,11 @@ extension Carousel: UIScrollViewDelegate { let index = scrollView.contentOffset.x / (itemWidth + separatorWidth) let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 if index < 1 { - self.currentIndex = 0 + currentIndex = 0 + updateModelIndex() } else if index > CGFloat(lastCellIndex - 1) { - self.currentIndex = lastCellIndex + currentIndex = lastCellIndex + updateModelIndex() } } @@ -348,9 +376,7 @@ extension Carousel: UIScrollViewDelegate { targetContentOffset.pointee = scrollView.contentOffset // This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). - guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { - return - } + guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return } // We switch cards if we pass the velocity threshold or position threshold (currently 50%). let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) diff --git a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift index 9fb4fe3b..c5f73a01 100644 --- a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift @@ -12,7 +12,7 @@ import UIKit public static var identifier: String = "carousel" public var backgroundColor: Color? public var molecules: [CarouselItemModel] - + public var index: Int = 0 public var spacing: Float? public var border: Bool? public var loop: Bool? @@ -29,6 +29,7 @@ import UIKit case moleculeName case backgroundColor case molecules + case index case spacing case border case loop @@ -40,15 +41,16 @@ import UIKit required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - self.molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) - self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - self.spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing) - self.border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border) - self.loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop) - self.height = try typeContainer.decodeIfPresent(Float.self, forKey: .height) - self.itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent) - self.itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) - self.pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule) + molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) + index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0 + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing) + border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border) + loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop) + height = try typeContainer.decodeIfPresent(Float.self, forKey: .height) + itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent) + itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) + pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule) } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index c0f267fc..000f2e7f 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -33,7 +33,7 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { bottomView.updateView(width) showFooter(width) } - self.tableView?.reloadData() + tableView?.reloadData() } open override func handleNewData() { diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index d19ebdaf..eb4be908 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -278,7 +278,8 @@ import UIKit return } - if needsUpdateUI || screenSizeChanged() { + // First update should be explicit (hence the zero check) + if needsUpdateUI || (previousScreenSize != .zero && screenSizeChanged()) { updateViews() needsUpdateUI = false } From 15911265a7d7b25fb4a498cb0568309fe60a4dbd Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 10 Apr 2020 14:29:40 -0400 Subject: [PATCH 33/38] comments and formatting --- MVMCoreUI/FormUIHelpers/FormValidator.swift | 9 ++++++--- .../Rules/Rules/FormGroupRule.swift | 16 ++++++++++++++++ .../Rules/Rules/RuleAllValueChangedModel.swift | 11 +++++++++-- .../Rules/Rules/RuleEqualsModel.swift | 18 ++++++++++++++---- .../Rules/Rules/RuleRegexModel.swift | 11 +++++++++++ .../Rules/Rules/RuleRequiredModel.swift | 17 +++++++++++++---- 6 files changed, 69 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index e4c20365..29690ae5 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -74,13 +74,13 @@ import MVMCore /// Validates all rule groups. Returns if valid public func validate() -> Bool { var valid = true - guard let formRules = formRules else { - return valid - } + guard let formRules = formRules else { return valid } + for group in formRules { let groupValid = validateGroup(group) valid = valid && groupValid } + return valid } @@ -138,13 +138,16 @@ import MVMCore // TODO: Temporary hacks, rewrite architecture to support this. public extension FormValidator { + func getGroupName(forPageType pageType: String?) -> String? { + for actionItem in groupWatchers { if let buttonModel = actionItem as? ButtonModel, pageType == (buttonModel.action as? ActionOpenPageModel)?.pageType { return buttonModel.groupName } } + return nil } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift index b40d0460..56a5a2cf 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift @@ -11,19 +11,35 @@ import Foundation open class FormGroupRule: Codable { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + var groupName: String var rules: [RulesProtocol] + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + init(_ groupName: String, _ rules: [RulesProtocol]) { self.groupName = groupName self.rules = rules } + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case groupName case rules } + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) self.groupName = try typeContainer.decode(String.self, forKey: .groupName) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift index 7535c6dd..1f50bcc7 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift @@ -9,11 +9,18 @@ import Foundation public class RuleAllValueChangedModel: RulesProtocol { - + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "allValueChanged" public var type: String = RuleAllValueChangedModel.identifier public var fields: [String] - + + //-------------------------------------------------- + // MARK: - Validation + //-------------------------------------------------- + public func isValid(_ formField: FormFieldProtocol) -> Bool { return formField.baseValue != formField.formFieldValue() } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift index e6b338a4..05e7ad32 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift @@ -10,31 +10,41 @@ import Foundation public class RuleEqualsModel: RulesProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "equals" public var type: String = RuleEqualsModel.identifier public var fields: [String] + + //-------------------------------------------------- + // MARK: - Validation + //-------------------------------------------------- public func isValid(_ formField: FormFieldProtocol) -> Bool { return false } public func isValid(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { + var valid = true var compareValue: AnyHashable? + for formKey in fields { - guard let formField = fieldMolecules[formKey] else { - continue - } + guard let formField = fieldMolecules[formKey] else { continue } if compareValue == nil { compareValue = formField.formFieldValue() continue } - if compareValue != formField.formFieldValue(){ + + if compareValue != formField.formFieldValue() { valid = false break } } + return valid } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift index db1c4859..68eea7e5 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift @@ -8,16 +8,27 @@ import Foundation + public class RuleRegexModel: RulesProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "regex" public var type: String = RuleRegexModel.identifier public var fields: [String] public var regex: String + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public func isValid(_ formField: FormFieldProtocol) -> Bool { + if let stringToValidate = formField.formFieldValue() as? String { return MVMCoreUIUtility.validate(stringToValidate, withRegularExpression: regex) } + return false } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift index cfff35eb..b34a24df 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift @@ -10,21 +10,30 @@ import Foundation public class RuleRequiredModel: RulesProtocol { - + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "allRequired" public var type: String = RuleRequiredModel.identifier public var fields: [String] + //-------------------------------------------------- + // MARK: - Validation + //-------------------------------------------------- + public func isValid(_ formField: FormFieldProtocol) -> Bool { - guard let value = formField.formFieldValue() else { - return false - } + guard let value = formField.formFieldValue() else { return false } + var valid = true + if let valueString = value as? String { valid = valueString.count > 0 + } else if let valueBool = value as? Bool { valid = valueBool } + return valid } } From 5ce98da559b77bf2b8c422b19f24a0eb7c5bf67d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 16:59:40 -0400 Subject: [PATCH 34/38] improvements --- MVMCoreUI.xcodeproj/project.pbxproj | 24 ++- MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift | 159 +++++++++++++++++ .../Views/RadioBoxCollectionViewCell.swift | 165 +----------------- .../Atomic/Atoms/Views/RadioBoxModel.swift | 13 +- MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 11 +- .../Atomic/Atoms/Views/RadioBoxesModel.swift | 22 ++- MVMCoreUI/Atomic/Organisms/Carousel.swift | 4 - MVMCoreUI/BaseClasses/CollectionView.swift | 46 +++++ ...ProgrammaticCollectionViewController.swift | 7 +- 9 files changed, 255 insertions(+), 196 deletions(-) create mode 100644 MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift create mode 100644 MVMCoreUI/BaseClasses/CollectionView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 0cd70ea2..0af01831 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -202,7 +202,6 @@ BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */; }; BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */; }; BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */; }; - BBAA4F02243D8E3B005AAD5F /* RadioBoxCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4EFE243D8E3A005AAD5F /* RadioBoxCollectionViewCell.swift */; }; BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */; }; BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */; }; BBAA4F05243D8E3B005AAD5F /* RadioBoxesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */; }; @@ -268,6 +267,9 @@ D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */; }; D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */; }; D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */; }; + D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA6243FE13B00D98315 /* RadioBox.swift */; }; + D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA92440F97600D98315 /* CollectionView.swift */; }; + D264FAAC2441009400D98315 /* RadioBoxCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */; }; D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; @@ -626,7 +628,6 @@ BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTall.swift; sourceTree = ""; }; BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShort.swift; sourceTree = ""; }; BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShortModel.swift; sourceTree = ""; }; - BBAA4EFE243D8E3A005AAD5F /* RadioBoxCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxCollectionViewCell.swift; sourceTree = ""; }; BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxes.swift; sourceTree = ""; }; BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = ""; }; BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxesModel.swift; sourceTree = ""; }; @@ -692,6 +693,9 @@ D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerCollectionReusableView.swift; sourceTree = ""; }; D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgrammaticCollectionViewController.swift; sourceTree = ""; }; D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplateItemProtocol.swift; sourceTree = ""; }; + D264FAA6243FE13B00D98315 /* RadioBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBox.swift; sourceTree = ""; }; + D264FAA92440F97600D98315 /* CollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = ""; }; + D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxCollectionViewCell.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; @@ -1412,6 +1416,13 @@ path = Doughnut; sourceTree = ""; }; + D264FAA8243FE17A00D98315 /* Selectors */ = { + isa = PBXGroup; + children = ( + ); + path = Selectors; + sourceTree = ""; + }; D29DF0C221E404D4003B2FB9 = { isa = PBXGroup; children = ( @@ -1487,6 +1498,7 @@ D29DF10D21E67A70003B2FB9 /* Atoms */ = { isa = PBXGroup; children = ( + D264FAA8243FE17A00D98315 /* Selectors */, D29DF22B21E6A0FA003B2FB9 /* TextFields */, D29DF17D21E69E26003B2FB9 /* Views */, D29DF16821E69E1F003B2FB9 /* Buttons */, @@ -1639,10 +1651,11 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */, BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */, BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */, BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */, - BBAA4EFE243D8E3A005AAD5F /* RadioBoxCollectionViewCell.swift */, + D264FAA6243FE13B00D98315 /* RadioBox.swift */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */, 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, @@ -1835,6 +1848,7 @@ 0AE14F63238315D2005417F8 /* TextField.swift */, D2755D7A23689C7500485468 /* TableViewCell.swift */, D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */, + D264FAA92440F97600D98315 /* CollectionView.swift */, 0A5D59C323AD488600EFD9E9 /* Protocols */, ); path = BaseClasses; @@ -2044,6 +2058,7 @@ AA56A20F243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift in Sources */, 94C2D9A923872E5E0006CF46 /* LabelAttributeImageModel.swift in Sources */, DBC4391922442197001AB423 /* DashLine.swift in Sources */, + D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */, 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */, AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */, @@ -2122,7 +2137,6 @@ 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */, - BBAA4F02243D8E3B005AAD5F /* RadioBoxCollectionViewCell.swift in Sources */, 8D084AD22410BF7600951227 /* ListOneColumnFullWidthTextBodyText.swift in Sources */, 94C0150C2421564A005811A9 /* ActionCollapseNotificationModel.swift in Sources */, 014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */, @@ -2232,6 +2246,7 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */, 8D24041523E7FC0B009E23BE /* ListLeftVariableIconWithRightCaretModel.swift in Sources */, D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, + D264FAAC2441009400D98315 /* RadioBoxCollectionViewCell.swift in Sources */, BB2C969224330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift in Sources */, D2D90B42240463E100DD6EC9 /* MoleculeHeaderModel.swift in Sources */, 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */, @@ -2375,6 +2390,7 @@ D260106523D0CEA700764D80 /* StackModel.swift in Sources */, 0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, + D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift new file mode 100644 index 00000000..616c7fd6 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift @@ -0,0 +1,159 @@ +// +// RadioBox.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/9/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class RadioBox: Control { + public let label = Label.createLabelRegularBodySmall(true) + public let subTextLabel = Label.createLabelRegularMicro(true) + public var isOutOfStock = false + public var accentColor = UIColor.mvmRed + + public let innerPadding: CGFloat = 12.0 + + private var borderLayer: CALayer? + private var strikeLayer: CALayer? + private var maskLayer: CALayer? + + public var radioBoxModel: RadioBoxModel? { + return model as? RadioBoxModel + } + + open override func draw(_ layer: CALayer, in ctx: CGContext) { + // Draw the strikethrough + strikeLayer?.removeFromSuperlayer() + if isOutOfStock { + let line = getStrikeThrough(color: .black, thickness: 1) + layer.addSublayer(line) + strikeLayer = line + } + + // Draw the border + borderLayer?.removeFromSuperlayer() + if isSelected { + layer.borderWidth = 0 + let border = getSelectedBorder() + layer.addSublayer(border) + borderLayer = border + } else { + layer.borderWidth = 1 + } + + // Handle Mask + maskLayer?.removeFromSuperlayer() + if !isEnabled { + let mask = getMaskLayer() + layer.mask = mask + } + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + subTextLabel.updateView(size) + layer.setNeedsDisplay() + } + + open override func setupView() { + super.setupView() + + layer.delegate = self + layer.borderColor = UIColor.black.cgColor + layer.borderWidth = 1 + + label.numberOfLines = 1 + addSubview(label) + NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: innerPadding, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) + + subTextLabel.textColor = .mvmCoolGray6 + subTextLabel.numberOfLines = 1 + addSubview(subTextLabel) + NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) + bottomAnchor.constraint(lessThanOrEqualTo: subTextLabel.bottomAnchor, constant: innerPadding).isActive = true + subTextLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2).isActive = true + + addTarget(self, action: #selector(touched), for: .touchUpInside) + } + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? RadioBoxModel else { return } + isSelected = model.selected + isEnabled = model.enabled + label.text = model.text + subTextLabel.text = model.subText + isOutOfStock = model.strikethrough + } + + @objc open func touched() { + isSelected = !isSelected + layer.setNeedsDisplay() + } + + /// Gets the selected state border + func getSelectedBorder() -> CAShapeLayer { + let layer = CAShapeLayer() + + let topLineWidth: CGFloat = 4 + let topLinePath = UIBezierPath() + topLinePath.lineWidth = topLineWidth + topLinePath.move(to: CGPoint(x: 0, y: topLineWidth / 2.0)) + topLinePath.addLine(to: CGPoint(x: bounds.width, y: topLineWidth / 2.0)) + + let topLineLayer = CAShapeLayer() + topLineLayer.fillColor = nil + topLineLayer.strokeColor = UIColor.mvmRed.cgColor + topLineLayer.lineWidth = 4 + topLineLayer.path = topLinePath.cgPath + layer.addSublayer(topLineLayer) + + let lineWidth: CGFloat = 1 + let halfLineWidth: CGFloat = 0.5 + let linePath = UIBezierPath() + linePath.move(to: CGPoint(x: halfLineWidth, y: topLineWidth)) + linePath.addLine(to: CGPoint(x: halfLineWidth, y: bounds.height)) + linePath.move(to: CGPoint(x: 0, y: bounds.height - halfLineWidth)) + linePath.addLine(to: CGPoint(x: bounds.width, y: bounds.height - halfLineWidth)) + linePath.move(to: CGPoint(x: bounds.width - halfLineWidth, y: bounds.height)) + linePath.addLine(to: CGPoint(x: bounds.width - halfLineWidth, y: topLineWidth)) + + let borderLayer = CAShapeLayer() + borderLayer.fillColor = nil + borderLayer.strokeColor = UIColor.black.cgColor + borderLayer.lineWidth = lineWidth + borderLayer.path = linePath.cgPath + layer.addSublayer(borderLayer) + + return layer + } + + /// Adds a border to edge + func getStrikeThrough(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + let border = CAShapeLayer() + border.name = "strikethrough" + border.fillColor = nil + border.opacity = 1.0 + border.lineWidth = thickness + border.strokeColor = color.cgColor + + let linePath = UIBezierPath() + linePath.move(to: CGPoint(x: 0, y: bounds.height)) + linePath.addLine(to: CGPoint(x: bounds.width, y: 0)) + border.path = linePath.cgPath + return border + } + + func getMaskLayer() -> CALayer { + let mask = CALayer() + mask.backgroundColor = UIColor.white.cgColor + mask.opacity = 0.3 + mask.frame = bounds + return mask + } +} + diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift index 419f278c..c90b5e92 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift @@ -7,164 +7,17 @@ // import Foundation -open class RadioBoxCollectionViewCell: UICollectionViewCell, MoleculeViewProtocol { +open class RadioBoxCollectionViewCell: CollectionViewCell { + let radioBox = RadioBox() - public let bodyLabel = Label(frame: .zero) - public let subTextLabel = Label(frame: .zero) - public let lineView = View(frame: .zero) - var bottomView = MVMCoreUICommonViewsUtility.commonView() - public var lineViewHeight: NSLayoutConstraint? - public var fieldValue: String? - var boxModel: RadioBoxModel! - - open override var isSelected: Bool{ - didSet{ - if let shapeLayer = self.bottomView.layer.sublayers?.filter({$0.name == "outofstock"}).first as? CAShapeLayer { - shapeLayer.strokeColor = isSelected ? UIColor.black.cgColor : UIColor.mfGet(forHex: "#747676").cgColor - } - self.lineViewHeight?.constant = self.isSelected ? 4.0 : 0 - self.bottomView.layer.sublayers?.filter({$0.name == "border"}).forEach({$0.removeFromSuperlayer()}) - if(self.isSelected){ - self.bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) - self.bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) - self.bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: "#0000"), thickness: 1.0) - } - else { - self.addBordertoView("#747676") - } - UIView.animate(withDuration: 0.5) { - self.layoutIfNeeded() - } - } + open override func setupView() { + super.setupView() + addMolecule(radioBox) + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) } - var isOutOfStock: Bool = false { - didSet{ - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.bottomView.layer.sublayers?.filter({$0.name == "outofstock"}).forEach({$0.removeFromSuperlayer()}) - if(self.isOutOfStock) { - self.addOutOfStockLine() - } - } - } - } - - public override init(frame: CGRect) { - super.init(frame: .zero) - setupView() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupView() - } - - public func setupView() { - guard bottomView.superview == nil else { - return - } - isAccessibilityElement = false - contentView.isAccessibilityElement = false - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - bottomView.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(bottomView) - NSLayoutConstraint.constraintPinSubview(toSuperview: bottomView) - - bottomView.addSubview(lineView) - NSLayoutConstraint.constraintPinSubview(lineView, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true) - lineViewHeight = lineView.heightAnchor.constraint(equalToConstant: 0) - lineViewHeight?.isActive = true - - bodyLabel.numberOfLines = 1 - bottomView.addSubview(bodyLabel) - NSLayoutConstraint.constraintPinSubview(bodyLabel, pinTop: false, topConstant:0 , pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 12, pinRight: true, rightConstant: 12) - bodyLabel.topAnchor.constraint(equalTo: lineView.bottomAnchor, constant: 12).isActive = true - - subTextLabel.numberOfLines = 1 - bottomView.addSubview(subTextLabel) - NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0 , pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 12, pinRight: true, rightConstant: 12) - subTextLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomView.bottomAnchor, constant: -12.0).isActive = true - subTextLabel.topAnchor.constraint(equalTo: bodyLabel.bottomAnchor, constant: 2).isActive = true - - subTextLabel.font = MFFonts.mfFontTXRegular(11.0) - bodyLabel.font = MFFonts.mfFontTXRegular(13.0) - subTextLabel.textColor = UIColor.mfGet(forHex: "#747676") - - - } - - public func updateView(_ size: CGFloat) { - self.setNeedsLayout() - self.bottomView.setNeedsLayout() - self.bottomView.layer.sublayers?.filter({$0.name == "border"}).forEach({$0.removeFromSuperlayer()}) - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.addBordertoView(self.boxModel.enabled ? "#747676" : "#D8DADA") - } - } - - private func addBordertoView(_ color:String){ - bottomView.layer.addBorder(edge: .bottom, color: UIColor.mfGet(forHex: color), thickness: 1.0) - bottomView.layer.addBorder(edge: .left, color: UIColor.mfGet(forHex: color), thickness: 1.0) - bottomView.layer.addBorder(edge: .right, color: UIColor.mfGet(forHex: color), thickness: 1.0) - bottomView.layer.addBorder(edge: .top, color: UIColor.mfGet(forHex: color), thickness: 1.0) - } - - private func addOutOfStockLine(){ - let path = UIBezierPath() - path.move(to: CGPoint(x: 0, y: bottomView.bounds.height)) - path.addLine(to: CGPoint(x: bottomView.bounds.width, y: 0)) - let shapeLayer = CAShapeLayer() - shapeLayer.name = "outofstock" - shapeLayer.path = path.cgPath - shapeLayer.strokeColor = self.boxModel.enabled ? UIColor.black.cgColor : UIColor.mfGet(forHex: "#D8DADA").cgColor - shapeLayer.lineWidth = 0.5 - bottomView.layer.addSublayer(shapeLayer) - } - - public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let collectionModel = model as? RadioBoxModel else { return } - boxModel = collectionModel - if let backgroundColor = collectionModel.backgroundColor { - bottomView.backgroundColor = backgroundColor.uiColor - } - self.isUserInteractionEnabled = collectionModel.enabled - lineView.backgroundColor = collectionModel.selectedAccentColor?.uiColor - bodyLabel.text = collectionModel.text - subTextLabel.text = collectionModel.subText - fieldValue = collectionModel.fieldValue - isOutOfStock = collectionModel.strikethrough - - } -} - -extension CALayer { - - func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) { - - let border = CALayer() - border.name = "border" - switch edge { - case UIRectEdge.top: - border.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: thickness) - break - case UIRectEdge.bottom: - border.frame = CGRect(x: 0, y: self.frame.height - thickness, width: self.frame.width, height: thickness) - break - case UIRectEdge.left: - border.frame = CGRect(x: 0, y: 0, width: thickness, height: self.frame.height) - break - case UIRectEdge.right: - border.frame = CGRect(x: self.frame.width - thickness, y: 0, width: thickness, height: self.frame.height) - break - default: - break - } - - border.backgroundColor = color.cgColor; - self.addSublayer(border) + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? RadioBoxModel else { return } + radioBox.set(with: model, delegateObject, additionalData) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift index 0c119ed8..89bdc842 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -12,7 +12,7 @@ import Foundation public var text: String public var subText: String? public var backgroundColor: Color? = Color(uiColor: .white) - public var selectedAccentColor: Color? = try? Color(colorString: "#D52B1E") + public var selectedAccentColor = Color(uiColor: .mvmRed) public var selected: Bool = false public var enabled: Bool = true public var strikethrough: Bool = false @@ -32,7 +32,6 @@ import Foundation case fieldValue case fieldKey case groupName - } required public init(from decoder: Decoder) throws { @@ -57,7 +56,6 @@ import Foundation fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) - } public func encode(to encoder: Encoder) throws { @@ -65,14 +63,13 @@ import Foundation try container.encode(moleculeName, forKey: .moleculeName) try container.encode(text, forKey: .text) try container.encodeIfPresent(subText, forKey: .subText) - try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) + try container.encode(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(selected, forKey: .selected) - try container.encodeIfPresent(enabled, forKey: .enabled) - try container.encodeIfPresent(strikethrough, forKey: .strikethrough) + try container.encode(selected, forKey: .selected) + try container.encode(enabled, forKey: .enabled) + try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) - } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift index d0cae6b4..58090598 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -10,10 +10,10 @@ import Foundation open class RadioBoxes: View { - public let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) /// The models for the molecules. - var boxes: [RadioBoxModel]? + public var boxes: [RadioBoxModel]? public var collectionViewHeight: NSLayoutConstraint? private let boxWidth: Double = 151.0 private let boxHeight: Double = 64.0 @@ -30,15 +30,8 @@ open class RadioBoxes: View { // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() - guard collectionView.superview == nil else { - return - } - collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.dataSource = self collectionView.delegate = self - collectionView.showsHorizontalScrollIndicator = false - collectionView.backgroundColor = .clear - collectionView.isAccessibilityElement = false addSubview(collectionView) NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift index d50978a1..653eae13 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift @@ -9,33 +9,37 @@ import Foundation @objcMembers public class RadioBoxesModel: MoleculeModelProtocol { public static var identifier: String = "radioBoxes" - public var backgroundColor: Color? = Color(uiColor: .white) - public var selectedAccentColor: Color? = Color(uiColor: .red) + public var backgroundColor: Color? + public var selectedAccentColor: Color? public var boxes: [RadioBoxModel] + public var fieldKey: String? + public var groupName: String? private enum CodingKeys: String, CodingKey { case moleculeName case selectedAccentColor case backgroundColor case boxes - + case fieldKey + case groupName } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) { - selectedAccentColor = color - } - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { - backgroundColor = color - } + selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(boxes, forKey: .boxes) try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(groupName, forKey: .groupName) } } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 1ed2ecd2..e900dda8 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -83,12 +83,8 @@ open class Carousel: View { // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() - collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.dataSource = self collectionView.delegate = self - collectionView.showsHorizontalScrollIndicator = false - collectionView.backgroundColor = .clear - collectionView.isAccessibilityElement = false addSubview(collectionView) bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint diff --git a/MVMCoreUI/BaseClasses/CollectionView.swift b/MVMCoreUI/BaseClasses/CollectionView.swift new file mode 100644 index 00000000..ff572a2e --- /dev/null +++ b/MVMCoreUI/BaseClasses/CollectionView.swift @@ -0,0 +1,46 @@ +// +// CollectionView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/10/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class CollectionView: UICollectionView, MVMCoreViewProtocol { + + private var initialSetupPerformed = false + + private func initialSetup() { + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } + + public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + initialSetup() + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + initialSetup() + } + + public func updateView(_ size: CGFloat) { + for cell in visibleCells { + (cell as? MVMCoreViewProtocol)?.updateView(size) + } + collectionViewLayout.invalidateLayout() + } + + public func setupView() { + translatesAutoresizingMaskIntoConstraints = false + showsHorizontalScrollIndicator = false + backgroundColor = .clear + isAccessibilityElement = false + contentInsetAdjustmentBehavior = .always + } +} diff --git a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift index 9d279d79..5e7b87b0 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift @@ -46,14 +46,9 @@ import Foundation /// Creates the collection view. open func createCollectionView() -> UICollectionView { - let collection = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) - collection.translatesAutoresizingMaskIntoConstraints = false + let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) collection.dataSource = self collection.delegate = self - collection.showsHorizontalScrollIndicator = false - collection.backgroundColor = .white - collection.isAccessibilityElement = false - collection.contentInsetAdjustmentBehavior = .always return collection } From b7b65e9d167ac7e4df587b3f8ba681b9363cb2f1 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 20:25:57 -0400 Subject: [PATCH 35/38] Form Validation layout fixing --- MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift | 94 +++++++----- .../Atomic/Atoms/Views/RadioBoxModel.swift | 19 ++- MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift | 135 ++++++++++-------- .../Atomic/Atoms/Views/RadioBoxesModel.swift | 20 ++- MVMCoreUI/Atomic/Organisms/Carousel.swift | 2 +- MVMCoreUI/BaseClasses/CollectionView.swift | 1 + 6 files changed, 166 insertions(+), 105 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift index 616c7fd6..da762dbb 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift @@ -20,10 +20,59 @@ open class RadioBox: Control { private var strikeLayer: CALayer? private var maskLayer: CALayer? + public var subTextLabelHeightConstraint: NSLayoutConstraint? + public var radioBoxModel: RadioBoxModel? { return model as? RadioBoxModel } + // MARK: - MVMCoreViewProtocol + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + subTextLabel.updateView(size) + layer.setNeedsDisplay() + } + + open override func setupView() { + super.setupView() + + layer.delegate = self + layer.borderColor = UIColor.black.cgColor + layer.borderWidth = 1 + + label.numberOfLines = 1 + addSubview(label) + NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: innerPadding, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) + + subTextLabel.textColor = .mvmCoolGray6 + subTextLabel.numberOfLines = 1 + addSubview(subTextLabel) + NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) + bottomAnchor.constraint(greaterThanOrEqualTo: subTextLabel.bottomAnchor, constant: innerPadding).isActive = true + subTextLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2).isActive = true + subTextLabelHeightConstraint = subTextLabel.heightAnchor.constraint(equalToConstant: 0) + subTextLabelHeightConstraint?.isActive = true + + addTarget(self, action: #selector(selectBox), for: .touchUpInside) + } + + // MARK: - MoleculeViewProtocol + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? RadioBoxModel else { return } + isSelected = model.selected + isEnabled = model.enabled + label.text = model.text + subTextLabel.text = model.subText + isOutOfStock = model.strikethrough + subTextLabelHeightConstraint?.isActive = (subTextLabel.text?.count ?? 0) == 0 + } + + // MARK: - State Handling + open override func draw(_ layer: CALayer, in ctx: CGContext) { // Draw the strikethrough strikeLayer?.removeFromSuperlayer() @@ -52,46 +101,21 @@ open class RadioBox: Control { } } - open override func updateView(_ size: CGFloat) { - super.updateView(size) - label.updateView(size) - subTextLabel.updateView(size) + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any size changes layer.setNeedsDisplay() } - open override func setupView() { - super.setupView() - - layer.delegate = self - layer.borderColor = UIColor.black.cgColor - layer.borderWidth = 1 - - label.numberOfLines = 1 - addSubview(label) - NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: innerPadding, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) - - subTextLabel.textColor = .mvmCoolGray6 - subTextLabel.numberOfLines = 1 - addSubview(subTextLabel) - NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) - bottomAnchor.constraint(lessThanOrEqualTo: subTextLabel.bottomAnchor, constant: innerPadding).isActive = true - subTextLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2).isActive = true - - addTarget(self, action: #selector(touched), for: .touchUpInside) + @objc open func selectBox() { + isSelected = true + radioBoxModel?.selected = isSelected + layer.setNeedsDisplay() } - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - guard let model = model as? RadioBoxModel else { return } - isSelected = model.selected - isEnabled = model.enabled - label.text = model.text - subTextLabel.text = model.subText - isOutOfStock = model.strikethrough - } - - @objc open func touched() { - isSelected = !isSelected + @objc open func deselectBox() { + isSelected = false + radioBoxModel?.selected = isSelected layer.setNeedsDisplay() } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift index 89bdc842..caf26800 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -7,7 +7,7 @@ // import Foundation -@objcMembers public class RadioBoxModel: MoleculeModelProtocol { +@objcMembers public class RadioBoxModel: MoleculeModelProtocol, FormFieldProtocol { public static var identifier: String = "radioBox" public var text: String public var subText: String? @@ -18,8 +18,9 @@ import Foundation public var strikethrough: Bool = false public var fieldValue: String? public var fieldKey: String? - public var groupName: String? - + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? + private enum CodingKeys: String, CodingKey { case moleculeName case text @@ -34,6 +35,10 @@ import Foundation case groupName } + public func formFieldValue() -> AnyHashable? { + return selected + } + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) text = try typeContainer.decode(String.self, forKey: .text) @@ -53,9 +58,13 @@ import Foundation if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { strikethrough = isStrikeTrough } + fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) + if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + self.groupName = groupName + } + baseValue = selected } public func encode(to encoder: Encoder) throws { @@ -70,6 +79,6 @@ import Foundation try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encode(groupName, forKey: .groupName) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift index 58090598..6fe66bc2 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift @@ -10,28 +10,31 @@ import Foundation open class RadioBoxes: View { - public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + public var collectionView: CollectionView! + public var collectionViewHeight: NSLayoutConstraint! + private let boxWidth: CGFloat = 151.0 + private let boxHeight: CGFloat = 64.0 + private let itemSpacing: CGFloat = 8.0 + + private var delegateObject: MVMCoreUIDelegateObject? /// The models for the molecules. public var boxes: [RadioBoxModel]? - public var collectionViewHeight: NSLayoutConstraint? - private let boxWidth: Double = 151.0 - private let boxHeight: Double = 64.0 - private let itemSpacing: Double = 10.0 - private let leadingSpacing: Double = 0 - - public var selectedBox: RadioBoxModel? { - get{ - guard let selectedItem = collectionView.indexPathsForSelectedItems?.first else {return nil} - return boxes?[selectedItem.item] + + private var size: CGFloat? + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + DispatchQueue.main.async { + self.collectionView.collectionViewLayout.invalidateLayout() } } - + // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() - collectionView.dataSource = self - collectionView.delegate = self + collectionView = createCollectionView() addSubview(collectionView) NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) @@ -41,62 +44,66 @@ open class RadioBoxes: View { // MARK: - MoleculeViewProtocol public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject + guard let radioBoxesModel = model as? RadioBoxesModel else { return } + boxes = radioBoxesModel.boxes + FormValidator.setupValidation(for: radioBoxesModel, delegate: delegateObject?.formHolderDelegate) + backgroundColor = radioBoxesModel.backgroundColor?.uiColor registerCells() - setupLayout(with: radioBoxesModel) - prepareMolecules(with: radioBoxesModel) + setHeight() collectionView.reloadData() } @objc override open func updateView(_ size: CGFloat) { - collectionView.collectionViewLayout.invalidateLayout() - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.setNeedsDisplay() - self.collectionView.layoutIfNeeded() - self.collectionView.reloadData() - guard let firstSelectedIndex = self.boxes?.firstIndex(where: {$0.selected == true}) else { - return - } - self.collectionView.selectItem(at: IndexPath(item: firstSelectedIndex, section: 0), animated: true, scrollPosition: .centeredHorizontally) - - } - } - - // MARK: - JSON Setters - /// Updates the layout being used - - func setupLayout(with radioBoxesModel: RadioBoxesModel?) { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .vertical - layout.sectionInset = UIEdgeInsets.init(top: CGFloat(leadingSpacing), left: CGFloat(leadingSpacing), bottom: CGFloat(leadingSpacing), right: CGFloat(leadingSpacing)) - layout.minimumLineSpacing = 10 - layout.minimumInteritemSpacing = 10 - collectionView.collectionViewLayout = layout + super.updateView(size) + self.size = size + collectionView.updateView(size) } - func prepareMolecules(with radioBoxesModel: RadioBoxesModel?) { - guard let newMolecules = radioBoxesModel?.boxes else { - boxes = nil - return - } - boxes = newMolecules - let height = Double(round(Double((boxes?.count ?? Int(0.0)))/2.0))*(boxHeight+10.0) - collectionViewHeight?.constant = CGFloat(height) - collectionViewHeight?.isActive = true + // MARK: - Creation + + /// Creates the layout for the collection. + open func createCollectionViewLayout() -> UICollectionViewLayout { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = itemSpacing + layout.minimumInteritemSpacing = itemSpacing + return layout + } + + /// Creates the collection view. + open func createCollectionView() -> CollectionView { + let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) + collection.dataSource = self + collection.delegate = self + return collection } /// Registers the cells with the collection view - func registerCells() { + open func registerCells() { collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell") } + + // MARK: - JSON Setters + open func setHeight() { + guard let boxes = boxes, boxes.count > 0 else { + collectionViewHeight.constant = 0 + return + } + + // Calculate the height + let rows = ceil(CGFloat(boxes.count) / 2.0) + let height = (rows * boxHeight) + ((rows - 1) * itemSpacing) + collectionViewHeight?.constant = height + } } extension RadioBoxes: UICollectionViewDelegateFlowLayout { open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth = (Double(collectionView.bounds.width) - itemSpacing)/2 - return CGSize(width: CGFloat(itemWidth), height: CGFloat(boxHeight)) + let itemWidth: CGFloat = (collectionView.bounds.width - itemSpacing) / 2 + return CGSize(width: itemWidth, height: boxHeight) } } @@ -106,23 +113,31 @@ extension RadioBoxes: UICollectionViewDataSource { } open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let molecule = boxes?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { - return UICollectionViewCell() + guard let molecule = boxes?[indexPath.row], + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { + fatalError() + } + cell.radioBox.isUserInteractionEnabled = false + cell.set(with: molecule, delegateObject, nil) + cell.updateView(size ?? collectionView.bounds.width) + if molecule.selected { + collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically) } - cell.set(with: molecule, nil, nil) - cell.updateView(collectionView.bounds.width) cell.layoutIfNeeded() return cell } } + extension RadioBoxes: UICollectionViewDelegate { public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let boxItem = boxes?[indexPath.row] else { return } - boxItem.selected = true + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } + cell.radioBox.selectBox() + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) } + public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { - guard let boxItem = boxes?[indexPath.row] else { return } - boxItem.selected = false + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } + cell.radioBox.deselectBox() } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift index 653eae13..28c4fab5 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift @@ -7,13 +7,22 @@ // import Foundation -@objcMembers public class RadioBoxesModel: MoleculeModelProtocol { +@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol { public static var identifier: String = "radioBoxes" public var backgroundColor: Color? public var selectedAccentColor: Color? public var boxes: [RadioBoxModel] public var fieldKey: String? - public var groupName: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? + + /// Returns the fieldValue of the selected box, otherwise the text of the selected box. + public func formFieldValue() -> AnyHashable? { + let selectedBox = boxes.first { (box) -> Bool in + return box.selected + } + return selectedBox?.fieldValue ?? selectedBox?.text + } private enum CodingKeys: String, CodingKey { case moleculeName @@ -30,7 +39,10 @@ import Foundation backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) + if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + self.groupName = groupName + } + baseValue = formFieldValue() } public func encode(to encoder: Encoder) throws { @@ -40,6 +52,6 @@ import Foundation try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encode(groupName, forKey: .groupName) } } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index e900dda8..820c5a5d 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -10,7 +10,7 @@ import UIKit open class Carousel: View { - public let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) /// The current index of the collection view. Includes dummy cells when looping. public var currentIndex = 0 diff --git a/MVMCoreUI/BaseClasses/CollectionView.swift b/MVMCoreUI/BaseClasses/CollectionView.swift index ff572a2e..a1a57376 100644 --- a/MVMCoreUI/BaseClasses/CollectionView.swift +++ b/MVMCoreUI/BaseClasses/CollectionView.swift @@ -39,6 +39,7 @@ open class CollectionView: UICollectionView, MVMCoreViewProtocol { public func setupView() { translatesAutoresizingMaskIntoConstraints = false showsHorizontalScrollIndicator = false + showsVerticalScrollIndicator = false backgroundColor = .clear isAccessibilityElement = false contentInsetAdjustmentBehavior = .always From e8b8d8132cc0447bcc066939e5ffded9d11d75d3 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 20:32:12 -0400 Subject: [PATCH 36/38] remove form validator from radio box for now --- .../Atomic/Atoms/Views/RadioBoxModel.swift | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift index caf26800..b2a23c6a 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift @@ -7,7 +7,7 @@ // import Foundation -@objcMembers public class RadioBoxModel: MoleculeModelProtocol, FormFieldProtocol { +@objcMembers public class RadioBoxModel: MoleculeModelProtocol { public static var identifier: String = "radioBox" public var text: String public var subText: String? @@ -17,9 +17,6 @@ import Foundation public var enabled: Bool = true public var strikethrough: Bool = false public var fieldValue: String? - public var fieldKey: String? - public var groupName: String = FormValidator.defaultGroupName - public var baseValue: AnyHashable? private enum CodingKeys: String, CodingKey { case moleculeName @@ -31,12 +28,6 @@ import Foundation case enabled case strikethrough case fieldValue - case fieldKey - case groupName - } - - public func formFieldValue() -> AnyHashable? { - return selected } required public init(from decoder: Decoder) throws { @@ -60,11 +51,6 @@ import Foundation } fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) - fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { - self.groupName = groupName - } - baseValue = selected } public func encode(to encoder: Encoder) throws { @@ -78,7 +64,5 @@ import Foundation try container.encode(enabled, forKey: .enabled) try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) - try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encode(groupName, forKey: .groupName) } } From c718cfa92af82796b1c1de273987f00eef3c1c00 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 20:34:06 -0400 Subject: [PATCH 37/38] selectors folder --- MVMCoreUI.xcodeproj/project.pbxproj | 20 +++++++++---------- .../Atoms/{Views => Selectors}/Checkbox.swift | 0 .../{Views => Selectors}/CheckboxModel.swift | 0 .../Atoms/{Views => Selectors}/RadioBox.swift | 0 .../RadioBoxCollectionViewCell.swift | 0 .../{Views => Selectors}/RadioBoxModel.swift | 0 .../{Views => Selectors}/RadioBoxes.swift | 0 .../RadioBoxesModel.swift | 0 .../{Buttons => Selectors}/RadioButton.swift | 0 .../RadioButtonModel.swift | 0 .../RadioButtonSelectionHelper.swift | 0 11 files changed, 10 insertions(+), 10 deletions(-) rename MVMCoreUI/Atomic/Atoms/{Views => Selectors}/Checkbox.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Views => Selectors}/CheckboxModel.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Views => Selectors}/RadioBox.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Views => Selectors}/RadioBoxCollectionViewCell.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Views => Selectors}/RadioBoxModel.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Views => Selectors}/RadioBoxes.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Views => Selectors}/RadioBoxesModel.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Buttons => Selectors}/RadioButton.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Buttons => Selectors}/RadioButtonModel.swift (100%) rename MVMCoreUI/Atomic/Atoms/{Buttons => Selectors}/RadioButtonSelectionHelper.swift (100%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 0af01831..294f6188 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -1419,6 +1419,16 @@ D264FAA8243FE17A00D98315 /* Selectors */ = { isa = PBXGroup; children = ( + D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */, + BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */, + BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */, + BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */, + D264FAA6243FE13B00D98315 /* RadioBox.swift */, + 0116A4E4228B19640094F3ED /* RadioButtonSelectionHelper.swift */, + 011D95AE2407266E000E3791 /* RadioButtonModel.swift */, + 01004F2F22721C3800991ECC /* RadioButton.swift */, + 31BE15CA23D8924C00452370 /* CheckboxModel.swift */, + 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, ); path = Selectors; sourceTree = ""; @@ -1641,9 +1651,6 @@ DBC4391A224421A0001AB423 /* CaretLink.swift */, D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */, D2E2A99E23E07F8A000B42E6 /* PillButton.swift */, - 0116A4E4228B19640094F3ED /* RadioButtonSelectionHelper.swift */, - 011D95AE2407266E000E3791 /* RadioButtonModel.swift */, - 01004F2F22721C3800991ECC /* RadioButton.swift */, ); path = Buttons; sourceTree = ""; @@ -1651,11 +1658,6 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( - D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */, - BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */, - BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */, - BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */, - D264FAA6243FE13B00D98315 /* RadioBox.swift */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */, 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, @@ -1672,8 +1674,6 @@ 017BEB7A236763000024EF95 /* LineModel.swift */, D213347623843825008E41B3 /* Line.swift */, 94C2D9822386F3E30006CF46 /* Label */, - 31BE15CA23D8924C00452370 /* CheckboxModel.swift */, - 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */, 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */, D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/Checkbox.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/CheckboxModel.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/RadioBox.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/RadioBoxCollectionViewCell.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/RadioBoxModel.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/RadioBoxes.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/RadioBoxesModel.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift From cf5b554b851f23ea0e1572ca5838072ad1fdd216 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 10 Apr 2020 20:36:42 -0400 Subject: [PATCH 38/38] remove old comment no longer applicable --- MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index 0f0baac6..ab0d5b43 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -47,7 +47,7 @@ import Foundation // need to move labelattributemodel to different method try? ModelRegistry.register(LabelAttributeFontModel.self) try? ModelRegistry.register(LabelAttributeColorModel.self) - try? ModelRegistry.register(LabelAttributeImageModel.self) // We need to separate the registry by types due to collisions... + try? ModelRegistry.register(LabelAttributeImageModel.self) try? ModelRegistry.register(LabelAttributeUnderlineModel.self) try? ModelRegistry.register(LabelAttributeStrikeThroughModel.self) try? ModelRegistry.register(LabelAttributeActionModel.self) @@ -66,9 +66,12 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ItemDropdownEntryField.self, viewModelClass: ItemDropdownEntryFieldModel.self) MoleculeObjectMapping.shared()?.register(viewClass: DateDropdownEntryField.self, viewModelClass: DateDropdownEntryFieldModel.self) - // Other Atoms + // Selectors + MoleculeObjectMapping.shared()?.register(viewClass: RadioButton.self, viewModelClass: RadioButtonModel.self) MoleculeObjectMapping.shared()?.register(viewClass: RadioBoxes.self, viewModelClass: RadioBoxesModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: Checkbox.self, viewModelClass: CheckboxModel.self) + // Other Atoms MoleculeObjectMapping.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MultiProgress.self, viewModelClass: MultiProgressBarModel.self) MoleculeObjectMapping.shared()?.register(viewClass: CaretView.self, viewModelClass: CaretViewModel.self) @@ -77,10 +80,8 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: Line.self, viewModelClass: LineModel.self) MoleculeObjectMapping.shared()?.register(viewClass: GraphView.self, viewModelClass: CircleProgressModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Toggle.self, viewModelClass: ToggleModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: Checkbox.self, viewModelClass: CheckboxModel.self) MoleculeObjectMapping.shared()?.register(viewClass: CheckboxLabel.self, viewModelClass: CheckboxLabelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Arrow.self, viewModelClass: ArrowModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: RadioButton.self, viewModelClass: RadioButtonModel.self) MoleculeObjectMapping.shared()?.register(viewClass: RadioButtonLabel.self, viewModelClass: RadioButtonLabelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: WebView.self, viewModelClass: WebViewModel.self)