Merge branch 'develop' into feature/list_progressbar_thin

This commit is contained in:
Lekshmi S 2020-05-14 12:57:08 +05:30
commit e1a5ac8faf
18 changed files with 420 additions and 410 deletions

View File

@ -68,6 +68,8 @@
0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */; };
0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */; };
0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */; };
0A25209624645AFD000FA9F6 /* TextViewEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A25209524645AFD000FA9F6 /* TextViewEntryField.swift */; };
0A25209824645B76000FA9F6 /* TextViewEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A25209724645B76000FA9F6 /* TextViewEntryFieldModel.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 */; };
0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */; };
@ -76,7 +78,6 @@
0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682A92435125F00AD3CA1 /* Styler.swift */; };
0A6682AC243531C300AD3CA1 /* Padding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682AB243531C300AD3CA1 /* Padding.swift */; };
0A6682B5243769C700AD3CA1 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682B3243769C700AD3CA1 /* TextView.swift */; };
0A6682B6243769C700AD3CA1 /* TextViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682B4243769C700AD3CA1 /* TextViewModel.swift */; };
0A69F611241BDEA700F7231B /* RuleAnyRequiredModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */; };
0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; };
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; };
@ -479,6 +480,8 @@
0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryField.swift; sourceTree = "<group>"; };
0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = "<group>"; };
0A25209524645AFD000FA9F6 /* TextViewEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewEntryField.swift; sourceTree = "<group>"; };
0A25209724645B76000FA9F6 /* TextViewEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewEntryFieldModel.swift; sourceTree = "<group>"; };
0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = "<group>"; };
0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = "<group>"; };
0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleGuidelinesProtocol.swift; sourceTree = "<group>"; };
@ -487,7 +490,6 @@
0A6682A92435125F00AD3CA1 /* Styler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Styler.swift; sourceTree = "<group>"; };
0A6682AB243531C300AD3CA1 /* Padding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Padding.swift; sourceTree = "<group>"; };
0A6682B3243769C700AD3CA1 /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
0A6682B4243769C700AD3CA1 /* TextViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewModel.swift; sourceTree = "<group>"; };
0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyRequiredModel.swift; sourceTree = "<group>"; };
0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = "<group>"; };
0A7918F423F5E7EA00772FF4 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
@ -1650,6 +1652,8 @@
0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */,
0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */,
0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */,
0A25209724645B76000FA9F6 /* TextViewEntryFieldModel.swift */,
0A25209524645AFD000FA9F6 /* TextViewEntryField.swift */,
);
path = TextFields;
sourceTree = "<group>";
@ -1760,7 +1764,6 @@
D2B18B7D236090D500A9AEDC /* BaseClasses */ = {
isa = PBXGroup;
children = (
0A6682B4243769C700AD3CA1 /* TextViewModel.swift */,
0A6682B3243769C700AD3CA1 /* TextView.swift */,
C003506023AA94CD00B6AC29 /* Button.swift */,
D2B18B7E2360913400A9AEDC /* Control.swift */,
@ -2038,6 +2041,7 @@
943820842432382400B43AF3 /* WebView.swift in Sources */,
0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */,
D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */,
0A25209624645AFD000FA9F6 /* TextViewEntryField.swift in Sources */,
014AA72623C501E2006F3E93 /* ContainerModelProtocol.swift in Sources */,
AA11A42123F15D7000D7962F /* ListRightVariablePaymentsModel.swift in Sources */,
011D9626240EBB16000E3791 /* RadioButtonLabelModel.swift in Sources */,
@ -2150,7 +2154,6 @@
012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */,
0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */,
D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */,
0A6682B6243769C700AD3CA1 /* TextViewModel.swift in Sources */,
9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */,
011D95A5240455DC000E3791 /* FormGroupRule.swift in Sources */,
D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */,
@ -2272,6 +2275,7 @@
AA26850C244840AE00CE34CC /* HeadersH2TinyButton.swift in Sources */,
011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */,
D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */,
0A25209824645B76000FA9F6 /* TextViewEntryFieldModel.swift in Sources */,
525019DD2406430800EED91C /* ListProgressBarDataModel.swift in Sources */,
C6FA7D5223C77A4A00A3614A /* UnOrderedList.swift in Sources */,
01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */,

View File

@ -69,6 +69,7 @@ import UIKit
get { return entryFieldContainer.showError }
set (error) {
self.feedback = error ? entryFieldModel?.errorMessage : entryFieldModel?.feedback
self.feedbackLabel.textColor = error ? entryFieldModel?.errorTextColor?.uiColor ?? .mvmBlack : .mvmBlack
self.entryFieldContainer.showError = error
self.entryFieldModel?.showError = error
}
@ -215,11 +216,10 @@ import UIKit
entryFieldContainer.refreshUI()
}
/**
Method to override.
Intended to add the interactive content (i.e. textField) to the entryFieldContainer.
*/
@objc open func setupFieldContainerContent(_ container: UIView) { }
/// Intended to add the interactive content (i.e. textField) to the entryFieldContainer.
@objc open func setupFieldContainerContent(_ container: UIView) {
// To Be Overriden
}
@objc open override func updateView(_ size: CGFloat) {
super.updateView(size)
@ -242,6 +242,7 @@ import UIKit
titleLabel.textColor = .mvmBlack
feedbackLabel.font = Styler.Font.RegularMicro.getFont()
feedbackLabel.textColor = .mvmBlack
entryFieldContainer.disableAllBorders = false
feedbackLabel.text = nil
entryFieldContainer.reset()
}
@ -257,6 +258,7 @@ import UIKit
title = model.title
feedback = model.feedback
isEnabled = model.enabled
entryFieldContainer.disableAllBorders = model.hideBorders
if let isLocked = model.locked {
self.isLocked = isLocked

View File

@ -22,8 +22,10 @@ import Foundation
public var title: String?
public var feedback: String?
public var errorMessage: String?
public var errorTextColor: Color?
public var enabled: Bool = true
public var showError: Bool?
public var hideBorders = false
public var locked: Bool?
public var selected: Bool?
public var text: String?
@ -37,7 +39,7 @@ import Foundation
}
/// Temporary binding mechanism for the view to update on enable changes.
public var updateUI: (() -> ())?
public var updateUI: ActionBlock?
//--------------------------------------------------
// MARK: - Keys
@ -50,9 +52,11 @@ import Foundation
case enabled
case feedback
case errorMessage
case errorTextColor
case locked
case selected
case showError
case hideBorders
case text
case fieldKey
case groupName
@ -67,7 +71,12 @@ import Foundation
}
public func setValidity(_ valid: Bool, rule: RulesProtocol) {
if let fieldKey = fieldKey,
let ruleErrorMessage = rule.errorMessage?[fieldKey] {
self.errorMessage = ruleErrorMessage
}
self.isValid = valid
}
//--------------------------------------------------
@ -89,10 +98,12 @@ import Foundation
title = try typeContainer.decodeIfPresent(String.self, forKey: .title)
feedback = try typeContainer.decodeIfPresent(String.self, forKey: .feedback)
errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage)
errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor)
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
locked = try typeContainer.decodeIfPresent(Bool.self, forKey: .locked)
selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected)
text = try typeContainer.decodeIfPresent(String.self, forKey: .text)
hideBorders = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideBorders) ?? false
baseValue = text
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
@ -111,8 +122,10 @@ import Foundation
try container.encodeIfPresent(locked, forKey: .locked)
try container.encodeIfPresent(showError, forKey: .showError)
try container.encodeIfPresent(selected, forKey: .selected)
try container.encodeIfPresent(errorTextColor, forKey: .errorTextColor)
try container.encodeIfPresent(errorMessage, forKey: .errorMessage)
try container.encode(enabled, forKey: .enabled)
try container.encode(hideBorders, forKey: .hideBorders)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
}

