From d8cda0f3dc5e5298977f089a2a17d56a0bb086d4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Oct 2022 09:03:27 -0500 Subject: [PATCH] Signed-off-by: Matt Bruce --- .../JSONCreator.xcodeproj/project.pbxproj | 4 + JSONCreator_iOS/JSONCreator/AppDelegate.swift | 145 +----------- .../JSON/Samples/FormContactInfo.json | 124 +++------- .../JSON/Samples/ToggleSample.json | 60 +++++ .../MF/JSONCreatorActionHandler.swift | 12 +- JSONCreator_iOS/JSONCreator/TestToggle.swift | 211 ++++++++++++++++++ 6 files changed, 310 insertions(+), 246 deletions(-) create mode 100644 JSONCreator_iOS/JSONCreator/JSON/Samples/ToggleSample.json create mode 100644 JSONCreator_iOS/JSONCreator/TestToggle.swift diff --git a/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj b/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj index 88447c8..a8825a0 100644 --- a/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj +++ b/JSONCreator_iOS/JSONCreator.xcodeproj/project.pbxproj @@ -77,6 +77,7 @@ EA5B696F2866BC1000B17D2E /* MVMCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA5B696C2866BC1000B17D2E /* MVMCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; EA5B69702866BC1000B17D2E /* MVMCoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA5B696D2866BC1000B17D2E /* MVMCoreUI.framework */; }; EA5B69712866BC1000B17D2E /* MVMCoreUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA5B696D2866BC1000B17D2E /* MVMCoreUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + EA797B2C2902D4BB00DBAFE6 /* TestToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA797B2B2902D4BB00DBAFE6 /* TestToggle.swift */; }; EAA658152875FA5E00484A7D /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAA658142875FA5E00484A7D /* VDSFormControlsTokens.xcframework */; }; EAA658162875FA5E00484A7D /* VDSFormControlsTokens.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EAA658142875FA5E00484A7D /* VDSFormControlsTokens.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; EACA5E5E2853DBC900CBA65B /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EACA5E5D2853DBC900CBA65B /* VDSColorTokens.xcframework */; }; @@ -164,6 +165,7 @@ EA3361FA2891D54A0071C351 /* VDSTypographyTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTypographyTokens.xcframework; path = ../SharedFrameworks/VDSTypographyTokens.xcframework; sourceTree = ""; }; EA5B696C2866BC1000B17D2E /* MVMCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EA5B696D2866BC1000B17D2E /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EA797B2B2902D4BB00DBAFE6 /* TestToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestToggle.swift; sourceTree = ""; }; EA7E676927582F2200ABF773 /* MVMCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EA7E676A27582F2200ABF773 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EAA658142875FA5E00484A7D /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = ""; }; @@ -228,6 +230,7 @@ D2B1E3FB22F4A6930065F95C /* Assets.xcassets */, D2B1E3FD22F4A6930065F95C /* LaunchScreen.storyboard */, D2B1E40022F4A6930065F95C /* Info.plist */, + EA797B2B2902D4BB00DBAFE6 /* TestToggle.swift */, ); path = JSONCreator; sourceTree = ""; @@ -479,6 +482,7 @@ D2FC4FB025897ACB00061EA4 /* OrderTracker.swift in Sources */, EA09CDD8282C40CC00A7835F /* GMFGBLEHandlerProtocol.swift in Sources */, EA09CDDC282C40CC00A7835F /* GMFGFotaHandler.swift in Sources */, + EA797B2C2902D4BB00DBAFE6 /* TestToggle.swift in Sources */, EA09CDD6282C40CC00A7835F /* GMFGConstant.swift in Sources */, D27564C925939E91003CA713 /* Links.swift in Sources */, EA09CDE6282C416C00A7835F /* BluetoothDebuggableProtocol.swift in Sources */, diff --git a/JSONCreator_iOS/JSONCreator/AppDelegate.swift b/JSONCreator_iOS/JSONCreator/AppDelegate.swift index 8becbf4..7f7d456 100644 --- a/JSONCreator_iOS/JSONCreator/AppDelegate.swift +++ b/JSONCreator_iOS/JSONCreator/AppDelegate.swift @@ -130,149 +130,6 @@ extension AppDelegate: MVMCoreGlobalTopAlertDelegateProtocol { extension AppDelegate { func register(){ - ModelRegistry.register(handler: TextEntryField.self, for: TextEntryField64Model.self) - ModelRegistry.register(handler: EmailVerifyField.self, for: EmailVerifyModel.self) - ModelRegistry.register(handler: ToggleWifiActionHandler.self, for: ToggleWifiActionModel.self) - } -} - -@objcMembers open class EmailVerifyModel: MoleculeModelProtocol { - - public static var identifier: String = "emailVerifyField" - public var moleculeName: String = EmailVerifyModel.identifier - public var emailField: TextEntryFieldModel - public var bottomMolecule: MoleculeModelProtocol - public var email: String - public var backgroundColor: MVMCoreUI.Color? - - public init(emailField: TextEntryFieldModel, email: String, bottomMolecule: MoleculeModelProtocol) { - self.emailField = emailField - self.email = email - self.bottomMolecule = bottomMolecule - } - - public required init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - emailField = try typeContainer.decode(TextEntryFieldModel.self, forKey: .emailField) - email = try typeContainer.decode(String.self, forKey: .email) - bottomMolecule = try typeContainer.decodeModel(codingKey: .bottomMolecule) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encode(emailField, forKey: .emailField) - try container.encode(email, forKey: .email) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeModel(bottomMolecule, forKey: .bottomMolecule) - } - - private enum CodingKeys: String, CodingKey { - case moleculeName - case emailField - case bottomMolecule - case backgroundColor - case email - } -} - -@objcMembers open class EmailVerifyField: View { - - private let emailField = TextEntryField() - - private var bottomView: MoleculeViewProtocol? - - private var emailVerifyFieldModel: EmailVerifyModel? { - return model as? EmailVerifyModel - } - - private var isBottomViewHidden: Bool { - guard let emailVerifyFieldModel = emailVerifyFieldModel, let text = emailField.text else { return true } - return !(emailVerifyFieldModel.email.caseInsensitiveCompare(text) == .orderedSame) - } - - private let containerView = MVMCoreUICommonViewsUtility.commonView() - - open override func setupView() { - super.setupView() - backgroundColor = .clear - clipsToBounds = true - addSubview(containerView) - NSLayoutConstraint.constraintPinSubview(toSuperview: containerView) - containerView.addSubview(emailField) - NSLayoutConstraint.constraintPinSubview(emailField, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true) - addEmailFieldObserver() - } - - private func addEmailFieldObserver() { - NotificationCenter.default.addObserver(self, selector: #selector(onEmailValueChange), name: UITextField.textDidChangeNotification, object: emailField.textField) - } - - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { - super.set(with: model, delegateObject, additionalData) - - guard let model = model as? EmailVerifyModel else { return } - - emailField.set(with: model.emailField, delegateObject, additionalData) - - if let bottomView = self.bottomView ?? ModelRegistry.createMolecule(model.bottomMolecule, delegateObject: delegateObject, additionalData: additionalData) { - bottomView.set(with: model.bottomMolecule, delegateObject, additionalData) - self.bottomView = bottomView - addBottomView(bottomView: bottomView) - } - } - - public func onEmailValueChange() { - bottomView?.isHidden = isBottomViewHidden - } - - private func addBottomView(bottomView: MoleculeViewProtocol) { - - containerView.addSubview(bottomView) - - bottomView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true - containerView.trailingAnchor.constraint(equalTo: bottomView.trailingAnchor).isActive = true - bottomView.topAnchor.constraint(equalTo: emailField.bottomAnchor).isActive = true - containerView.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true - bottomView.isHidden = isBottomViewHidden - } -} - -class ToggleWifiActionModel: ActionModelProtocol { - static var identifier: String = "toggleWifi" - var extraParameters: JSONValueDictionary? - var analyticsData: JSONValueDictionary? - var wifiId: String - var actionType: String = ToggleWifiActionModel.identifier - - init(wifiId: String) { - self.wifiId = wifiId - } -} - -class TextEntryField64Model: TextEntryFieldModel { - open override class var identifier: String { "textFieldBase64" } - - open override func formFieldServerValue() -> AnyHashable? { - guard let value = super.formFieldServerValue() as? String else { return nil } - return value.base64Encoded() - } - - required init(from decoder: Decoder) throws { - try super.init(from: decoder) - text = text?.base64Decoded() - } -} - -extension String { - - func base64Encoded() -> String? { - data(using: .utf8)?.base64EncodedString() - } - - func base64Decoded() -> String? { - guard let data = Data(base64Encoded: self) else { return nil } - return String(data: data, encoding: .utf8) + ModelRegistry.register(handler: TestToggle.self, for: TestToggleModel.self) } } diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json index 6918dfb..9f494ea 100644 --- a/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/FormContactInfo.json @@ -22,72 +22,43 @@ { "moleculeName": "stackItem", "molecule": { - "moleculeName": "testToggle", + "moleculeName": "toggle", "fieldKey": "isActive" } }, - { - "moleculeName": "stackItem", - "molecule": { - "moleculeName": "textField", - "fieldKey": "firstName", - "type": "text", - "errorMessage": "Please enter a valid first name.", - "placeholder": "John A", - "titleLabel": { - "moleculeName": "label", - "text": "First Name" + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName":"labelToggle", + "label":{ + "moleculeName": "label", + "text":"Label Text Goes Here" + }, + "toggle":{ + "moleculeName": "toggle" + } + } + }, + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName":"headlineBodyToggle", + "headlineBody":{ + "moleculeName": "headlineBody", + "headline":{ + "moleculeName": "label", + "text": "Headline Text Goes Here" + }, + "body":{ + "moleculeName": "label", + "text": "Body Text Goes Here" + } + }, + "toggle":{ + "moleculeName": "toggle" + } } } - }, - { - "moleculeName": "stackItem", - "molecule": { - "moleculeName": "textField", - "fieldKey": "lastName", - "type": "text", - "placeholder": "Smith", - "errorMessage": "Please enter a valid last name.", - "titleLabel": { - "moleculeName": "label", - "text": "Last Name" - } - } - }, - { - "moleculeName": "stackItem", - "molecule": { - "moleculeName": "textField", - "fieldKey": "phoneNumber", - "type": "phone", - "placeholder": "212-555-1234", - "title": "Contact Phone Number", - "errorMessage": "Please enter a valid phone number." - } - }, - { - "moleculeName": "stackItem", - "molecule": { - "moleculeName": "textField", - "fieldKey": "emailID", - "type": "text", - "placeholder": "JSMith123@gmail.com", - "title": "Email", - "errorMessage": "Please enter a valid greeting name." - } - }, - { - "moleculeName": "stackItem", - "molecule": { - "moleculeName": "textFieldBase64", - "fieldKey": "zipcode", - "type": "number", - "placeholder": "90210", - "title": "Zip Code", - "text": "NzUwMzQ=", - "errorMessage": "Please enter a valid zip code." - } - } ] }, "footer": { @@ -113,36 +84,7 @@ { "groupName": "default", "rules": [ - { - "type": "regex", - "fields": [ - "emailID" - ], - "regex": "^[a-zA-Z0-9](\\.?\\_?\\-?[a-zA-Z0-9]){0,}@[a-zA-Z0-9-_]+\\.([a-zA-Z0-9-_]{1,}\\.){0,}[a-zA-Z]{2,}$" - }, - { - "type": "regex", - "fields": [ - "zipcode" - ], - "regex": "^\\d{5}(?:[-\\s]\\d{4})?$" - }, - { - "regex": "^(\\d{3})[\\s.-]{0,1}(\\d{3})[\\s.-]{0,1}(\\d{4})$", - "type": "regex", - "fields": [ - "phoneNumber" - ] - }, - { - "type": "allRequired", - "ruleId": "requiredRule", - "fields": [ - "emailID", - "firstName", - "lastName" - ] - } + ] } ] diff --git a/JSONCreator_iOS/JSONCreator/JSON/Samples/ToggleSample.json b/JSONCreator_iOS/JSONCreator/JSON/Samples/ToggleSample.json new file mode 100644 index 0000000..e9a76a0 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/JSON/Samples/ToggleSample.json @@ -0,0 +1,60 @@ +{ + "Page": { + "template": "stack", + "pageType": "moleculeStack", + "screenHeading": "Manage Profile", + "hideFabOverlay": true, + "suppressPostLaunchRequests": false, + "tabBarHidden": true, + "header": { + "moleculeName": "header", + "molecule": { + "moleculeName": "headlineBody", + "headline": { + "moleculeName": "label", + "text": "Zenkey" + } + } + }, + "stack": { + "moleculeName": "stack", + "molecules": [ + { + "moleculeName": "stackItem", + "molecule": { + "moleculeName": "toggle", + "fieldKey": "isActive" + } + } + ] + }, + "footer": { + "moleculeName": "footer", + "molecule": { + "moleculeName": "twoButtonView", + "primaryButton": { + "moleculeName": "button", + "title": "Edit", + "groupName": "default", + "action": { + "actionType": "openPage", + "pageType": "updateProfile", + "extraParameters": { + "from": "none" + }, + "presentationStyle": "push" + } + } + } + }, + "formRules": [ + { + "groupName": "default", + "rules": [ + + ] + } + ] + } +} + diff --git a/JSONCreator_iOS/JSONCreator/MF/JSONCreatorActionHandler.swift b/JSONCreator_iOS/JSONCreator/MF/JSONCreatorActionHandler.swift index 77e25b0..378af3c 100644 --- a/JSONCreator_iOS/JSONCreator/MF/JSONCreatorActionHandler.swift +++ b/JSONCreator_iOS/JSONCreator/MF/JSONCreatorActionHandler.swift @@ -22,17 +22,7 @@ import MVMCoreUI } public class JSONCreatorActionHandler: MVMCoreUIActionHandler { - public override func handleOtherActions(_ actionType: String?, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -> Bool { - if actionType == "print" { - if actionInformation?.boolForKey("delay") ?? false { - sleep(2) - } - print(actionInformation?.stringForkey("text") ?? "fail") - return true - } - return super.handleOtherActions(actionType, actionInformation: actionInformation, additionalData: additionalData, delegateObject: delegateObject) - } - + public static func doStuff() { MVMCoreObject.sharedInstance()?.actionHandler = JSONCreatorActionHandler() ModelRegistry.register(ActionPrintModel.self) diff --git a/JSONCreator_iOS/JSONCreator/TestToggle.swift b/JSONCreator_iOS/JSONCreator/TestToggle.swift new file mode 100644 index 0000000..92f92f4 --- /dev/null +++ b/JSONCreator_iOS/JSONCreator/TestToggle.swift @@ -0,0 +1,211 @@ +// +// TestToggle.swift +// JSONCreator +// +// Created by Matt Bruce on 10/21/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore +import MVMCoreUI +import UIKit +import VDS + +/** + A custom implementation of Apple's UISwitch. + + By default this class begins in the off state. + + Container: The background of the toggle control. + Knob: The circular indicator that slides on the container. + */ +open class TestToggle: ToggleBase, VDSMoleculeViewProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var viewModel: TestToggleModel! + public var delegateObject: MVMCoreUIDelegateObject? + public var additionalData: [AnyHashable: Any]? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public override func initialSetup() { + super.initialSetup() + + publisher(for: .touchUpInside) + .sink {[weak self] toggle in + guard let self = self else { return } + self.toggle() + }.store(in: &subscribers) + + publisher(for: .valueChanged) + .sink {[weak self] toggle in + guard let self = self else { return } + self.valueChanged(isOn: toggle.isOn) + }.store(in: &subscribers) + + accessibilityLabelEnabled = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") + accessibilityLabelDisabled = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") + accessibilityHintEnabled = MVMCoreUIUtility.hardcodedString(withKey: "AccToggleHint") + accessibilityHintDisabled = MVMCoreUIUtility.hardcodedString(withKey: "AccDisabled") + accessibilityValueEnabled = MVMCoreUIUtility.hardcodedString(withKey: "AccOn") + accessibilityValueDisabled = MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + } + + // MARK:- MVMCoreViewProtocol + open func updateView(_ size: CGFloat) {} + + open func viewModelDidUpdate() { + guard let viewModel else { return } + + additionalData = additionalData.dictionaryAdding(key: KeySourceModel, value: viewModel) + } + + private func valueChanged(isOn: Bool){ + guard let viewModel else { return } + //sync the value on the viewModel + viewModel.selected = isOn + + //tell the form you changed + _ = FormValidator.validate(delegate: self.delegateObject?.formHolderDelegate) + + if viewModel.action != nil || viewModel.alternateAction != nil { + var action: ActionModelProtocol? + if isOn { + action = viewModel.action + } else { + action = viewModel.alternateAction ?? viewModel.action + } + if let action { + MVMCoreUIActionHandler.performActionUnstructured(with: action, + sourceModel: viewModel, + additionalData: additionalData, + delegateObject: delegateObject) + } + } + + print("toggle value changed to: \(isOn)") + print("viewModel server value: \(viewModel.formFieldServerValue()!)") + } + + public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 44 + } + + private typealias ActionDefinition = (model: ActionModelProtocol, + sourceModel: MoleculeModelProtocol?) + + private func performActionUnstructured(definition: ActionDefinition) { + MVMCoreUIActionHandler.performActionUnstructured(with: definition.model, + sourceModel: definition.sourceModel, + additionalData: additionalData, + delegateObject: delegateObject) + } +} +// MARK: - MVMCoreUIViewConstrainingProtocol +extension TestToggle { + + public func needsToBeConstrained() -> Bool { true } + + public func horizontalAlignment() -> UIStackView.Alignment { .trailing } +} + +public class TestToggleModel: MoleculeModelProtocol, FormFieldProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "testToggle" + public var backgroundColor: Color? //not used + + public var selected: Bool = false + public var enabled: Bool = true + public var readOnly: Bool = false + public var action: ActionModelProtocol? + public var alternateAction: ActionModelProtocol? + public var accessibilityText: String? + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case state + case enabled + case readOnly + case action + case accessibilityIdentifier + case alternateAction + case accessibilityText + case fieldKey + case groupName + } + + //-------------------------------------------------- + // MARK: - Form Valdiation + //-------------------------------------------------- + + public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + return selected + } + + //-------------------------------------------------- + // MARK: - Server Value + //-------------------------------------------------- + open func formFieldServerValue() -> AnyHashable? { + return formFieldValue() + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ state: Bool) { + selected = state + baseValue = state + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { + selected = state + } + action = try typeContainer.decodeModelIfPresent(codingKey: .action) + alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction) + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + baseValue = selected + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + if let gName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + groupName = gName + } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false + + + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) + try container.encodeModelIfPresent(action, forKey: .action) + try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(selected, forKey: .state) + try container.encode(enabled, forKey: .enabled) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encode(readOnly, forKey: .readOnly) + } +}