From 163535398925b20ec54f2ccf7306ef64f9d1bf1e Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Mon, 13 Apr 2020 18:07:54 +0530 Subject: [PATCH] Code refactoring by using collection base classes. --- MVMCoreUI.xcodeproj/project.pbxproj | 10 +- MVMCoreUI/Atomic/Atoms/RadioSwatchItem.swift | 127 ------------- .../Atoms/Selectors/RadioSwatchItem.swift | 167 ++++++++++++++++++ .../RadioSwatchItemCollectionViewCell.swift | 24 +++ .../Atoms/{ => Selectors}/RadioSwatches.swift | 113 ++++++------ .../{ => Selectors}/RadioSwatchesModel.swift | 53 +++--- MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 1 + 7 files changed, 280 insertions(+), 215 deletions(-) delete mode 100644 MVMCoreUI/Atomic/Atoms/RadioSwatchItem.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchItem.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchItemCollectionViewCell.swift rename MVMCoreUI/Atomic/Atoms/{ => Selectors}/RadioSwatches.swift (54%) rename MVMCoreUI/Atomic/Atoms/{ => Selectors}/RadioSwatchesModel.swift (86%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 3e55a236..077b0ca2 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -190,6 +190,7 @@ AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */; }; AA56A20F243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */; }; AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA56A210243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift */; }; + AA85236C244435A20059CC1E /* RadioSwatchItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA85236B244435A20059CC1E /* RadioSwatchItemCollectionViewCell.swift */; }; AAA74A172410C04600080241 /* HeadersH2NoButtonsBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */; }; AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */; }; AAB9C10824346F4B00151545 /* RadioSwatches.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB9C10724346F4B00151545 /* RadioSwatches.swift */; }; @@ -619,6 +620,7 @@ AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnSpeedTestDivider.swift; sourceTree = ""; }; AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDividerModel.swift; sourceTree = ""; }; AA56A210243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDivider.swift; sourceTree = ""; }; + AA85236B244435A20059CC1E /* RadioSwatchItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchItemCollectionViewCell.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 = ""; }; AAB9C10724346F4B00151545 /* RadioSwatches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatches.swift; sourceTree = ""; }; @@ -1435,6 +1437,10 @@ 01004F2F22721C3800991ECC /* RadioButton.swift */, 31BE15CA23D8924C00452370 /* CheckboxModel.swift */, 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, + AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */, + AAB9C10724346F4B00151545 /* RadioSwatches.swift */, + AAB9C109243496DD00151545 /* RadioSwatchItem.swift */, + AA85236B244435A20059CC1E /* RadioSwatchItemCollectionViewCell.swift */, ); path = Selectors; sourceTree = ""; @@ -1515,9 +1521,6 @@ isa = PBXGroup; children = ( D264FAA8243FE17A00D98315 /* Selectors */, - AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */, - AAB9C10724346F4B00151545 /* RadioSwatches.swift */, - AAB9C109243496DD00151545 /* RadioSwatchItem.swift */, D29DF22B21E6A0FA003B2FB9 /* TextFields */, D29DF17D21E69E26003B2FB9 /* Views */, D29DF16821E69E1F003B2FB9 /* Buttons */, @@ -2330,6 +2333,7 @@ 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */, D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */, + AA85236C244435A20059CC1E /* RadioSwatchItemCollectionViewCell.swift in Sources */, 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */, D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */, 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/RadioSwatchItem.swift b/MVMCoreUI/Atomic/Atoms/RadioSwatchItem.swift deleted file mode 100644 index 17352ed7..00000000 --- a/MVMCoreUI/Atomic/Atoms/RadioSwatchItem.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// RadioSwatchItem.swift -// MVMCoreUI -// -// Created by Lekshmi S on 01/04/20. -// Copyright © 2020 Verizon Wireless. All rights reserved. -// - -import UIKit - -open class RadioSwatchItem: UICollectionViewCell, MoleculeViewProtocol { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - public var bottomText = Label(frame: .zero) - let circleLayer = CAShapeLayer() - var cellView = MVMCoreUICommonViewsUtility.commonView() - var fillColor: Color = Color(uiColor: .mvmBlue) - - //------------------------------------------------------ - // MARK: - Property Observer - //------------------------------------------------------ - open override var isSelected: Bool { - didSet { - drawCircle() - isSelected ? drawOuterCircle() : removeOuterCircle() - isSelected ? (bottomText.isHidden = false) : (bottomText.isHidden = true) - } - } - - var isStrikeThrough: Bool = false { - didSet { - cellView.layer.sublayers?.filter({$0.name == "OutOfStock"}).forEach({$0.removeFromSuperlayer()}) - if isStrikeThrough { - drawStrikeThrough() - } - } - } - - //------------------------------------------------------ - // MARK: - Initialization - //------------------------------------------------------ - public override init(frame: CGRect) { - super.init(frame: .zero) - setupView() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupView() - } - - //------------------------------------------------------ - // MARK: - Drawing - //------------------------------------------------------ - public func drawCircle() { - cellView.layer.sublayers?.filter({$0.name == "InnerCircle"}).forEach({$0.removeFromSuperlayer()}) - circleLayer.path = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)).cgPath - circleLayer.fillColor = fillColor.cgColor - circleLayer.strokeColor = isUserInteractionEnabled ? UIColor.mvmBlack.cgColor : UIColor.mvmCoolGray6.cgColor - circleLayer.name = "InnerCircle" - circleLayer.lineWidth = 1 - cellView.layer.addSublayer(circleLayer) - } - - public func drawStrikeThrough() { - let startPoint = CGPoint(x: 12, y: 30) - let endPoint = CGPoint(x: 42, y: 0) - let bezierPath = UIBezierPath() - bezierPath.move(to: startPoint) - bezierPath.addLine(to: endPoint) - let strikeThroughLayer = CAShapeLayer() - strikeThroughLayer.path = bezierPath.cgPath - strikeThroughLayer.name = "OutOfStock" - strikeThroughLayer.strokeColor = UIColor.mvmCoolGray6.cgColor - strikeThroughLayer.lineWidth = 1 - cellView.layer.addSublayer(strikeThroughLayer) - } - - public func drawOuterCircle() { - self.cellView.layer.sublayers?.filter({$0.name == "OuterCircle"}).forEach({$0.removeFromSuperlayer()}) - circleLayer.path = UIBezierPath(ovalIn: CGRect(x: 15, y: 4, width: 24, height: 24)).cgPath - let outerCircleLayer = CAShapeLayer() - outerCircleLayer.path = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)).cgPath - outerCircleLayer.name = "OuterCircle" - outerCircleLayer.strokeColor = UIColor.mvmBlack.cgColor - outerCircleLayer.fillColor = UIColor.clear.cgColor - outerCircleLayer.lineWidth = 1 - cellView.layer.addSublayer(outerCircleLayer) - } - - public func removeOuterCircle() { - circleLayer.path = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)).cgPath - self.cellView.layer.sublayers?.filter({$0.name == "OuterCircle"}).forEach({$0.removeFromSuperlayer()}) - } - - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - public func setupView() { - guard cellView.superview == nil else { - return - } - isAccessibilityElement = false - contentView.isAccessibilityElement = false - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - contentView.addSubview(cellView) - NSLayoutConstraint.constraintPinSubview(toSuperview: cellView) - cellView.addSubview(bottomText) - bottomText.textAlignment = .center - bottomText.font = MFFonts.mfFontTXRegular(11.0) - bottomText.topAnchor.constraint(equalTo: cellView.topAnchor, constant: 38).isActive = true - bottomText.leadingAnchor.constraint(equalTo: cellView.leadingAnchor).isActive = true - bottomText.trailingAnchor.constraint(equalTo: cellView.trailingAnchor).isActive = true - cellView.bottomAnchor.constraint(equalTo: bottomText.bottomAnchor).isActive = true - } - - public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let collectionModel = model as? RadioSwatchItemModel else { return } - fillColor = collectionModel.color - isUserInteractionEnabled = collectionModel.enabled ?? true - isStrikeThrough = collectionModel.strikethrough ?? false - bottomText.text = collectionModel.text - } -} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchItem.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchItem.swift new file mode 100644 index 00000000..f6054ebb --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchItem.swift @@ -0,0 +1,167 @@ +// +// RadioSwatchItem.swift +// MVMCoreUI +// +// Created by Lekshmi S on 01/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +import UIKit + +open class RadioSwatchItem: Control { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var bottomText = Label.createLabelRegularMicro(true) + public var isOutOfStock = false + public var fillColor: Color = Color(uiColor: .mvmBlue) + + private var circleLayer: CAShapeLayer? + private var selectedLayer: CALayer? + private var strikeLayer: CALayer? + private var maskLayer: CALayer? + + public var radioSwatchModel: RadioSwatchItemModel? { + return model as? RadioSwatchItemModel + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func updateView(_ size: CGFloat) { + super.updateView(size) + bottomText.updateView(size) + layer.setNeedsDisplay() + } + + public override func setupView() { + super.setupView() + + addSubview(bottomText) + bottomText.textAlignment = .center + bottomText.topAnchor.constraint(equalTo: topAnchor, constant: 38).isActive = true + bottomText.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + bottomText.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + bottomAnchor.constraint(equalTo: bottomText.bottomAnchor).isActive = true + addTarget(self, action: #selector(selectSwatch), 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? RadioSwatchItemModel else { return } + fillColor = model.color + isSelected = model.selected ?? false + isEnabled = model.enabled ?? true + bottomText.text = model.text + isOutOfStock = model.strikethrough ?? false + } + + //------------------------------------------------------ + // MARK: - State Handling + //------------------------------------------------------ + + open override func draw(_ layer: CALayer, in ctx: CGContext) { + //Draw the swatch + circleLayer?.removeFromSuperlayer() + let circle = getCircle(color: UIColor.mvmBlack, thickness: 1) + layer.addSublayer(circle) + circleLayer = circle + + //Draw the strikethrough + strikeLayer?.removeFromSuperlayer() + if isOutOfStock { + let line = getStrikeThrough(color: UIColor.mvmCoolGray6, thickness: 1) + layer.addSublayer(line) + strikeLayer = line + } + + //Draw the selected layer + selectedLayer?.removeFromSuperlayer() + if isSelected { + let outerCircle = getSelectedLayer(color: UIColor.black, thickness: 1) + layer.addSublayer(outerCircle) + selectedLayer = outerCircle + bottomText.isHidden = false + } else { + circleLayer?.path = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)).cgPath + bottomText.isHidden = true + } + + //Handle Mask + maskLayer?.removeFromSuperlayer() + if !isEnabled { + let mask = getMaskLayer() + layer.mask = mask + maskLayer = mask + } + } + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any size changes + layer.setNeedsDisplay() + } + + @objc open func selectSwatch() { + isSelected = true + radioSwatchModel?.selected = isSelected + layer.setNeedsDisplay() + } + + @objc open func deselectSwatch() { + isSelected = false + radioSwatchModel?.selected = isSelected + layer.setNeedsDisplay() + } + + func getCircle(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + let circle = CAShapeLayer() + circle.name = "innercircle" + circle.fillColor = fillColor.cgColor + circle.opacity = 1.0 + circle.lineWidth = thickness + circle.strokeColor = isEnabled ? color.cgColor : UIColor.mvmCoolGray6.cgColor + + let circlePath = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)) + circle.path = circlePath.cgPath + return circle + } + + func getStrikeThrough(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + let strikeThrough = CAShapeLayer() + strikeThrough.name = "strikethrough" + strikeThrough.fillColor = nil + strikeThrough.opacity = 1.0 + strikeThrough.lineWidth = thickness + strikeThrough.strokeColor = color.cgColor + + let linePath = UIBezierPath() + linePath.move(to: CGPoint(x: 12, y: 30)) + linePath.addLine(to: CGPoint(x: 42, y: 0)) + strikeThrough.path = linePath.cgPath + return strikeThrough + } + + func getSelectedLayer(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + circleLayer?.path = UIBezierPath(ovalIn: CGRect(x: 15, y: 4, width: 24, height: 24)).cgPath + let circle = CAShapeLayer() + circle.name = "outercircle" + circle.fillColor = UIColor.clear.cgColor + circle.opacity = 1.0 + circle.lineWidth = thickness + circle.strokeColor = color.cgColor + + let circlePath = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)) + circle.path = circlePath.cgPath + return circle + } + + 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/Selectors/RadioSwatchItemCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchItemCollectionViewCell.swift new file mode 100644 index 00000000..b274d28d --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchItemCollectionViewCell.swift @@ -0,0 +1,24 @@ +// +// RadioSwatchItemCollectionViewCell.swift +// MVMCoreUI +// +// Created by Lekshmi S on 13/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +open class RadioSwatchItemCollectionViewCell: CollectionViewCell { + let radioSwatch = RadioSwatchItem() + + open override func setupView() { + super.setupView() + addMolecule(radioSwatch) + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? RadioSwatchItemModel else { return } + radioSwatch.set(with: model, delegateObject, additionalData) + self.isUserInteractionEnabled = model.enabled ?? true + } +} diff --git a/MVMCoreUI/Atomic/Atoms/RadioSwatches.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift similarity index 54% rename from MVMCoreUI/Atomic/Atoms/RadioSwatches.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift index a27af6c8..d73c749a 100644 --- a/MVMCoreUI/Atomic/Atoms/RadioSwatches.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift @@ -6,97 +6,97 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import UIKit +import Foundation open class RadioSwatches: View { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + public var collectionView: CollectionView! var swatches: [RadioSwatchItemModel]? - - public var selectedSwatchItem: RadioSwatchItemModel? { - get{ - guard let selectedItem = collectionView.indexPathsForSelectedItems?.first else {return nil} - return swatches?[selectedItem.item] - } - } + private var size: CGFloat? + private var delegateObject: MVMCoreUIDelegateObject? //------------------------------------------------------ // MARK: - Constraints //------------------------------------------------------ public var collectionViewHeight: NSLayoutConstraint? - public let cellSize: Double = 54.0 - public let spacing: Double = 10 + private let cellSize: CGFloat = 54.0 + private let itemSpacing: CGFloat = 8.0 //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + DispatchQueue.main.async { + self.collectionView.collectionViewLayout.invalidateLayout() + } + } + 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 + collectionView = createCollectionView() addSubview(collectionView) NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 100) collectionViewHeight?.isActive = true } - public override func updateView(_ size: CGFloat) { - DispatchQueue.main.async { - self.collectionView.collectionViewLayout.invalidateLayout() - self.collectionView.reloadData() - guard let selectedCell = self.swatches?.firstIndex(where: {$0.selected == true}) else { - return - } - self.collectionView.selectItem(at: IndexPath(item: selectedCell, section: 0), animated: true, scrollPosition: .centeredHorizontally) - } + @objc override open func updateView(_ size: CGFloat) { + super.updateView(size) + self.size = size + collectionView.updateView(size) } public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject + guard let radioSwatchesModel = model as? RadioSwatchesModel else { return } - collectionView.layer.borderColor = backgroundColor?.cgColor + swatches = radioSwatchesModel.swatches + FormValidator.setupValidation(for: radioSwatchesModel, delegate: delegateObject?.formHolderDelegate) registerCells() - setupLayout(with: radioSwatchesModel) - prepareMolecules(with: radioSwatchesModel) + setHeight() collectionView.reloadData() } //------------------------------------------------------ // MARK: - Methods //------------------------------------------------------ - func registerCells() { - collectionView.register(RadioSwatchItem.self, forCellWithReuseIdentifier: "RadioSwatchItemCollectionViewCell") + open func registerCells() { + collectionView.register(RadioSwatchItemCollectionViewCell.self, forCellWithReuseIdentifier: "RadioSwatchItemCollectionViewCell") } - func setupLayout(with radioSwatchesModel: RadioSwatchesModel?) { + /// Creates the collection view. + open func createCollectionView() -> CollectionView { + let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) + collection.dataSource = self + collection.delegate = self + return collection + } + + /// Creates the layout for the collection. + open func createCollectionViewLayout() -> UICollectionViewLayout { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .vertical - layout.minimumLineSpacing = CGFloat(spacing) - layout.minimumInteritemSpacing = CGFloat(spacing) - collectionView.collectionViewLayout = layout + layout.minimumLineSpacing = itemSpacing + layout.minimumInteritemSpacing = itemSpacing + return layout } - func prepareMolecules(with radioSwatchesModel: RadioSwatchesModel?) { - guard let newSwatches = radioSwatchesModel?.swatches else { - swatches = nil + open func setHeight() { + guard let swatches = swatches, swatches.count > 0 else { + collectionViewHeight?.constant = 0 return } - swatches = newSwatches + // Calculate the height let collectionViewWidth = UIScreen.main.bounds.width - (2 * MFStyler.defaultHorizontalPaddingForApplicationWidth()) - let swatchesInRow = Double(floor(Double(collectionViewWidth/60.0))) - let numberOfRows = floor(Double(swatches?.count ?? 1)/swatchesInRow) + 1.0 - let height = (numberOfRows * cellSize) + (spacing * (numberOfRows-1)) + let swatchesInRow = floor(CGFloat(collectionViewWidth/(cellSize + itemSpacing))) + let numberOfRows = ceil(CGFloat(swatches.count)/swatchesInRow) + let height = (numberOfRows * cellSize) + (itemSpacing * (numberOfRows-1)) collectionViewHeight?.constant = CGFloat(height) - collectionViewHeight?.isActive = true } } @@ -115,21 +115,28 @@ extension RadioSwatches: UICollectionViewDataSource { } open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let molecule = swatches?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioSwatchItemCollectionViewCell", for: indexPath) as? RadioSwatchItem else { - return UICollectionViewCell() + guard let molecule = swatches?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioSwatchItemCollectionViewCell", for: indexPath) as? RadioSwatchItemCollectionViewCell else { + fatalError() } - cell.set(with: molecule, nil, nil) + cell.radioSwatch.isUserInteractionEnabled = false + cell.set(with: molecule, delegateObject, nil) + cell.updateView(size ?? collectionView.bounds.width) + if molecule.selected ?? false { + collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically) + } + cell.layoutIfNeeded() return cell } } extension RadioSwatches: UICollectionViewDelegate { public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let swatchItem = swatches?[indexPath.row] else { return } - swatchItem.selected = true + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioSwatchItemCollectionViewCell else { return } + cell.radioSwatch.selectSwatch() + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) } public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { - guard let swatchItem = swatches?[indexPath.row] else { return } - swatchItem.selected = false + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioSwatchItemCollectionViewCell else { return } + cell.radioSwatch.deselectSwatch() } } diff --git a/MVMCoreUI/Atomic/Atoms/RadioSwatchesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift similarity index 86% rename from MVMCoreUI/Atomic/Atoms/RadioSwatchesModel.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift index e5e0ce11..965b72f6 100644 --- a/MVMCoreUI/Atomic/Atoms/RadioSwatchesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift @@ -8,25 +8,39 @@ import Foundation -@objcMembers public class RadioSwatchesModel: MoleculeModelProtocol { +@objcMembers public class RadioSwatchesModel: MoleculeModelProtocol, FormFieldProtocol { public var backgroundColor: Color? public static var identifier: String = "radioSwatches" public var swatches: [RadioSwatchItemModel] + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? - public init(swatches: [RadioSwatchItemModel]) { - self.swatches = swatches + /// Returns the fieldValue of the selected swatch, otherwise the text of selected swatch. + public func formFieldValue() -> AnyHashable? { + let selectedSwatch = swatches.first { (swatch) -> Bool in + return (swatch.selected ?? false) + } + return selectedSwatch?.fieldValue ?? selectedSwatch?.text } private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor case swatches + case fieldKey + case groupName } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - self.swatches = try typeContainer.decode([RadioSwatchItemModel].self, forKey: .swatches) self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + self.swatches = try typeContainer.decode([RadioSwatchItemModel].self, forKey: .swatches) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + self.groupName = groupName + } + baseValue = formFieldValue() } public func encode(to encoder: Encoder) throws { @@ -34,10 +48,12 @@ import Foundation try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encode(swatches, forKey: .swatches) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(groupName, forKey: .groupName) } } -@objcMembers public class RadioSwatchItemModel: MoleculeModelProtocol, FormFieldProtocol { +@objcMembers public class RadioSwatchItemModel: MoleculeModelProtocol { public var backgroundColor: Color? public static var identifier: String = "radioSwatchItem" public var color: Color = Color(uiColor: .mvmBlue) @@ -47,24 +63,6 @@ import Foundation public var strikethrough: Bool? = false public var fieldKey: String? public var fieldValue: String? - public var baseValue: AnyHashable? - public var groupName: String = FormValidator.defaultGroupName - - public init(color: Color, text:String, selected: Bool, enabled: Bool, strikethrough: Bool, fieldKey: String, fieldValue: String, groupName:String) { - self.color = color - self.text = text - self.selected = selected - self.fieldValue = fieldValue - self.enabled = enabled - self.strikethrough = strikethrough - self.fieldKey = fieldKey - self.groupName = groupName - baseValue = selected - } - - public func formFieldValue() -> AnyHashable? { - return selected - } private enum CodingKeys: String, CodingKey { case moleculeName @@ -74,9 +72,7 @@ import Foundation case selected case enabled case strikethrough - case fieldKey case fieldValue - case groupName } required public init(from decoder: Decoder) throws { @@ -95,12 +91,7 @@ import Foundation if let strikethrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { self.strikethrough = strikethrough } - baseValue = self.selected - fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) - if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { - self.groupName = groupName - } } public func encode(to encoder: Encoder) throws { @@ -112,9 +103,7 @@ import Foundation try container.encodeIfPresent(selected, forKey: .selected) try container.encodeIfPresent(enabled, forKey: .enabled) try container.encodeIfPresent(strikethrough, forKey: .strikethrough) - try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) - try container.encodeIfPresent(groupName, forKey: .groupName) } } diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index ab0d5b43..1b050aca 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -70,6 +70,7 @@ import Foundation 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) + MoleculeObjectMapping.shared()?.register(viewClass: RadioSwatches.self, viewModelClass: RadioSwatchesModel.self) // Other Atoms MoleculeObjectMapping.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self)