View File

@ -244,14 +244,21 @@ import UIKit
self.isValid = isValid
if previousValidity && !isValid {
showError = true
observingTextFieldDelegate?.isInvalid?(textfield: self)
shouldShowError(true)
} else if (!previousValidity && isValid) {
showError = false
observingTextFieldDelegate?.isValid?(textfield: self)
shouldShowError(false)
}
}
func shouldShowError(_ showError: Bool) {
self.showError = showError
if showError {
observingTextFieldDelegate?.isValid?(textfield: self)
entryFieldContainer.originalUI()
} else {
observingTextFieldDelegate?.isInvalid?(textfield: self)
}
}
/// Executes on UITextField.textDidBeginEditingNotification
@objc func startEditing() {
isSelected = true
@ -268,10 +275,16 @@ import UIKit
/// Executes on UITextField.textDidEndEditingNotification
@objc func endInputing() {
resignFirstResponder()
if isValid {
showError = false
entryFieldContainer.bottomBar?.backgroundColor = UIColor.mvmBlack.cgColor
// Don't show error till user starts typing.
guard text?.count ?? 0 != 0 else {
return
}
if let isValid = (model as? TextEntryFieldModel)?.isValid {
self.isValid = isValid
}
shouldShowError(!isValid)
}
@objc public func dismissFieldInput(_ sender: Any?) {

View File

@ -31,6 +31,7 @@
public var placeholder: String?
public var enabledTextColor: Color = Color(uiColor: .mvmBlack)
public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3)
public var textAlignment: NSTextAlignment = .left
public var type: EntryType?
//--------------------------------------------------
@ -39,6 +40,7 @@
private enum CodingKeys: String, CodingKey {
case placeholder
case textAlignment
case enabledTextColor
case disabledTextColor
case type
@ -51,6 +53,7 @@
required public init(from decoder: Decoder) throws {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
placeholder = try typeContainer.decodeIfPresent(String.self, forKey: .placeholder)
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type)
@ -61,12 +64,17 @@
if let disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor) {
self.disabledTextColor = disabledTextColor
}
if let textAlignment = try typeContainer.decodeIfPresent(NSTextAlignment.self, forKey: .textAlignment) {
self.textAlignment = textAlignment
}
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(placeholder, forKey: .placeholder)
try container.encodeIfPresent(textAlignment, forKey: .textAlignment)
try container.encode(enabledTextColor, forKey: .enabledTextColor)
try container.encode(disabledTextColor, forKey: .disabledTextColor)
try container.encodeIfPresent(type, forKey: .type)

View File

@ -0,0 +1,279 @@
//
// TextViewEntryField.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 5/7/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDelegate {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
open private(set) var textView: TextView = {
let textView = TextView()
textView.setContentCompressionResistancePriority(.required, for: .vertical)
return textView
}()
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
/// Validate on each entry in the textView. Default: true
public var validateEachCharacter: Bool = true
private var observingForChange: Bool = false
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
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
if self.textView.isShowingPlaceholder {
self.textView.textColor = self.textView.placeholderTextColor
} else {
self.textView.textColor = (enabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.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
}
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 textViewEntryFieldModel?.placeholder }
set {
textView.placeholder = newValue ?? ""
textViewEntryFieldModel?.placeholder = newValue
textView.setPlaceholderIfAvailable()
}
}
//--------------------------------------------------
// MARK: - Constraint
//--------------------------------------------------
public var heightConstraint: NSLayoutConstraint?
private var topConstraint: NSLayoutConstraint?
private var leadingConstraint: NSLayoutConstraint?
private var trailingConstraint: NSLayoutConstraint?
private var bottomConstraint: NSLayoutConstraint?
private func adjustMarginConstraints(constant: CGFloat) {
topConstraint?.constant = constant
leadingConstraint?.constant = constant
trailingConstraint?.constant = constant
bottomConstraint?.constant = constant
}
//--------------------------------------------------
// MARK: - Delegate Properties
//--------------------------------------------------
/// The delegate and block for validation. Validates if the text that the user has entered.
public weak var observingTextViewDelegate: ObservingTextFieldDelegate? {
didSet {
if observingTextViewDelegate != 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 observingTextViewDelegate == 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 }
}
@objc public func setBothTextDelegates(to delegate: (UITextViewDelegate & ObservingTextFieldDelegate)?) {
observingTextViewDelegate = delegate
uiTextViewDelegate = delegate
}
open func setupTextViewToolbar() {
let observingDelegate = observingTextViewDelegate ?? self
textView.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate,
action: #selector(observingDelegate.dismissFieldInput))
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
@objc open override func setupFieldContainerContent(_ container: UIView) {
container.addSubview(textView)
topConstraint = textView.topAnchor.constraint(equalTo: container.topAnchor, constant: Padding.Three)
leadingConstraint = textView.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: Padding.Three)
trailingConstraint = container.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: Padding.Three)
bottomConstraint = container.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: Padding.Three)
topConstraint?.isActive = true
leadingConstraint?.isActive = true
trailingConstraint?.isActive = true
bottomConstraint?.isActive = true
heightConstraint = textView.heightAnchor.constraint(equalToConstant: 0)
accessibilityElements = [titleLabel, textView, feedbackLabel]
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
textView.updateView(size)
}
open override func reset() {
super.reset()
textView.reset()
adjustMarginConstraints(constant: Padding.Three)
heightConstraint?.constant = 0
heightConstraint?.isActive = false
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Validates the text of the entry field.
@objc public func validateTextView() {
text = textView.text
if let isValid = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) {
self.isValid = isValid
}
}
/// Executes on UITextView.textDidBeginEditingNotification
@objc func startEditing() {
isSelected = true
_ = textView.becomeFirstResponder()
}
/// Executes on UITextView.textDidChangeNotification (each character entry)
@objc func valueChanged() {
guard validateEachCharacter else { return }
validateTextView()
}
/// Executes on UITextView.textDidEndEditingNotification
@objc func endInputing() {
resignFirstResponder()
isSelected = false
showError = !isValid
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? TextViewEntryFieldModel else { return }
if let height = model.height {
heightConstraint?.constant = height
heightConstraint?.isActive = true
}
text = model.text
uiTextViewDelegate = delegateObject?.uiTextViewDelegate
observingTextViewDelegate = delegateObject?.observingTextFieldDelegate
if let accessibilityText = model.accessibilityText {
accessibilityLabel = accessibilityText
}
textView.isEditable = model.editable
textView.textAlignment = model.textAlignment
textView.textColor = model.enabled ? model.enabledTextColor.uiColor : model.disabledTextColor.uiColor
textView.font = model.fontStyle.getFont()
textView.placeholder = model.placeholder ?? ""
textView.placeholderFontStyle = model.placeholderFontStyle
textView.placeholderTextColor = model.placeholderTextColor.uiColor
textView.setPlaceholderIfAvailable()
switch model.type {
case .secure, .password:
textView.isSecureTextEntry = true
case .number:
textView.keyboardType = .numberPad
case .email:
textView.keyboardType = .emailAddress
default: break
}
/// No point in configuring if the TextView is Read-only.
if textView.isEditable {
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
setupTextViewToolbar()
if isSelected {
DispatchQueue.main.async {
_ = self.textView.becomeFirstResponder()
}
}
}
if model.hideBorders {
adjustMarginConstraints(constant: 0)
}
if !model.enabled {
isEnabled = false
}
}
}

