diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 6f4709e4..880487b4 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -42,10 +42,10 @@ 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; - 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */; }; + 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; - 0ABD136B237B193A0081388D /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormView.swift */; }; + 0ABD136B237B193A0081388D /* FormFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormFieldContainer.swift */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; @@ -236,12 +236,12 @@ 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; - 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownEntryField.swift; sourceTree = ""; }; + 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = ""; }; 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; - 0ABD136A237B193A0081388D /* FormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = ""; }; + 0ABD136A237B193A0081388D /* FormFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldContainer.swift; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; @@ -452,7 +452,7 @@ 0ABD1369237B18EE0081388D /* views */ = { isa = PBXGroup; children = ( - 0ABD136A237B193A0081388D /* FormView.swift */, + 0ABD136A237B193A0081388D /* FormFieldContainer.swift */, ); path = views; sourceTree = ""; @@ -809,7 +809,7 @@ 0A21DB7E235DECC500C160A2 /* EntryField.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, - 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */, + 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */, 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */, 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */, ); @@ -1187,14 +1187,14 @@ D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, - 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */, + 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, - 0ABD136B237B193A0081388D /* FormView.swift in Sources */, + 0ABD136B237B193A0081388D /* FormFieldContainer.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift similarity index 74% rename from MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift rename to MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift index a9d5d693..11b6917f 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -1,5 +1,5 @@ // -// DropdownEntryField.swift +// BaseDropdownEntryField.swift // MVMCoreUI // // Created by Kevin Christiano on 10/23/19. @@ -9,10 +9,10 @@ import UIKit /** - This class is intended to be subclassed. - See ItemDropdownEntryField and DateDropdownEntryField. + This class is intended to be subclassed. + See ItemDropdownEntryField and DateDropdownEntryField. */ -@objcMembers open class DropdownEntryField: TextEntryField { +@objcMembers open class BaseDropdownEntryField: TextEntryField { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -31,9 +31,11 @@ import UIKit // MARK: - Property Observers //-------------------------------------------------- - public override var isEnabled: Bool { - didSet { - dropDownCaretView.isEnabled = isEnabled + @objc public override var isEnabled: Bool { + get { super.isEnabled } + set (enabled) { + dropDownCaretView.isEnabled = enabled + super.isEnabled = enabled } } @@ -41,11 +43,11 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("DropdownEntryField does not support xib.") } @@ -54,7 +56,7 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - public override func setupFieldContainerContent(_ container: UIView) { + @objc public override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) container.addSubview(dropDownCaretView) @@ -66,7 +68,6 @@ import UIKit container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true - dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true let caretTap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) @@ -75,15 +76,13 @@ import UIKit } // MARK: - Molecular -extension DropdownEntryField { +extension BaseDropdownEntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } - if let _ = dictionary[KeyType] as? String { - dropDownCaretView.isHidden = false - } + dropDownCaretView.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) } } diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 0a9952ed..f0a7a368 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -9,7 +9,7 @@ import UIKit -open class DateDropdownEntryField: DropdownEntryField { +@objcMembers open class DateDropdownEntryField: BaseDropdownEntryField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -32,44 +32,41 @@ open class DateDropdownEntryField: DropdownEntryField { return formatter }() - //-------------------------------------------------- - // MARK: - Delegate - //-------------------------------------------------- - - /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. - private weak var proprietorTextDelegate: UITextFieldDelegate? - //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public convenience init() { - self.init(frame: .zero) - + @objc public override init(frame: CGRect) { + super.init(frame: frame) setup() } - public convenience init(startDate: Date, endDate: Date, showStartDate: Bool = true) { + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc public convenience init(startDate: Date, endDate: Date, showStartDate: Bool = true) { self.init(frame: .zero) - - setup() setDatePickerDuration(from: startDate, to: endDate, showStartDate: showStartDate) } - private func setup() { + @objc required public init?(coder: NSCoder) { + fatalError("DateDropdownEntryField init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + @objc private func setup() { datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) datePicker?.addTarget(self, action: #selector(pickerValueChanged), for: .valueChanged) datePicker?.timeZone = NSTimeZone.system MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: self) - uiTextFieldDelegate = self } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - public func setDatePickerDuration(from startDate: Date?, to endDate: Date?, showStartDate: Bool = true) { + @objc public func setDatePickerDuration(from startDate: Date?, to endDate: Date?, showStartDate: Bool = true) { datePicker?.minimumDate = startDate datePicker?.maximumDate = endDate @@ -79,7 +76,7 @@ open class DateDropdownEntryField: DropdownEntryField { } } - public func dismissDatePicker() -> Date? { + @objc public func dismissDatePicker() -> Date? { let pickedDate = datePicker?.date setTextWith(date: pickedDate) @@ -88,8 +85,8 @@ open class DateDropdownEntryField: DropdownEntryField { return pickedDate } - private func setTextWith(date: Date?) { - + @objc private func setTextWith(date: Date?) { + guard let date = date else { return } if calendar.isDate(date, inSameDayAs: Date()) { @@ -104,26 +101,16 @@ open class DateDropdownEntryField: DropdownEntryField { super.dismissFieldInput(sender) } - @objc func pickerValueChanged(_ sender: UIDatePicker){ - - setTextWith(date: datePicker?.date) - } -} - -// MARK: - UITextField Intercept -extension DateDropdownEntryField { - - public func textFieldDidBeginEditing(_ textField: UITextField) { + @objc func pickerValueChanged(_ sender: UIDatePicker) { - isSelected = true - proprietorTextDelegate?.textFieldDidBeginEditing?(textField) + setTextWith(date: datePicker?.date) } } // MARK: - Molecular extension DateDropdownEntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 4ad1b1ad..79d4e554 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -8,9 +8,8 @@ import UIKit -@objc protocol DigitBoxDelegate: NSObjectProtocol { +@objc protocol DigitBoxDelegate { @objc optional func digitFieldDidDelete(_ textField: UITextField?) - @objc optional func textFieldDidChange(_ textField: UITextField) } @@ -40,11 +39,12 @@ import UIKit //-------------------------------------------------- public override var showError: Bool { - didSet { + get { return super.showError } + set (error) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.borderStrokeColor = self.showError ? .mfPumpkin() : .mfSilver() + self.borderStrokeColor = error ? .mfPumpkin() : .mfSilver() let barHeight: CGFloat = self.showError ? 4 : 1 self.bottomBar?.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight) @@ -53,6 +53,7 @@ import UIKit self.setNeedsDisplay() self.layoutIfNeeded() } + super.showError = error } } @@ -142,16 +143,7 @@ import UIKit //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if string.isBackspace { - digitBoxDelegate?.digitFieldDidDelete?(self.digitField) - } - - return true - } - + public override func updateView(_ size: CGFloat) { super.updateView(size) @@ -188,13 +180,3 @@ import UIKit } } } - - -// TODO: Move if working properly. -extension String { - - var isBackspace: Bool { - let char = self.cString(using: String.Encoding.utf8)! - return strcmp(char, "\\b") == -92 - } -} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 6caee0c9..ce53c0d1 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -27,14 +27,15 @@ import UIKit //-------------------------------------------------- public override var isEnabled: Bool { - didSet { - titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() + get { return super.isEnabled } + set (enabled) { + titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() digitBoxes.forEach { - $0.isEnabled = self.isEnabled - $0.isUserInteractionEnabled = isEnabled - $0.digitField.isEnabled = isEnabled - $0.digitField.textColor = isEnabled ? .black : .mfBattleshipGrey() + $0.isEnabled = enabled + $0.isUserInteractionEnabled = enabled + $0.digitField.isEnabled = enabled + $0.digitField.textColor = enabled ? .black : .mfBattleshipGrey() } } } @@ -46,8 +47,9 @@ import UIKit } public override var isLocked: Bool { - didSet { - digitBoxes.forEach { $0.isLocked = self.isLocked } + get { return super.isLocked } + set (locked) { + digitBoxes.forEach { $0.isLocked = locked } } } @@ -127,25 +129,20 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - public convenience init(numberOfDigits: Int) { + @objc public convenience init(numberOfDigits: Int) { self.init(frame: .zero) self.numberOfDigits = numberOfDigits } - public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?, size: CGFloat? = nil) { - self.numberOfDigits = numberOfDigits - super.init(bothDelegates: delegate) - } - - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("DigitEntryField xib has not been implemented") } @@ -154,15 +151,15 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - public override func setupFieldContainerContent(_ container: UIView) { + @objc public override func setupFieldContainerContent(_ container: UIView) { alignCenterHorizontal() isAccessibilityElement = false - entryContainer.disableBorders = true + entryContainer.disableAllBorders = true assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } - private func createDigitField() -> DigitBox { + @objc private func createDigitField() -> DigitBox { let digitBox = DigitBox() digitBox.isAccessibilityElement = true @@ -172,7 +169,7 @@ import UIKit return digitBox } - func assembleDigitFieldsView(size: CGFloat) { + @objc func assembleDigitFieldsView(size: CGFloat) { var accessibleElements: [Any] = [titleLabel] @@ -218,10 +215,10 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - open override func updateView(_ size: CGFloat) { + @objc open override func updateView(_ size: CGFloat) { super.updateView(size) - entryContainer.disableBorders = true + entryContainer.disableAllBorders = true DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -234,10 +231,10 @@ import UIKit } } - open override func reset() { + @objc open override func reset() { super.reset() - entryContainer.disableBorders = false + entryContainer.disableAllBorders = false digitBoxes.forEach { $0.reset() } } @@ -245,7 +242,7 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - public func setAsSecureTextEntry(_ secureEntry: Bool) { + @objc public func setAsSecureTextEntry(_ secureEntry: Bool) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -259,7 +256,7 @@ import UIKit } } - public override func defaultValidationBlock() { + @objc public override func defaultValidationBlock() { validationBlock = { enteredValue in guard let enteredValue = enteredValue else { return false } @@ -268,7 +265,7 @@ import UIKit } } - public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { + @objc public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { var selectPreviousField = false @@ -292,7 +289,7 @@ import UIKit } } - public func selectNextDigitField(_ currentTextField: UITextField?, clear: Bool) { + @objc public func selectNextDigitField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false @@ -326,7 +323,7 @@ import UIKit selectedDigitField?.digitField.becomeFirstResponder() } - override open func resignFirstResponder() -> Bool { + @objc override open func resignFirstResponder() -> Bool { selectedDigitField?.digitField.resignFirstResponder() selectedDigitField?.isSelected = false @@ -343,7 +340,7 @@ import UIKit } } - open class func getEnabledDigitFields(_ textFieldToDetermine: [DigitBox]) -> [TextField]? { + @objc open class func getEnabledDigitFields(_ textFieldToDetermine: [DigitBox]) -> [TextField]? { return textFieldToDetermine.filter { $0.isEnabled }.compactMap { $0.digitField } } @@ -352,7 +349,6 @@ import UIKit // MARK: - TextField Delegate extension DigitEntryField { - @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() @@ -360,12 +356,7 @@ extension DigitEntryField { return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true } - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if string.isBackspace { - selectPreviousDigitField(textField, clear: true) - return true - } + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false @@ -400,7 +391,7 @@ extension DigitEntryField { return false } - func digitFieldDidDelete(_ textField: UITextField?) { + @objc func digitFieldDidDelete(_ textField: UITextField?) { // Empty cell, go back to previous cell and clear. selectPreviousDigitField(textField, clear: true) @@ -454,7 +445,7 @@ extension DigitEntryField { // MARK: - Molecular extension DigitEntryField { - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json else { return } @@ -473,7 +464,7 @@ extension DigitEntryField { } } - open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + @objc open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 115 } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index ef6ee97e..e31199bc 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -49,83 +49,97 @@ import UIKit weak var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- - public var isValid = false + public var isValid: Bool = false public var fieldKey: String? public var errorMessage: String? + /// Determines whther the feedback label will clear itself after user interaction or display update. +// public var fixedFeedback: Bool = false + + private var _isEnabled: Bool = true + private var _showError: Bool = false + private var _isLocked: Bool = false + private var _isSelected: Bool = false + //-------------------------------------------------- - // MARK: - Property Observers + // MARK: - Computed Properties //-------------------------------------------------- - /// Toggles error or original UI. - public var showError = false { - willSet { - isLocked = false - isSelected = false - isEnabled = false - } - didSet { + /// Toggles enabled (original) or disabled UI. + public var isEnabled: Bool { + get { return _isEnabled } + set (enabled) { + + _isEnabled = enabled + _isLocked = false + _isSelected = false + _showError = false + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.showError = self.showError - self.feedback = self.showError ? self.errorMessage : nil + self.entryContainer.isEnabled = enabled + self.feedbackLabel.textColor = enabled ? .black : .mfSilver() + self.titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() } } } - /// Toggles enabled (original) or disabled UI. - public var isEnabled = true { - willSet { - isLocked = false - isSelected = false - showError = false - } - didSet { + /// Toggles error or original UI. + public var showError: Bool { + get { return _showError } + set (error) { + + _showError = error + _isLocked = false + _isSelected = false + _isEnabled = true + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isUserInteractionEnabled = self.isEnabled - self.entryContainer.isEnabled = self.isEnabled - self.feedbackLabel.textColor = self.isEnabled ? .black : .mfSilver() - self.titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() + self.entryContainer.showError = error + self.feedback = error ? self.errorMessage : nil } } } /// Toggles original or locked UI. - public var isLocked = false { - willSet { - isEnabled = true - isSelected = false - showError = false - } - didSet { + public var isLocked: Bool { + get { return _isLocked } + set (locked) { + + _isLocked = locked + _isEnabled = true + _isSelected = false + _showError = false + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isUserInteractionEnabled = !self.isLocked - self.entryContainer.isLocked = self.isLocked + self.entryContainer.isLocked = locked } } } /// Toggles selected or original (unselected) UI. - public var isSelected = false { - willSet { - isLocked = false - isEnabled = true - showError = false - } - didSet { + public var isSelected: Bool { + get { return _isSelected } + set (selected) { + + _isSelected = selected + _isLocked = false + _isEnabled = true + _showError = false + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.isSelected = self.isSelected + self.entryContainer.isSelected = selected } } } @@ -137,9 +151,9 @@ import UIKit /// Sets the text of titleLabel public var title: String? { get { return titleLabel.text } - set { - titleLabel.text = newValue - setAccessibilityString(newValue) + set (newText) { + titleLabel.text = newText + setAccessibilityString(newText) } } @@ -152,9 +166,9 @@ import UIKit /// Sets feedback text in the textField. public var feedback: String? { get { return feedbackLabel.text } - set { - feedbackLabel.text = newValue - setAccessibilityString(newValue) + set (newFeedback) { + feedbackLabel.text = newFeedback + setAccessibilityString(newFeedback) entryContainer.refreshUI() } } @@ -180,21 +194,21 @@ import UIKit //-------------------------------------------------- /// This must be overriden by a subclass. - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - public init(title: String) { + @objc public init(title: String) { super.init(frame: .zero) titleLabel.text = title } - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("EntryField does not support xib.") } @@ -204,7 +218,7 @@ import UIKit //-------------------------------------------------- /// Initial configuration of class and view. - final public override func setupView() { + @objc final public override func setupView() { guard subviews.isEmpty else { return } @@ -243,7 +257,7 @@ import UIKit layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true } - open override func layoutSubviews() { + @objc open override func layoutSubviews() { super.layoutSubviews() entryContainer.refreshUI() @@ -251,11 +265,11 @@ import UIKit /// Method to override. /// Intended to add the interactive content (i.e. textField) to the entryContainer. - open func setupFieldContainerContent(_ container: UIView) { + @objc open func setupFieldContainerContent(_ container: UIView) { // To be overridden by subclass. } - open override func updateView(_ size: CGFloat) { + @objc open override func updateView(_ size: CGFloat) { super.updateView(size) titleLabel.updateView(size) @@ -263,13 +277,18 @@ import UIKit entryContainer.updateView(size) } - open override func reset() { + @objc open override func reset() { super.reset() + isEnabled = true + _isLocked = false + _isSelected = false + _showError = false + backgroundColor = .clear titleLabel.reset() feedbackLabel.reset() - entryContainer.subviews.forEach { $0.removeFromSuperview() } + entryContainer.reset() titleLabel.textColor = .mfBattleshipGrey() } @@ -277,14 +296,14 @@ import UIKit // MARK: - Constraint Methods //-------------------------------------------------- - open override func setLeftPinConstant(_ constant: CGFloat) { + @objc open override func setLeftPinConstant(_ constant: CGFloat) { entryContainerLeading?.constant = constant feedbackLabelLeading?.constant = constant titleLabelLeading?.constant = constant } - open override func setRightPinConstant(_ constant: CGFloat) { + @objc open override func setRightPinConstant(_ constant: CGFloat) { entryContainerTrailing?.constant = constant feedbackLabelTrailing?.constant = constant @@ -295,7 +314,7 @@ import UIKit // MARK: - Molecular extension EntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) self.delegateObject = delegateObject @@ -303,7 +322,7 @@ extension EntryField { entryContainer.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) - if let titleText = dictionary["title"] as? String { + if let titleText = dictionary[KeyTitle] as? String { title = titleText } @@ -319,13 +338,17 @@ extension EntryField { self.isLocked = isLocked } + if let isSelected = dictionary["isSelected"] as? Bool { + self.isSelected = isSelected + } + // Key used to send text value to server if let fieldKey = dictionary[KeyFieldKey] as? String { self.fieldKey = fieldKey } } - override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + @objc override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 115 } } @@ -333,15 +356,15 @@ extension EntryField { // MARK: - Form Validation extension EntryField: FormValidationProtocol { - public func isValidField() -> Bool { + @objc public func isValidField() -> Bool { return isValid } - public func formFieldName() -> String? { + @objc public func formFieldName() -> String? { return fieldKey } - public func formFieldValue() -> Any? { + @objc public func formFieldValue() -> Any? { return text } } diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index f77dcf78..b8085014 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -9,7 +9,7 @@ import UIKit -open class ItemDropdownEntryField: DropdownEntryField { +open class ItemDropdownEntryField: BaseDropdownEntryField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,59 +19,49 @@ open class ItemDropdownEntryField: DropdownEntryField { public var componentsCount = 1 - /// When selecting first responder, allow initial selected value to appear in empty text field. + /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true - //-------------------------------------------------- - // MARK: - Delegate - //-------------------------------------------------- - - /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. - private weak var proprietorTextDelegate: UITextFieldDelegate? - - /// If you're using a MFViewController, you must set this to it - public override weak var uiTextFieldDelegate: UITextFieldDelegate? { - get { return textField.delegate } - set { - textField.delegate = self - proprietorTextDelegate = newValue - } - } - //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public convenience init() { - self.init(frame: .zero) + @objc public override init(frame: CGRect) { + super.init(frame: frame) setup() } - public convenience init(pickerData: [String]) { + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc public convenience init(pickerData: [String]) { self.init(frame: .zero) - self.pickerData = pickerData - setup() } - private func setup() { - - pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) - textField.hideBlinkingCaret = true - uiTextFieldDelegate = self + @objc required public init?(coder: NSCoder) { + fatalError("ItemDropdownEntryField init(coder:) has not been implemented") } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { + @objc private func setup() { + + pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) + textField.hideBlinkingCaret = true + uiTextFieldDelegate = self + } + + @objc public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { pickerView?.delegate = delegate pickerView?.dataSource = delegate } - private func setInitialValueFromPicker() { + @objc private func setInitialValueFromPicker() { if setInitialValueInTextField, let pickerIndex = pickerView?.selectedRow(inComponent: 0) { text = pickerData[pickerIndex] @@ -88,19 +78,19 @@ open class ItemDropdownEntryField: DropdownEntryField { // MARK:- Base Picker Delegate extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { - public func numberOfComponents(in pickerView: UIPickerView) -> Int { + @objc public func numberOfComponents(in pickerView: UIPickerView) -> Int { return componentsCount } - public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + @objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count } - public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + @objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[row] } - public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + @objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { text = pickerData[row] } } @@ -108,7 +98,7 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { // MARK: - Molecular extension ItemDropdownEntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } @@ -119,20 +109,10 @@ extension ItemDropdownEntryField { } } -// MARK: - UITextField Intercept +// MARK: - Accessibility extension ItemDropdownEntryField { - public func textFieldDidBeginEditing(_ textField: UITextField) { - - setInitialValueFromPicker() - proprietorTextDelegate?.textFieldDidBeginEditing?(textField) - } -} - -// MARK: - Accessibility -extension DropdownEntryField { - - open override func setAccessibilityString(_ accessibilityString: String?) { + @objc open override func setAccessibilityString(_ accessibilityString: String?) { var accessibilityString = accessibilityString ?? "" diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 0dec727a..0905340d 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -16,7 +16,7 @@ import MVMCore */ @objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- public var isNationalMDN = true @@ -30,7 +30,7 @@ import MVMCore private weak var proprietorTextDelegate: UITextFieldDelegate? //-------------------------------------------------- - // MARK: - Property Observers + // MARK: - Computed Properties //-------------------------------------------------- /// Formats the MDN when setting and removes format of MDN when reading. @@ -52,21 +52,15 @@ import MVMCore // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: .zero) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public override init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - super.init(frame: .zero) - setBothTextDelegates(to: bothDelegates) - } - - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("MdnEntryField xib not supported.") } @@ -75,7 +69,7 @@ import MVMCore // MARK: - Setup //-------------------------------------------------- - public override func setupFieldContainerContent(_ container: UIView) { + @objc public override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) textField.keyboardType = .numberPad @@ -92,7 +86,7 @@ import MVMCore // MARK: - Methods //-------------------------------------------------- - public func hasValidMDN() -> Bool { + @objc public func hasValidMDN() -> Bool { guard let MDN = mdn, !MDN.isEmpty else { return true } @@ -103,13 +97,13 @@ import MVMCore return MVMCoreUIUtility.validateInternationalMDNString(MDN) } - public func validateAndColor() -> Bool { + @objc public func validateAndColor() -> Bool { if !shouldValidateMDN { let isValid = hasValidMDN() if isValid { - clearErrorState() + showError = false } else { errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") showError = true @@ -136,7 +130,7 @@ import MVMCore // MARK: - Contact Picker Delegate //-------------------------------------------------- - public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + @objc public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { if let phoneNumber = contactProperty.value as? CNPhoneNumber { @@ -163,14 +157,14 @@ import MVMCore // MARK: - Implemented TextField Delegate //-------------------------------------------------- - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true } - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false @@ -179,13 +173,13 @@ import MVMCore return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } - public func textFieldDidBeginEditing(_ textField: UITextField) { + @objc public func textFieldDidBeginEditing(_ textField: UITextField) { textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } - public func textFieldDidEndEditing(_ textField: UITextField) { + @objc public func textFieldDidEndEditing(_ textField: UITextField) { proprietorTextDelegate?.textFieldDidEndEditing?(textField) @@ -194,21 +188,17 @@ import MVMCore } } - //-------------------------------------------------- - // MARK: - Passed Along TextField delegate - //-------------------------------------------------- - - public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } - public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true } - public func textFieldShouldClear(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index d3d33bcc..2e7c151b 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -36,30 +36,41 @@ import UIKit }() //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- /// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property. public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.black, .mfSilver()) - public var observingForChange = false + public var observingForChange: Bool = false //-------------------------------------------------- - // MARK: - Property Observers + // MARK: - Computed Properties //-------------------------------------------------- public override var isEnabled: Bool { - didSet { + get { return super.isEnabled } + set (enabled) { + super.isEnabled = enabled + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.textField.isEnabled = self.isEnabled - self.textField.textColor = self.isEnabled ? self.textColor.enabled : self.textColor.disabled + self.textField.isEnabled = enabled + self.textField.textColor = enabled ? self.textColor.enabled : self.textColor.disabled } } } - /// The text of this textField. + public override var showError: Bool { + get { return super.showError } + set (error) { + textField.accessibilityValue = nil + super.showError = error + } + } + + /// The text of this TextField. public override var text: String? { get { return textField.text } set { @@ -74,6 +85,10 @@ import UIKit set { textField.placeholder = newValue } } + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + public var validationBlock: ((_ value: String?) -> Bool)? { didSet { valueChanged() } } @@ -122,21 +137,15 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - super.init(frame: .zero) - setBothTextDelegates(to: bothDelegates) - } - - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("TextEntryField does not support xib.") } @@ -145,7 +154,7 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - open override func setupFieldContainerContent(_ container: UIView) { + @objc open override func setupFieldContainerContent(_ container: UIView) { MFStyler.styleTextField(textField) container.addSubview(textField) @@ -162,42 +171,28 @@ import UIKit accessibilityElements = [titleLabel, textField, feedbackLabel] } - open override func updateView(_ size: CGFloat) { + @objc open override func updateView(_ size: CGFloat) { super.updateView(size) MFStyler.styleTextField(textField) layoutIfNeeded() } - deinit { + @objc deinit { setBothTextDelegates(to: nil) } - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - open func clearErrorState() { - - textField.accessibilityValue = nil - feedback = nil - showError = false - } - - public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { observingTextFieldDelegate = delegate uiTextFieldDelegate = delegate } - public func defaultValidationBlock() { - - validationBlock = { enteredValue in - return (enteredValue?.count ?? 0) > 0 - } - } + //-------------------------------------------------- + // MARK: - Observing for Change (TextFieldDelegate) + //-------------------------------------------------- - override open func resignFirstResponder() -> Bool { + @objc override open func resignFirstResponder() -> Bool { textField.resignFirstResponder() isSelected = false @@ -209,10 +204,14 @@ import UIKit _ = self.resignFirstResponder() } - //-------------------------------------------------- - // MARK: - Observing for Change (TextFieldDelegate) - //-------------------------------------------------- + public func defaultValidationBlock() { + + validationBlock = { enteredValue in + return (enteredValue?.count ?? 0) > 0 + } + } + /// Executes on UITextField.textDidChangeNotification @objc func valueChanged() { if !showError { @@ -225,19 +224,20 @@ import UIKit isValid = validationBlock?(text) ?? true if previousValidity && !isValid { - feedback = errorMessage + showError = true observingTextFieldDelegate?.isInvalid?(textfield: self) } else if !previousValidity && isValid { - clearErrorState() + showError = false observingTextFieldDelegate?.isValid?(textfield: self) } } + /// Executes on UITextField.textDidEndEditingNotification @objc func endInputing() { if isValid { - clearErrorState() + showError = false entryContainer.bottomBar?.backgroundColor = UIColor.black.cgColor } else if let errMessage = errorMessage { @@ -245,6 +245,7 @@ import UIKit } } + /// Executes on UITextField.textDidBeginEditingNotification @objc func startEditing() { isSelected = true @@ -255,7 +256,7 @@ import UIKit // MARK: - Molecular extension TextEntryField { - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let delegateObject = delegateObject, @@ -306,7 +307,7 @@ extension TextEntryField { } if let formValidationProtocol = delegateObject.formValidationProtocol { - observingTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) as? ObservingTextFieldDelegate + observingTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) } uiTextFieldDelegate = delegateObject.uiTextFieldDelegate @@ -317,7 +318,7 @@ extension TextEntryField { // MARK: - Accessibility extension TextEntryField { - open override func pushAccessibilityNotification() { + @objc open override func pushAccessibilityNotification() { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -326,7 +327,7 @@ extension TextEntryField { } } - open override func setAccessibilityString(_ accessibilityString: String?) { + @objc open override func setAccessibilityString(_ accessibilityString: String?) { var accessibilityString = accessibilityString ?? "" diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index aa1ad8e3..5d4f99c8 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -18,7 +18,7 @@ public var direction: Direction = .right public var size: CaretSize? - + public var enabledColor: UIColor = .black public var disabledColor: UIColor = .mfSilver() @@ -48,17 +48,17 @@ case vertical case horizontal } - - // Dimensions of container; provided by InVision. + + // Dimensions of container; provided by InVision design. func dimensions() -> CGSize { - + switch self { case .small(let o): return o == .vertical ? CGSize(width: 6, height: 10) : CGSize(width: 10, height: 6) case .medium(let o): return o == .vertical ? CGSize(width: 9, height: 16) : CGSize(width: 16, height: 9) - + case .large(let o): return o == .vertical ? CGSize(width: 14, height: 24) : CGSize(width: 24, height: 14) } @@ -114,7 +114,7 @@ caretPath.removeAllPoints() caretPath.lineJoinStyle = .miter caretPath.lineWidth = lineWidth - + let inset = lineWidth / 2 let halfWidth = frame.size.width / 2 let halfHeight = frame.size.height / 2 @@ -168,7 +168,7 @@ @objc public func setConstraints() { guard let dimensions = size?.dimensions() else { return } - + heightAnchor.constraint(equalToConstant: dimensions.height).isActive = true widthAnchor.constraint(equalToConstant: dimensions.width).isActive = true } @@ -191,16 +191,16 @@ strokeColor = UIColor.mfGet(forHex: strokeColorHex) } - if let isHiddenValue = dictionary[KeyIsHidden] as? Bool { - isHidden = isHiddenValue + if let isHidden = dictionary[KeyIsHidden] as? Bool { + self.isHidden = isHidden } - if let isOpaqueValue = dictionary[KeyIsOpaque] as? Bool { - isOpaque = isOpaqueValue + if let isOpaque = dictionary[KeyIsOpaque] as? Bool { + self.isOpaque = isOpaque } - if let lineWidthValue = dictionary["lineWidth"] as? CGFloat { - lineWidth = lineWidthValue + if let lineWidth = dictionary["lineWidth"] as? CGFloat { + self.lineWidth = lineWidth } } diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift index ca813983..c8321f4d 100644 --- a/MVMCoreUI/BaseClasses/TextField.swift +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -29,7 +29,6 @@ open class TextField: UITextField { // MARK: - Delegate //-------------------------------------------------- - /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. private weak var proprietorTextDelegate: UITextFieldDelegate? diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormFieldContainer.swift similarity index 71% rename from MVMCoreUI/Containers/views/FormView.swift rename to MVMCoreUI/Containers/views/FormFieldContainer.swift index 38bef4b8..1673d2fa 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormFieldContainer.swift @@ -23,14 +23,23 @@ import UIKit return layer }() - /// Total control over bottom bar and the drawn borders. - public var disableBorders = false { + /// Total control overthe drawn top,bottom, left and right borders. + public var disableAllBorders = false { didSet { - bottomBar?.isHidden = disableBorders + bottomBar?.isHidden = disableAllBorders } } - /// Determines if a border should be drawn. + private(set) var fieldState: FieldState = .original { + didSet (oldState) { + // Will not update if new state is the same as old. + if fieldState != oldState { + fieldState.setStateUI(for: self) + } + } + } + + /// Determines if the top, left, and right borders should be drawn. private var hideBorders = false public var borderStrokeColor: UIColor = .mfSilver() @@ -40,27 +49,60 @@ import UIKit // MARK: - Property Observers //-------------------------------------------------- - public var showError = false { - didSet { - showError ? errorUI() : originalUI() + private var _isEnabled: Bool = true + private var _showError: Bool = false + private var _isLocked: Bool = false + private var _isSelected: Bool = false + + public var isEnabled: Bool { + get { return _isEnabled } + set (enabled) { + + _isEnabled = enabled + _isLocked = false + _isSelected = false + _showError = false + + fieldState = enabled ? .original : .disabled } } - public var isEnabled = true { - didSet { - isEnabled ? originalUI() : disabledUI() + public var showError: Bool { + get { return _showError } + set (error) { + + _showError = error + _isEnabled = true + _isLocked = false + _isSelected = false + + fieldState = error ? .error : .original } } - public var isLocked = false { - didSet { - isLocked ? lockedUI() : originalUI() + public var isLocked: Bool { + get { return _isLocked } + set (locked) { + + _isLocked = locked + _isEnabled = true + _isSelected = false + _showError = false + + fieldState = locked ? .locked : .original } } - public var isSelected = false { - didSet { - isSelected ? selectedUI() : originalUI() + public var isSelected: Bool { + get { return _isSelected } + set (selected) { + + _isSelected = selected + _isLocked = false + _isEnabled = true + _showError = false + + fieldState = selected ? .selected : .original } } @@ -93,7 +135,7 @@ import UIKit borderPath.removeAllPoints() - if !disableBorders && !hideBorders { + if !disableAllBorders && !hideBorders { // Brings the other half of the line inside the view to prevent cropping. let origin = bounds.origin let size = frame.size @@ -119,11 +161,22 @@ import UIKit } } + open override func reset() { + super.reset() + + isEnabled = true + _isLocked = false + _isSelected = false + _showError = false + + subviews.forEach { $0.removeFromSuperview() } + } + //-------------------------------------------------- // MARK: - Draw States //-------------------------------------------------- - public enum State { + public enum FieldState { case original case error case selected @@ -162,8 +215,8 @@ import UIKit open func errorUI() { isUserInteractionEnabled = true - borderStrokeColor = .mfPumpkin() hideBorders = false + borderStrokeColor = .mfPumpkin() bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor refreshUI(bottomBarSize: 4) } @@ -171,8 +224,8 @@ import UIKit open func selectedUI() { isUserInteractionEnabled = true - borderStrokeColor = .black hideBorders = false + borderStrokeColor = .black bottomBar?.backgroundColor = UIColor.black.cgColor refreshUI(bottomBarSize: 1) } @@ -180,8 +233,8 @@ import UIKit open func lockedUI() { isUserInteractionEnabled = false - borderStrokeColor = .clear hideBorders = true + borderStrokeColor = .clear bottomBar?.backgroundColor = UIColor.clear.cgColor refreshUI(bottomBarSize: 1) } @@ -189,15 +242,15 @@ import UIKit open func disabledUI() { isUserInteractionEnabled = false - borderStrokeColor = .mfSilver() hideBorders = false + borderStrokeColor = .mfSilver() bottomBar?.backgroundColor = UIColor.mfSilver().cgColor refreshUI(bottomBarSize: 1) } open func refreshUI(bottomBarSize: CGFloat? = nil) { - if !disableBorders { + if !disableAllBorders { let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) @@ -217,8 +270,8 @@ import UIKit guard let dictionary = json, !dictionary.isEmpty else { return } - if let disableBorders = dictionary["disableBorders"] as? Bool { - self.disableBorders = disableBorders + if let disableAllBorders = dictionary["disableAllBorders"] as? Bool { + self.disableAllBorders = disableAllBorders } } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 7dd8538f..237def93 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -38,8 +38,9 @@ @"caretButton": CaretButton.class, @"textField": TextEntryField.class, @"digitEntryField": DigitEntryField.class, + @"itemDropdownEntryField": ItemDropdownEntryField.class, + @"dateDropdownEntryField": DateDropdownEntryField.class, @"mdnEntryField" : MdnEntryField.class, - @"dropdownEntryField" : DropdownEntryField.class, @"checkbox" : Checkbox.class, @"checkboxWithLabel" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class,