From efe3f95746001e2aad38237914cc47fa74ca29da Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 7 May 2020 14:41:50 -0400 Subject: [PATCH] further textviewfield development --- .../Atoms/TextFields/TextViewEntryField.swift | 157 ++++++++++++++++-- .../TextFields/TextViewEntryFieldModel.swift | 20 +-- MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 2 +- MVMCoreUI/BaseClasses/TextView.swift | 27 ++- MVMCoreUI/BaseClasses/TextViewModel.swift | 85 +++++++++- 5 files changed, 248 insertions(+), 43 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index b880ed5e..7b1a30bc 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -9,7 +9,7 @@ import UIKit -class TextViewEntryField: EntryField { +class TextViewEntryField: EntryField, UITextViewDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -26,12 +26,137 @@ class TextViewEntryField: EntryField { return textView }() + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + /// Validate on each entry in the textField. Default: true + public var validateEachCharacter: Bool = true + + + public var textViewEntryFieldModel: TextViewEntryFieldModel? { + return model as? TextViewEntryFieldModel + } + + public override var isEnabled: Bool { + get { return super.isEnabled } + set (enabled) { + super.isEnabled = enabled + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.textView.isEnabled = enabled + self.textView.textColor = enabled ? self.textEntryFieldModel?.enabledTextColor.uiColor : self.textEntryFieldModel?.disabledTextColor.uiColor + } + } + } + + public override var showError: Bool { + get { return super.showError } + set (error) { + + if error { + textView.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textView_error_message") ?? "", textView.text ?? "", entryFieldModel?.errorMessage ?? "") + } else { + textView.accessibilityValue = nil + } + + if textView.isSecureTextEntry { +// showErrorView(error) + } + + super.showError = error + } + } + + /// The text of this textView. + open override var text: String? { + get { return textView.text } + set { + textView.text = newValue + textViewEntryFieldModel?.text = newValue + } + } + + /// Placeholder access for the textView. + public var placeholder: String? { + get { +// return textView.placeholder + return textViewEntryFieldModel?.placeholder + } + set { +// textView.placeholder = newValue + textViewEntryFieldModel?.placeholder = newValue + } + } + + + /// Executes on UITextField.textDidBeginEditingNotification + @objc func startEditing() { + isSelected = true + textView.becomeFirstResponder() + } + + /// Executes on UITextField.textDidChangeNotification (each character entry) + @objc func valueChanged() { + guard validateEachCharacter else { return } + isSelected = true + validateTextField() + } + + /// Validates the text of the entry field. + @objc public func validateTextField() { + text = textField.text + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + } + + /// Executes on UITextField.textDidEndEditingNotification + @objc func endInputing() { + resignFirstResponder() + if isValid { + showError = false + entryFieldContainer.bottomBar?.backgroundColor = UIColor.mvmBlack.cgColor + } + } + + @objc public func dismissFieldInput(_ sender: Any?) { + resignFirstResponder() + } + //-------------------------------------------------- // MARK: - Constraint //-------------------------------------------------- public var heightConstraint: NSLayoutConstraint? - + + //-------------------------------------------------- + // MARK: - Delegate Properties + //-------------------------------------------------- + + /// The delegate and block for validation. Validates if the text that the user has entered. + public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? { + didSet { + if observingTextFieldDelegate != nil && !observingForChange { + observingForChange = true + NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextView.textDidChangeNotification, object: textView) + NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextView.textDidEndEditingNotification, object: textView) + NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextView.textDidBeginEditingNotification, object: textView) + + } else if observingTextFieldDelegate == nil && observingForChange { + observingForChange = false + NotificationCenter.default.removeObserver(self, name: UITextView.textDidChangeNotification, object: textView) + NotificationCenter.default.removeObserver(self, name: UITextView.textDidEndEditingNotification, object: textView) + NotificationCenter.default.removeObserver(self, name: UITextView.textDidBeginEditingNotification, object: textView) + } + } + } + + /// If you're using a ViewController, you must set this to it + public weak var uiTextViewDelegate: UITextViewDelegate? { + get { return textView.delegate } + set { textView.delegate = newValue } + } //-------------------------------------------------- // MARK: - Lifecycle @@ -50,13 +175,13 @@ class TextViewEntryField: EntryField { ]) heightConstraint = textView.heightAnchor.constraint(equalToConstant: 0) + heightConstraint?.isActive = true - -// textView.addTarget(self, action: #selector(startEditing), for: .editingDidBegin) -// textView.addTarget(self, action: #selector(dismissFieldInput), for: .editingDidEnd) -// -// let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) -// entryFieldContainer.addGestureRecognizer(tap) + // textView.addTarget(self, action: #selector(startEditing), for: .editingDidBegin) + // textView.addTarget(self, action: #selector(dismissFieldInput), for: .editingDidEnd) + // + // let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + // entryFieldContainer.addGestureRecognizer(tap) accessibilityElements = [titleLabel, textView, feedbackLabel] } @@ -72,6 +197,7 @@ class TextViewEntryField: EntryField { super.reset() textView.font = Styler.Font.RegularBodyLarge.getFont() + heightConstraint?.constant = 0 } @@ -84,18 +210,13 @@ class TextViewEntryField: EntryField { guard let model = model as? TextViewEntryFieldModel else { return } - heightConstraint?.isActive = false - if let height = model.height { - heightConstraint?.constant = height - heightConstraint?.isActive = true - } + heightConstraint?.constant = model.height ?? 0 - textView.isEditable = model.editable +// textView.isEditable = model.editable textView.textAlignment = model.textAlignment textView.textColor = model.enabledTextColor.uiColor -// textView.hideBorders = model.hideBorders text = model.text - textView.uiTextViewDelegate = delegateObject?.uiTextViewDelegate + uiTextViewDelegate = delegateObject?.uiTextViewDelegate if let accessibilityText = model.accessibilityText { accessibilityLabel = accessibilityText @@ -115,10 +236,10 @@ class TextViewEntryField: EntryField { textView.font = model.fontStyle.getFont() textView.setPlaceholderIfAvailable() - if isEditable { + if textView.isEditable { FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) let observingDelegate = delegateObject?.uiTextViewDelegate ?? self - inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate, + textView.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate, action: #selector(textView.dismissFieldInput)) if (model.selected ?? false) && !model.wasInitiallySelected { diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryFieldModel.swift index e518fafc..bd0188f6 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryFieldModel.swift @@ -8,6 +8,7 @@ import UIKit + class TextViewEntryFieldModel: TextEntryFieldModel { //-------------------------------------------------- // MARK: - Properties @@ -19,14 +20,11 @@ class TextViewEntryFieldModel: TextEntryFieldModel { public var accessibilityText: String? public var fontStyle: Styler.Font = Styler.Font.RegularBodySmall - public var textAlignment: NSTextAlignment = .left public var height: CGFloat? + public var textAlignment: NSTextAlignment = .left public var placeholderTextColor: Color = Color(uiColor: .mvmCoolGray3) public var placeholderFontStyle: Styler.Font = Styler.Font.RegularMicro - public var showsPlaceholder: Bool = false - public var hideBorders: Bool = false - public var editable: Bool = true - + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -37,7 +35,6 @@ class TextViewEntryFieldModel: TextEntryFieldModel { case fontStyle case textAlignment case height - case hideBorders case placeholderFontStyle case placeholderTextColor case editable @@ -63,14 +60,6 @@ class TextViewEntryFieldModel: TextEntryFieldModel { self.textAlignment = textAlignment } - if let hideBorders = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideBorders) { - self.hideBorders = hideBorders - } - - if let editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) { - self.editable = editable - } - if let fontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .fontStyle) { self.fontStyle = fontStyle } @@ -85,11 +74,10 @@ class TextViewEntryFieldModel: TextEntryFieldModel { try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(height, forKey: .height) try container.encode(fontStyle, forKey: .fontStyle) - try container.encode(hideBorders, forKey: .hideBorders) try container.encode(text, forKey: .text) try container.encode(placeholderFontStyle, forKey: .placeholderFontStyle) try container.encode(placeholderTextColor, forKey: .placeholderTextColor) try container.encode(textAlignment, forKey: .textAlignment) - try container.encode(editable, forKey: .editable) + } } diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index a9e86d50..af85da8e 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -52,7 +52,7 @@ import Foundation try? ModelRegistry.register(LabelAttributeActionModel.self) // TextView - MoleculeObjectMapping.shared()?.register(viewClass: TextView.self, viewModelClass: TextViewModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: TextViewEntryField.self, viewModelClass: TextViewEntryFieldModel.self) // Buttons MoleculeObjectMapping.shared()?.register(viewClass: PillButton.self, viewModelClass: ButtonModel.self) diff --git a/MVMCoreUI/BaseClasses/TextView.swift b/MVMCoreUI/BaseClasses/TextView.swift index 0d381777..33f51bac 100644 --- a/MVMCoreUI/BaseClasses/TextView.swift +++ b/MVMCoreUI/BaseClasses/TextView.swift @@ -9,7 +9,7 @@ import UIKit -@objc open class TextView: UITextView, UITextViewDelegate, MVMCoreViewProtocol { +@objc open class TextView: UITextView, UITextViewDelegate, MVMCoreViewProtocol, MoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -26,9 +26,21 @@ import UIKit /// Set to true to hide the blinking textField cursor. public var hideBlinkingCaret = false - public var textViewEntryFieldModel: TextViewEntryFieldModel? { - return model as? TextViewEntryFieldModel + public var textViewModel: TextViewModel? { + return model as? TextViewModel } + + open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + + if let color = model.backgroundColor?.uiColor { + backgroundColor = color + } + } + + public var isEnabled: Bool = true { + didSet { } + } + /* //-------------------------------------------------- // MARK: - Drawing Properties @@ -202,6 +214,7 @@ import UIKit inputAccessoryView = nil initialConfiguration() } + /* open override func layoutSubviews() { super.layoutSubviews() @@ -368,8 +381,8 @@ import UIKit isShowingPlaceholder = false text = "" - font = textViewModel?.fontStyle.getFont() - textColor = textViewModel?.enabledTextColor.uiColor + font = Styler.Font.RegularBodySmall.getFont()//textViewModel?.fontStyle.getFont() + textColor = .black//textViewModel?.enabledTextColor.uiColor } public func setPlaceholderContentTraits() { @@ -397,7 +410,7 @@ import UIKit @objc public func textViewDidBeginEditing(_ textView: UITextView) { setTextAppearance() - isSelected = true +// isSelected = true proprietorTextDelegate?.textViewDidBeginEditing?(textView) } @@ -420,7 +433,7 @@ import UIKit @objc public func textViewDidEndEditing(_ textView: UITextView) { setPlaceholderIfAvailable() - isSelected = false +// isSelected = false proprietorTextDelegate?.textViewDidEndEditing?(textView) } } diff --git a/MVMCoreUI/BaseClasses/TextViewModel.swift b/MVMCoreUI/BaseClasses/TextViewModel.swift index 858c4a6f..6d68de1c 100644 --- a/MVMCoreUI/BaseClasses/TextViewModel.swift +++ b/MVMCoreUI/BaseClasses/TextViewModel.swift @@ -6,4 +6,87 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation +import UIKit + + +open class TextViewModel: MoleculeModelProtocol {//, FormFieldProtocol, FormRuleWatcherFieldProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public class var identifier: String { + return "" + } + + public var baseValue: AnyHashable? + public var backgroundColor: Color? +// public var fieldKey: String? + public var fontStyle: Styler.Font = Styler.Font.RegularBodySmall + public var text: String? + public var placeholder: String? + public var textAlignment: NSTextAlignment = .left +// public var groupName: String + public var editable: Bool = true + public var showsPlaceholder: Bool = false + public var placeholderTextColor: Color = Color(uiColor: .mvmCoolGray3) + public var placeholderFontStyle: Styler.Font = Styler.Font.RegularMicro + + public var isValid: Bool? { + didSet { updateUI?() } + } + + /// Temporary binding mechanism for the view to update on enable changes. + public var updateUI: ActionBlock? + + //-------------------------------------------------- + // MARK: - Validation Methods + //-------------------------------------------------- + + public func formFieldValue() -> AnyHashable? { + return text + } + + public func setValidity(_ valid: Bool, rule: RulesProtocol) { + self.isValid = valid + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case editable +// case groupName + case textAlignment + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + if let editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) { + self.editable = editable + } + +// if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { +// self.groupName = groupName +// } + + if let textAlignment = try typeContainer.decodeIfPresent(NSTextAlignment.self, forKey: .textAlignment) { + self.textAlignment = textAlignment + } + + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) +// try container.encode(groupName, forKey: .groupName) + try container.encode(editable, forKey: .editable) + try container.encode(textAlignment, forKey: .textAlignment) + } +}