View File

@ -1,15 +1,15 @@
//
// TextViewModel.swift
// TextViewEntryFieldModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 4/2/20.
// Created by Kevin Christiano on 5/7/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
import UIKit
open class TextViewModel: TextEntryFieldModel {
class TextViewEntryFieldModel: TextEntryFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -19,26 +19,21 @@ open class TextViewModel: TextEntryFieldModel {
}
public var accessibilityText: String?
public var fontStyle: Styler.Font = Styler.Font.RegularBodySmall
public var textAlignment: NSTextAlignment = .left
public var fontStyle: Styler.Font = Styler.Font.RegularBodyLarge
public var height: CGFloat?
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
public var showsPlaceholder: Bool = false
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case text
case accessibilityText
case fontStyle
case textAlignment
case height
case hideBorders
case placeholderFontStyle
case placeholderTextColor
case editable
@ -60,22 +55,14 @@ open class TextViewModel: TextEntryFieldModel {
self.placeholderTextColor = placeholderTextColor
}
if let textAlignment = try typeContainer.decodeIfPresent(NSTextAlignment.self, forKey: .textAlignment) {
self.textAlignment = textAlignment
}
if let hideBorders = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideBorders) {
self.hideBorders = hideBorders
if let fontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .fontStyle) {
self.fontStyle = fontStyle
}
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
}
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height)
}
@ -86,11 +73,8 @@ open class TextViewModel: 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(editable, forKey: .editable)
try container.encode(placeholderFontStyle, forKey: .placeholderFontStyle)
try container.encode(placeholderTextColor, forKey: .placeholderTextColor)
try container.encode(textAlignment, forKey: .textAlignment)
try container.encode(editable, forKey: .editable)
}
}

View File

@ -32,16 +32,16 @@ import UIKit
switch style {
case .standard:
updateLineConstraints(constant: 1)
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mfSilver()
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmCoolGray3
case .thin:
updateLineConstraints(constant: 1)
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .black
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack
case .medium:
updateLineConstraints(constant: 2)
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .black
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack
case .heavy:
updateLineConstraints(constant: 4)
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .black
backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack
case .none:
updateLineConstraints(constant: 0)
}
@ -97,6 +97,7 @@ import UIKit
}
extension Line: MVMCoreUIViewConstrainingProtocol {
open func needsToBeConstrained() -> Bool {
return true
}

View File

@ -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)

View File

@ -9,7 +9,7 @@
import UIKit
@objc open class TextView: UITextView, UITextViewDelegate, MVMCoreViewProtocol {
@objc open class TextView: UITextView, MVMCoreViewProtocol, MoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -19,87 +19,18 @@ import UIKit
private var initialSetupPerformed = false
/// If true then text textView is currently displaying the stored placeholder text as there is not content to display.
public var isShowingPlaceholder: Bool = false {
didSet { textViewModel?.showsPlaceholder = isShowingPlaceholder }
}
public var isShowingPlaceholder: Bool = false
/// Set to true to hide the blinking textField cursor.
public var hideBlinkingCaret = false
public var textViewModel: TextViewModel? {
return model as? TextViewModel
}
public var placeholder = ""
public var fontStyle: Styler.Font = Styler.Font.RegularBodyLarge
public var placeholderFontStyle: Styler.Font = Styler.Font.RegularMicro
public var placeholderTextColor: UIColor = .mvmCoolGray3
//--------------------------------------------------
// MARK: - Drawing Properties
//--------------------------------------------------
private(set) var fieldState: FieldState = .original {
didSet (oldState) {
// Will not update if new state is the same as old.
if fieldState != oldState {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.fieldState.setStateUI(for: self)
}
}
}
}
/// Determines if the top, left, and right borders should be drawn.
private var hideBorders = false
public var borderStrokeColor: UIColor = .mvmCoolGray3
public var bottomStrokeColor: UIColor = .mvmBlack
private var borderPath: UIBezierPath = UIBezierPath()
private var bottomPath: UIBezierPath = UIBezierPath()
//--------------------------------------------------
// MARK: - Property Observers
//--------------------------------------------------
private var _isEnabled: Bool = true
private var _showError: Bool = false
private var _isSelected: Bool = false
public var isEnabled: Bool {
get { return _isEnabled }
set (enabled) {
_isEnabled = enabled
_isSelected = false
_showError = false
fieldState = enabled ? .original : .disabled
}
}
public var showError: Bool {
get { return _showError }
set (error) {
_showError = error
_isEnabled = true
_isSelected = false
fieldState = error ? .error : .original
}
}
public var isSelected: Bool {
get { return _isSelected }
set (selected) {
_isSelected = selected
_isEnabled = true
if _showError {
fieldState = selected ? .selectedError : .error
} else {
fieldState = selected ? .selected : .original
}
}
public var isEnabled: Bool = true {
didSet { isUserInteractionEnabled = isEnabled }
}
//--------------------------------------------------
@ -109,26 +40,6 @@ import UIKit
/// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well.
public weak var didDeleteDelegate: TextInputDidDeleteProtocol?
/// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well.
private weak var proprietorTextDelegate: UITextViewDelegate?
/// If you're using a ViewController, you must set this to it.
public weak var uiTextViewDelegate: UITextViewDelegate? {
get { return delegate }
set {
delegate = self
proprietorTextDelegate = newValue
}
}
var delegateObject: MVMCoreUIDelegateObject?
//--------------------------------------------------
// MARK: - Constraint
//--------------------------------------------------
public var heightConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Initialization
//--------------------------------------------------
@ -147,11 +58,6 @@ import UIKit
initialSetup()
}
convenience init(delegate: UITextViewDelegate) {
self.init(frame: .zero, textContainer: nil)
self.delegate = delegate
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
@ -166,172 +72,53 @@ import UIKit
}
open func updateView(_ size: CGFloat) {
setNeedsDisplay()
font = (isShowingPlaceholder ? placeholderFontStyle : fontStyle).getFont()
}
/// Will be called only once.
open func setupView() {
translatesAutoresizingMaskIntoConstraints = false
initialConfiguration()
defaultConfiguration()
}
public func initialConfiguration() {
public func defaultConfiguration() {
text = ""
placeholder = ""
textAlignment = .left
insetsLayoutMarginsFromSafeArea = false
showsVerticalScrollIndicator = false
showsHorizontalScrollIndicator = false
isSecureTextEntry = false
textContainerInset = UIEdgeInsets(top: Padding.Three, left: Padding.Three, bottom: Padding.Three, right: Padding.Three)
backgroundColor = .mvmWhite
clipsToBounds = true
smartQuotesType = .no
smartDashesType = .no
smartInsertDeleteType = .no
inputAccessoryView = nil
font = textViewModel?.fontStyle.getFont()
isAccessibilityElement = true
accessibilityTraits = .staticText
font = fontStyle.getFont()
keyboardType = .default
isEditable = true
isOpaque = false
}
open func reset() {
fontStyle = Styler.Font.RegularBodyLarge
placeholderFontStyle = Styler.Font.RegularMicro
placeholderTextColor = .mvmCoolGray3
textColor = .mvmBlack
isEnabled = true
text = ""
isShowingPlaceholder = false
inputAccessoryView?.removeFromSuperview()
inputAccessoryView = nil
initialConfiguration()
}
open override func layoutSubviews() {
super.layoutSubviews()
setNeedsDisplay()
defaultConfiguration()
}
//--------------------------------------------------
// MARK: - Draw
//--------------------------------------------------
/// This handles the top, left, and right border lines.
open override func draw(_ rect: CGRect) {
super.draw(rect)
borderPath.removeAllPoints()
bottomPath.removeAllPoints()
if !hideBorders {
// Brings the other half of the line inside the view to prevent line cropping.
let origin = bounds.origin
let size = frame.size
let insetLean: CGFloat = 0.5
borderPath.lineWidth = 1
// Drawing begins and ends from the bottom left.
borderPath.move(to: CGPoint(x: origin.x + insetLean, y: origin.y + size.height))
borderPath.addLine(to: CGPoint(x: origin.x + insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + size.height))
borderStrokeColor.setStroke()
borderPath.stroke()
let lineWidth: CGFloat = showError || isSelected ? 4 : 1
bottomPath.lineWidth = lineWidth
bottomPath.move(to: CGPoint(x: origin.x + size.width, y: origin.y + size.height - (lineWidth / 2)))
bottomPath.addLine(to: CGPoint(x: origin.x, y: origin.y + size.height - (lineWidth / 2)))
bottomStrokeColor.setStroke()
bottomPath.stroke()
}
}
//--------------------------------------------------
// MARK: - Draw States
//--------------------------------------------------
public enum FieldState {
case original
case error
case selectedError
case selected
case disabled
public func setStateUI(for inputField: TextView) {
switch self {
case .original:
inputField.originalUI()
case .error:
inputField.errorUI()
case .selectedError:
inputField.selectedErrorUI()
case .selected:
inputField.selectedUI()
case .disabled:
inputField.disabledUI()
}
inputField.setNeedsDisplay()
}
}
open func originalUI() {
isEditable = textViewModel?.editable ?? true
isUserInteractionEnabled = true
hideBorders = textViewModel?.hideBorders ?? false
borderStrokeColor = .mvmCoolGray3
bottomStrokeColor = .mvmBlack
textColor = isShowingPlaceholder ? textViewModel?.placeholderTextColor.uiColor : textViewModel?.enabledTextColor.uiColor
}
open func errorUI() {
isEditable = textViewModel?.editable ?? true
isUserInteractionEnabled = true
hideBorders = textViewModel?.hideBorders ?? false
borderStrokeColor = .mvmOrange
bottomStrokeColor = .mvmOrange
textColor = textViewModel?.enabledTextColor.uiColor
}
open func selectedErrorUI() {
isEditable = textViewModel?.editable ?? true
isUserInteractionEnabled = true
hideBorders = textViewModel?.hideBorders ?? false
borderStrokeColor = .mvmBlack
bottomStrokeColor = .mvmOrange
textColor = textViewModel?.enabledTextColor.uiColor
}
open func selectedUI() {
isEditable = textViewModel?.editable ?? true
isUserInteractionEnabled = true
hideBorders = textViewModel?.hideBorders ?? false
borderStrokeColor = .mvmBlack
bottomStrokeColor = .mvmBlack
textColor = textViewModel?.enabledTextColor.uiColor
}
open func disabledUI() {
isEditable = textViewModel?.editable ?? false
isUserInteractionEnabled = false
hideBorders = textViewModel?.hideBorders ?? false
borderStrokeColor = .mvmCoolGray3
bottomStrokeColor = .mvmCoolGray3
textColor = textViewModel?.disabledTextColor.uiColor
}
//--------------------------------------------------
// MARK: - Methods
// MARK: - TextInputDidDeleteProtocol
//--------------------------------------------------
/// Alters the blinking caret line as per design standards.
@ -350,141 +137,49 @@ import UIKit
didDeleteDelegate?.textInputDidDelete()
}
public func setTextAppearance() {
//--------------------------------------------------
// MARK: - Text / Placeholder
//--------------------------------------------------
open override func becomeFirstResponder() -> Bool {
if isShowingPlaceholder {
setTextContentTraits()
}
return super.becomeFirstResponder()
}
open override func resignFirstResponder() -> Bool {
setPlaceholderIfAvailable()
return super.resignFirstResponder()
}
public func setPlaceholderIfAvailable() {
if let placeholder = textViewModel?.placeholder, !placeholder.isEmpty && text.isEmpty {
if !placeholder.isEmpty && text.isEmpty {
setPlaceholderContentTraits()
}
}
public func setTextContentTraits() {
open func setTextContentTraits() {
isShowingPlaceholder = false
text = ""
font = textViewModel?.fontStyle.getFont()
textColor = textViewModel?.enabledTextColor.uiColor
font = fontStyle.getFont()
textColor = .mvmBlack
}
public func setPlaceholderContentTraits() {
open func setPlaceholderContentTraits() {
isShowingPlaceholder = true
textColor = textViewModel?.placeholderTextColor.uiColor
font = textViewModel?.placeholderFontStyle.getFont()
text = textViewModel?.placeholder
textColor = placeholderTextColor
font = placeholderFontStyle.getFont()
text = placeholder
}
@objc func dismissFieldInput(_ sender: TextView) {
@objc open func dismissFieldInput(_ sender: TextView) {
resignFirstResponder()
}
//--------------------------------------------------
// MARK: - UITextViewDelegate
//--------------------------------------------------
@objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
return proprietorTextDelegate?.textViewShouldBeginEditing?(textView) ?? true
}
@objc public func textViewDidBeginEditing(_ textView: UITextView) {
setTextAppearance()
isSelected = true
proprietorTextDelegate?.textViewDidBeginEditing?(textView)
}
@objc public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return proprietorTextDelegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) ?? true
}
@objc public func textViewDidChange(_ textView: UITextView) {
textViewModel?.text = textView.text
proprietorTextDelegate?.textViewDidChange?(textView)
}
@objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
return proprietorTextDelegate?.textViewShouldEndEditing?(textView) ?? true
}
@objc public func textViewDidEndEditing(_ textView: UITextView) {
setPlaceholderIfAvailable()
isSelected = false
proprietorTextDelegate?.textViewDidEndEditing?(textView)
}
}
// MARK:- MoleculeViewProtocol
extension TextView: MoleculeViewProtocol {
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.model = model
self.delegateObject = delegateObject
if let color = model.backgroundColor?.uiColor {
backgroundColor = color
}
guard let model = model as? TextViewModel else { return }
heightConstraint?.isActive = false
if let height = model.height {
heightConstraint = heightAnchor.constraint(equalToConstant: height)
heightConstraint?.isActive = true
}
isEditable = model.editable
textAlignment = model.textAlignment
textColor = model.enabledTextColor.uiColor
hideBorders = model.hideBorders
text = model.text
uiTextViewDelegate = delegateObject?.uiTextViewDelegate
if let accessibilityText = model.accessibilityText {
accessibilityLabel = accessibilityText
}
switch model.type {
case .secure, .password:
isSecureTextEntry = true
case .number:
keyboardType = .numberPad
case .email:
keyboardType = .emailAddress
default:
break
}
font = model.fontStyle.getFont()
setPlaceholderIfAvailable()
if isEditable {
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
let observingDelegate = delegateObject?.uiTextViewDelegate ?? self
inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate,
action: #selector(dismissFieldInput))
if (model.selected ?? false) && !model.wasInitiallySelected {
model.wasInitiallySelected = true
DispatchQueue.main.async {
self.becomeFirstResponder()
}
}
}
if !model.enabled {
isEnabled = false
}
_ = resignFirstResponder()
}
}

View File

@ -58,7 +58,7 @@ import UIKit
if navigationController == MVMCoreUISession.sharedGlobal()?.navigationController,
navigationController.topViewController == viewController {
// Update line.
MVMCoreUISession.sharedGlobal()?.navigationController?.separatorView?.setStyle(navigationItemModel.line?.type ?? .standard)
MVMCoreUISession.sharedGlobal()?.navigationController?.separatorView?.isHidden = navigationItemModel.line?.type ?? .standard == .none
}
if navigationController == MVMCoreUISplitViewController.main()?.navigationController,

View File

@ -15,6 +15,7 @@ public class RuleAllValueChangedModel: RulesProtocol {
public static var identifier: String = "allValueChanged"
public var type: String = RuleAllValueChangedModel.identifier
public var errorMessage: [String: String]?
public var fields: [String]
//--------------------------------------------------

View File

@ -17,6 +17,7 @@ public class RuleAnyRequiredModel: RulesProtocol {
public static var identifier: String = "anyRequired"
public var type: String = RuleRequiredModel.identifier
public var fields: [String]
public var errorMessage: [String: String]?
//--------------------------------------------------
// MARK: - Methods

View File

@ -16,6 +16,7 @@ public class RuleAnyValueChangedModel: RulesProtocol {
public static var identifier: String = "anyValueChanged"
public var type: String = RuleAnyValueChangedModel.identifier
public var errorMessage: [String: String]?
public var fields: [String]
//--------------------------------------------------

View File

@ -17,6 +17,7 @@ public class RuleEqualsModel: RulesProtocol {
public static var identifier: String = "equals"
public var type: String = RuleEqualsModel.identifier
public var fields: [String]
public var errorMessage: [String: String]?
//--------------------------------------------------
// MARK: - Validation
@ -27,9 +28,9 @@ public class RuleEqualsModel: RulesProtocol {
}
public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool {
var valid = true
var compareValue: AnyHashable?
var valid = true
var compareValue: AnyHashable?
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
@ -37,13 +38,16 @@ public class RuleEqualsModel: RulesProtocol {
compareValue = formField.formFieldValue()
continue
}
if compareValue != formField.formFieldValue() {
valid = false
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
break
} else {
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
}
}
return valid
return valid
}
}

View File

@ -18,6 +18,7 @@ public class RuleRegexModel: RulesProtocol {
public var type: String = RuleRegexModel.identifier
public var fields: [String]
public var regex: String
public var errorMessage: [String: String]?
//--------------------------------------------------
// MARK: - Properties

View File

@ -16,6 +16,7 @@ public class RuleRequiredModel: RulesProtocol {
public static var identifier: String = "allRequired"
public var type: String = RuleRequiredModel.identifier
public var errorMessage: [String: String]?
public var fields: [String]
//--------------------------------------------------

View File

@ -16,7 +16,9 @@ public enum RulesCodingKey: String, CodingKey {
public protocol RulesProtocol: ModelProtocol {
// The type of rule
var type: String { get }
var errorMessage: [String: String]? { get }
// The fields that this rule applies to.
var fields: [String] { get set }