Latest state.

This commit is contained in:
Kevin G Christiano 2019-10-23 15:15:23 -04:00
parent 74abef0c30
commit a5ffae9edd
7 changed files with 130 additions and 1087 deletions

View File

@ -213,7 +213,6 @@
01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = "<group>"; };
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; };
0A21DB7E235DECC500C160A2 /* FormEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormEntryField.swift; sourceTree = "<group>"; };
0A21DB80235DF87300C160A2 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.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>"; };
0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = "<group>"; };
@ -769,7 +768,6 @@
0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */,
0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */,
0A21DB7E235DECC500C160A2 /* FormEntryField.swift */,
0A21DB80235DF87300C160A2 /* TextField.swift */,
0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */,
0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */,
0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */,

View File

@ -9,7 +9,7 @@
import UIKit
class DigitEntryField: TextEntryField, UITextFieldDelegate, DigitTextBoxDelegate {
class DigitEntryField: TextEntryField, DigitTextBoxDelegate {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
@ -25,7 +25,7 @@ class DigitEntryField: TextEntryField, UITextFieldDelegate, DigitTextBoxDelegate
public var digitFields: [DigitTextBox]?
/// Setgs placeholder text in the textField.
public override var placeholder: String? {
public override var feedback: String? {
get {
var string = ""

View File

@ -358,7 +358,7 @@ import UIKit
}
// MARK: - Date Picker
extension TextEntryField {
extension DropdownEntryField {
private func createDatePicker() {
@ -426,7 +426,7 @@ extension TextEntryField {
}
// MARK: - Molecular
extension TextEntryField {
extension DropdownEntryField {
override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
@ -453,7 +453,7 @@ extension TextEntryField {
}
// MARK: - Accessibility
extension TextEntryField {
extension DropdownEntryField {
open override func pushAccessibilityNotification() {

View File

@ -25,14 +25,6 @@ import UIKit
public var separatorView: UIView?
public var dashLine: DashLine?
//--------------------------------------------------
// MARK: - Accessories
//--------------------------------------------------
public weak var datePicker: UIDatePicker?
public var pickerView: UIPickerView?
private(set) weak var toolbar: UIToolbar?
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -42,7 +34,12 @@ import UIKit
private var borderPath: UIBezierPath?
public var errorMessage: String?
public var errorMessage: String? {
didSet {
feedback = errorMessage
}
}
public var showErrorMessage = false
public var isEnabled = true {
@ -83,21 +80,19 @@ import UIKit
public var feedback: String? {
get { return feedbackLabel?.text }
set {
guard isEnabled,
let newFeedback = newValue
else { return }
guard isEnabled else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.feedbackLabel?.text = newFeedback
self.feedbackLabel?.text = newValue
self.separatorHeightConstraint?.constant = self.showErrorMessage ? 4 : 1
self.separatorView?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black
self.setNeedsDisplay()
self.layoutIfNeeded()
}
setAccessibilityString(newFeedback)
setAccessibilityString(newValue)
}
}

View File

@ -11,20 +11,20 @@ import ContactsUI
import UIKit
import MVMCore
class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate {
class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public weak var customDelegate: UITextFieldDelegate?
public var isNationalMdn = false
public var shouldValidateMDN = false
public var mdn: String? {
get { return MVMCoreUIUtility.removeMdnFormat(text) }
set {
text = MVMCoreUIUtility.formatMdn(newValue)
}
set { text = MVMCoreUIUtility.formatMdn(newValue) }
}
//--------------------------------------------------
@ -70,10 +70,6 @@ class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigati
set {
super.uiTextFieldDelegate = newValue
customDelegate = uiTextFieldDelegate
if newValue != nil {
textField?.delegate = self
}
}
}
@ -81,13 +77,11 @@ class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigati
// MARK: - Methods
//--------------------------------------------------
func hasValidMdn() -> Bool {
func hasValidMDN() -> Bool {
guard let MDN = mdn else { return true }
if MDN.isEmpty {
return true
}
guard let MDN = mdn,
!MDN.isEmpty
else { return true }
if isNationalMdn {
return MVMCoreUIUtility.validateMDNString(MDN)
@ -99,12 +93,12 @@ class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigati
func validateAndColor() -> Bool {
if !shouldValidateMDN {
let isValid = hasValidMdn()
let isValid = hasValidMDN()
if isValid {
hideError()
clearError()
} else {
self.errorMessage = getErrorMessage() ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message")
errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message")
UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField)
}
@ -114,18 +108,9 @@ class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigati
return true
}
func getErrorMessage() -> String? {
return nil
}
@objc func dismissFieldInput(_ sender: Any?) {
if let delegate = uiTextFieldDelegate {
delegate.perform(#selector(dismissFieldInput(_:)), with: textField)
} else {
textField?.resignFirstResponder()
}
textField?.resignFirstResponder()
}
func getContacts(_ sender: Any?) {
@ -144,10 +129,9 @@ class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigati
public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) {
if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) {
if let phoneNumber = contactProperty.value as? CNPhoneNumber {
let phoneNumber = contactProperty.value as? CNPhoneNumber
let MDN = phoneNumber?.stringValue
let MDN = phoneNumber.stringValue
var unformattedMDN = MVMCoreUIUtility.removeMdnFormat(MDN)
// Sometimes user add extra 1 in front of mdn in their address book
@ -169,7 +153,7 @@ class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigati
}
//--------------------------------------------------
// MARK: - ImplementedTextField Delegate
// MARK: - Implemented TextField Delegate
//--------------------------------------------------
@discardableResult

View File

@ -19,7 +19,7 @@ import UIKit
}
@objcMembers open class TextEntryField: FormEntryField {
@objcMembers open class TextEntryField: FormEntryField, UITextFieldDelegate {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
@ -27,6 +27,13 @@ import UIKit
private(set) var textField: UITextField?
private var calendar: Calendar?
//--------------------------------------------------
// MARK: - Accessories
//--------------------------------------------------
public weak var datePicker: UIDatePicker?
public var pickerView: UIPickerView?
//--------------------------------------------------
// MARK: - Delegate Properties
//--------------------------------------------------
@ -68,6 +75,7 @@ import UIKit
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.textField?.isEnabled = self.isEnabled
self.textField?.textColor = self.isEnabled ? self.enabledTextColor : self.enabledTextColor
}
}
@ -88,6 +96,18 @@ import UIKit
}
}
public override var errorMessage: String? {
didSet {
textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField?.text ?? "", errorMessage ?? "")
}
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var textFieldTrailingConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -111,7 +131,7 @@ import UIKit
public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) {
super.init(frame: .zero)
setupView()
setBothTextFieldDelegates(bothDelegates)
setBothTextDelegates(bothDelegates)
}
//--------------------------------------------------
@ -137,6 +157,9 @@ import UIKit
textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 10),
textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)])
textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16)
textFieldTrailingConstraint?.isActive = true
}
open override func updateView(_ size: CGFloat) {
@ -158,66 +181,91 @@ import UIKit
// MARK: - Methods
//--------------------------------------------------
public func showDropDown(_ show: Bool) {
if hasDropDown {
dropDownCaretLabel?.isHidden = !show
dropDownCarrotWidth?.isActive = !show
setNeedsLayout()
layoutIfNeeded()
}
}
open override func showErrorMessage(_ errorMessage: String?) {
super.showErrorMessage(errorMessage)
textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField?.text ?? "", errorMessage ?? "")
}
open override func hideError() {
super.hideError()
open func clearError() {
feedback = nil
textField?.accessibilityValue = nil
}
public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) {
public func setBothTextDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) {
mfTextFieldDelegate = delegate
uiTextFieldDelegate = delegate
}
public override func setWithMap(_ map: [AnyHashable: Any]?) {
super.setWithMap(map)
public func defaultValidationBlock() {
guard let map = map, !map.isEmpty else { return }
validationBlock = { enteredValue in
return (enteredValue?.count ?? 0) > 0
}
}
//--------------------------------------------------
// MARK: - Observing for change
//--------------------------------------------------
func valueChanged() {
if let formText = map[KeyLabel] as? String {
self.formText = formText
if !showErrorMessage {
feedback = ""
}
if let text = map[KeyValue] as? String {
self.text = text
}
let previousValidity = isValid
if let text = map[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) {
formIsDisabled()
}
// If validation not set, input will always be valid
isValid = validationBlock?(text) ?? true
if let dropDown = map[KeyType] as? String {
dropDownCaretLabel?.isHidden = false
self.hasDropDown = true
}
// Key used to send text value to server
if let fieldKey = map[KeyFieldKey] as? String {
self.fieldKey = fieldKey
}
switch map.stringForkey(KeyType) {
case "dropDown":
dropDownCaretLabel?.isHidden = false
self.hasDropDown = true
if previousValidity && !isValid {
feedback = errorMessage
mfTextFieldDelegate?.isInvalid?(textfield: self)
} else if !previousValidity && isValid {
clearError()
if let mfTextFieldDelegate = mfTextFieldDelegate {
mfTextFieldDelegate.isValid?(textfield: self)
}
}
}
func endInputing() {
if isValid {
clearError()
separatorView?.backgroundColor = .black
} else if let errMessage = errorMessage {
feedback = errMessage
}
}
func startEditing() {
textField?.becomeFirstResponder()
}
}
// MARK: - Molecular
extension TextEntryField {
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let delegateObject = delegateObject,
let dictionary = json,
!dictionary.isEmpty
else { return }
FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol)
if let enabledTextColorHex = dictionary["enabledTextColor"] as? String {
enabledTextColor = UIColor.mfGet(forHex: enabledTextColorHex)
}
if let disabledTextColorHex = dictionary["disabledTextColor"] as? String {
disabledTextColor = UIColor.mfGet(forHex: disabledTextColorHex)
}
switch dictionary.stringForkey(KeyType) {
case "password":
textField?.isSecureTextEntry = true
@ -231,7 +279,7 @@ import UIKit
break
}
let regex = map.stringForkey("regex")
let regex = dictionary.stringForkey("regex")
if !regex.isEmpty {
validationBlock = { enteredValue in
@ -241,196 +289,17 @@ import UIKit
} else {
defaultValidationBlock()
}
}
public func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) {
guard let textField = textField else { return }
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate)
setBothTextFieldDelegates(delegate)
setWithMap(map)
}
public func defaultValidationBlock() {
validationBlock = { enteredValue in
return (enteredValue?.count ?? 0) > 0
}
}
open override func formIsEnabled() {
super.formIsEnabled()
textField?.isUserInteractionEnabled = true
textField?.isEnabled = true
showDropDown(true)
}
open override func formIsDisabled() {
super.formIsDisabled()
textField?.isUserInteractionEnabled = false
textField?.isEnabled = false
self.showDropDown(false)
}
//--------------------------------------------------
// MARK: - Observing for change
//--------------------------------------------------
func valueChanged() {
// Update label for placeholder
if !showError {
feedbackLabel?.text = ""
if let formValidationProtocol = delegateObject.formValidationProtocol {
mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol)
}
let previousValidity = isValid
uiTextFieldDelegate = delegateObject.uiTextFieldDelegate
// If validation not set, input will always be valid
isValid = validationBlock?(text) ?? true
if previousValidity && !isValid {
if let errMessage = errorMessage {
showErrorMessage(errMessage)
}
if let mfTextFieldDelegate = mfTextFieldDelegate {
mfTextFieldDelegate.isInvalid?(textfield: self)
}
} else if !previousValidity && isValid {
hideError()
if let mfTextFieldDelegate = mfTextFieldDelegate {
mfTextFieldDelegate.isValid?(textfield: self)
}
if let textField = textField {
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate)
}
}
func endInputing() {
if isValid {
hideError()
separatorView?.backgroundColor = .black
} else if let errMessage = errorMessage {
showErrorMessage(errMessage)
}
}
func startEditing() {
textField?.becomeFirstResponder()
showErrorDropdown(!showError)
}
class func getEnabledTextfields(_ textFieldToDetermine: [TextEntryField]?) -> [AnyHashable]? {
var enabledTextFields = [AnyHashable]()
for textfield in textFieldToDetermine ?? [] {
if textfield.isEnabled {
enabledTextFields.append(textfield)
}
}
return enabledTextFields
}
}
// MARK: - Date Picker
extension TextEntryField {
private func createDatePicker() {
guard let textField = textField else { return }
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate)
datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField)
var calendar: Calendar = .current
calendar.timeZone = NSTimeZone.system
self.calendar = calendar
}
public func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) {
createDatePicker()
if show, let fromDate = fromDate {
if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
self.text = formatter.string(from: fromDate)
}
}
datePicker?.minimumDate = fromDate
datePicker?.maximumDate = toDate
}
public func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) {
if let fromDate = fromDate {
if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
text = formatter.string(from: fromDate)
}
}
datePicker?.minimumDate = fromDate
datePicker?.maximumDate = toDate
datePicker?.timeZone = NSTimeZone.system
}
public func dismissDatePicker() -> Date? {
let pickedDate = datePicker?.date
if let pickedDate = pickedDate {
if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
text = formatter.string(from: pickedDate)
}
}
textField?.resignFirstResponder()
return pickedDate
}
public func dismissPicker() {
textField?.resignFirstResponder()
}
}
// MARK: - Molecular
extension TextEntryField {
override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
if let delegateObject = delegateObject {
FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol)
setWithMap(json)
if let formValidationProtocol = delegateObject.formValidationProtocol {
mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol)
}
uiTextFieldDelegate = delegateObject.uiTextFieldDelegate
if let textField = textField {
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate)
}
}
}
override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
return 76
}
}
// MARK: - Accessibility
@ -451,14 +320,10 @@ extension TextEntryField {
var accessibilityString = accessibilityString ?? ""
if hasDropDown, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") {
accessibilityString += txtPickerItem
} else if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") {
if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") {
accessibilityString += txtRegular
}
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
}
}

View File

@ -1,799 +0,0 @@
//
// TextField.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 10/2/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import UIKit
@objc public protocol TextFieldDelegate: NSObjectProtocol {
/// Called when the entered text becomes valid based on the validation block
@objc optional func isValid(textfield: TextField?)
/// Called when the entered text becomes invalid based on the validation block
@objc optional func isInvalid(textfield: TextField?)
/// Dismisses the keyboard.
@objc optional func dismissField(sender: Any?)
}
@objcMembers open class TextField: ViewConstrainingView {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
public var textFieldContainerView: UIView?
public var backgroundView: UIView?
public var textField: UITextField?
public var formLabel: Label?
public var separatorView: UIView?
public var placeholderErrorLabel: Label?
public var dashLine: DashLine?
public var dropDownCarrotLabel: UILabel?
//--------------------------------------------------
// MARK: - Accessories
//--------------------------------------------------
public weak var datePicker: UIDatePicker?
private(set) weak var toolbar: UIToolbar?
//--------------------------------------------------
// MARK: - Delegate Properties
//--------------------------------------------------
/// The delegate and block for validation. Validates if the text that the user has entered is valid or not. Checked after each change if there is a delegate.
public weak var mfTextFieldDelegate: TextFieldDelegate? {
didSet {
if mfTextFieldDelegate != nil && !observingForChanges {
observingForChanges = true
NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextField.textDidChangeNotification, object: textField)
NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextField.textDidEndEditingNotification, object: textField)
NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextField.textDidBeginEditingNotification, object: textField)
} else if mfTextFieldDelegate == nil && observingForChanges {
observingForChanges = false
NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: textField)
NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: textField)
NotificationCenter.default.removeObserver(self, name: UITextField.textDidBeginEditingNotification, object: textField)
}
}
}
/// If you're using a MFViewController, you must set this to it
public weak var uiTextFieldDelegate: UITextFieldDelegate? {
get {
return textField?.delegate
}
set {
textField?.delegate = newValue
}
}
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
private var calendar: Calendar?
public var pickerView: UIPickerView?
public var observingForChanges = false
public var showError = false
public var hasDropDown = false
public var isEnabled = true
private var borderPath: UIBezierPath?
/// Determines if a border should be drawn.
public var hideBorder = false {
didSet {
setNeedsLayout()
}
}
public var text: String? {
get {
return textField?.text
}
set {
textField?.text = newValue
valueChanged()
}
}
public var formText: String? {
get {
return formLabel?.text
}
set {
formLabel?.text = newValue
setAccessibilityString(newValue)
}
}
public var placeholderTextColor: UIColor = .black
/// Setgs placeholder text in the textField.
public var placeholder: String? {
get {
guard let attributedPlaceholder = textField?.attributedPlaceholder else { return nil }
return attributedPlaceholder.string
}
set {
guard let newPlaceholderText = newValue else {
textField?.attributedPlaceholder = nil
return
}
textField?.attributedPlaceholder = NSAttributedString(string: newPlaceholderText, attributes: [NSAttributedString.Key.foregroundColor: placeholderTextColor])
if !showError {
placeholderErrorLabel?.text = (textField?.text?.count ?? 0) > 0 ? newPlaceholderText : ""
}
setAccessibilityString(newPlaceholderText)
}
}
public var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeZone = NSTimeZone.system
formatter.locale = .current
formatter.formatterBehavior = .default
return formatter
}()
public var isValid = false
public var fieldKey: String?
public var validationBlock: ((_ enteredValue: String?) -> Bool)? {
didSet {
valueChanged()
}
}
public var enabledTextColor: UIColor?
public var disabledTextColor: UIColor?
public var errorMessage: String?
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var heightConstraint: NSLayoutConstraint?
public var dropDownCarrotWidth: NSLayoutConstraint?
public var textContainerLeftPin: NSLayoutConstraint?
public var textContainerRightPin: NSLayoutConstraint?
public var errorLableRightPin: NSLayoutConstraint?
public var errorLableLeftPin: NSLayoutConstraint?
public var formLabelLeftPin: NSLayoutConstraint?
public var formLabelRightPin: NSLayoutConstraint?
public var separatorHeightConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
/// Basic initializer.
public convenience init() {
self.init(frame: .zero)
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("init(coder:) has not been implemented")
}
/// - parameter bothDelegates: Sets both MF/UI Text Field Delegates.
public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) {
super.init(frame: .zero)
setupView()
setBothTextFieldDelegates(bothDelegates)
}
/// - parameter hasDropDown: tbd
/// - parameter map: Dictionary of values to setup this TextField
/// - parameter bothDelegate: Sets both MF/UI Text Field Delegates.
public init(hasDropDown: Bool = false, map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) {
super.init(frame: .zero)
setupView()
dropDownCarrotLabel?.isHidden = hasDropDown
self.hasDropDown = !hasDropDown
setWithMap(map, bothDelegates: bothDelegates)
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
/// Initial configuration of class and view.
override open func setupView() {
guard subviews.isEmpty else { return }
translatesAutoresizingMaskIntoConstraints = false
setContentHuggingPriority(.required, for: .vertical)
setContentCompressionResistancePriority(.required, for: .vertical)
backgroundColor = .clear
let formLabel = Label()
self.formLabel = formLabel
formLabel.font = MFStyler.fontB3()
formLabel.textColor = UIColor.mfBattleshipGrey()
formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal)
formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical)
formLabel.setContentCompressionResistancePriority(.required, for: .vertical)
addSubview(formLabel)
formLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
formLabelLeftPin = formLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
formLabelLeftPin?.isActive = true
formLabelRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: formLabel.trailingAnchor)
formLabelRightPin?.isActive = true
let textFieldContainerView = UIView(frame: .zero)
self.textFieldContainerView = textFieldContainerView
textFieldContainerView.translatesAutoresizingMaskIntoConstraints = false
addSubview(textFieldContainerView)
constrainTextFieldContent(textFieldContainerView)
textFieldContainerView.topAnchor.constraint(equalTo: formLabel.bottomAnchor, constant: 4).isActive = true
textContainerLeftPin = textFieldContainerView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
textContainerLeftPin?.isActive = true
textContainerRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: textFieldContainerView.trailingAnchor)
textContainerRightPin?.isActive = true
let placeholderErrorLabel = Label()
self.placeholderErrorLabel = placeholderErrorLabel
placeholderErrorLabel.font = MFStyler.fontForTextFieldUnderLabel()
placeholderErrorLabel.textColor = .black
placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal)
placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal)
placeholderErrorLabel.setContentCompressionResistancePriority(.required, for: .vertical)
addSubview(placeholderErrorLabel)
placeholderErrorLabel.topAnchor.constraint(equalTo: textFieldContainerView.bottomAnchor).isActive = true
errorLableLeftPin = placeholderErrorLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
errorLableLeftPin?.isActive = true
errorLableRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: placeholderErrorLabel.trailingAnchor)
errorLableRightPin?.isActive = true
layoutMarginsGuide.bottomAnchor.constraint(equalTo: placeholderErrorLabel.bottomAnchor).isActive = true
setNeedsLayout()
}
/// Configuration logic for the text container view.
private func constrainTextFieldContent(_ parentView: UIView) {
let backgroundView = UIView(frame: .zero)
self.backgroundView = backgroundView
backgroundView.translatesAutoresizingMaskIntoConstraints = false
addSubview(backgroundView)
NSLayoutConstraint.activate([
backgroundView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 1),
backgroundView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 1),
parentView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: 1),
parentView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: 1)])
let textField = UITextField(frame: .zero)
self.textField = textField
textField.translatesAutoresizingMaskIntoConstraints = false
textField.setContentCompressionResistancePriority(.required, for: .vertical)
textField.heightAnchor.constraint(equalToConstant: 24).isActive = true
textField.font = MFStyler.fontForTextField()
textField.smartQuotesType = .no
textField.smartDashesType = .no
textField.smartInsertDeleteType = .no
MFStyler.styleTextField(textField)
addSubview(textField)
NSLayoutConstraint.activate([
textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10),
textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16),
parentView.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)])
let dropDownCarrotLabel = Label()
self.dropDownCarrotLabel = dropDownCarrotLabel
dropDownCarrotLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal)
dropDownCarrotLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical)
dropDownCarrotLabel.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal)
dropDownCarrotLabel.isHidden = true
dropDownCarrotLabel.isUserInteractionEnabled = true
let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing))
dropDownCarrotLabel.addGestureRecognizer(tapOnCarrot)
addSubview(dropDownCarrotLabel)
dropDownCarrotLabel.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
dropDownCarrotLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6).isActive = true
parentView.trailingAnchor.constraint(equalTo: dropDownCarrotLabel.trailingAnchor, constant: 16).isActive = true
parentView.bottomAnchor.constraint(equalTo: dropDownCarrotLabel.bottomAnchor).isActive = true
dropDownCarrotWidth = dropDownCarrotLabel.widthAnchor.constraint(equalToConstant: 0)
dropDownCarrotWidth?.isActive = true
let separatorView = UIView(frame: .zero)
self.separatorView = separatorView
separatorView.translatesAutoresizingMaskIntoConstraints = false
separatorView.backgroundColor = .black
addSubview(separatorView)
separatorHeightConstraint = separatorView.heightAnchor.constraint(equalToConstant: 1)
separatorHeightConstraint?.isActive = true
separatorView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
parentView.trailingAnchor.constraint(equalTo: separatorView.trailingAnchor).isActive = true
parentView.bottomAnchor.constraint(equalTo: separatorView.bottomAnchor).isActive = true
let dashLine = DashLine()
dashLine.translatesAutoresizingMaskIntoConstraints = false
dashLine.backgroundColor = .white
dashLine.isHidden = true
addSubview(dashLine)
NSLayoutConstraint.activate([
dashLine.centerYAnchor.constraint(equalTo: separatorView.centerYAnchor),
dashLine.centerXAnchor.constraint(equalTo: separatorView.centerXAnchor),
dashLine.topAnchor.constraint(equalTo: separatorView.topAnchor),
dashLine.leadingAnchor.constraint(equalTo: separatorView.leadingAnchor)])
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
formLabel?.updateView(size)
placeholderErrorLabel?.font = MFStyler.fontForTextFieldUnderLabel()
if let textField = textField {
MFStyler.styleTextField(textField)
}
dashLine?.updateView(size)
layoutIfNeeded()
}
deinit {
mfTextFieldDelegate = nil
uiTextFieldDelegate = nil
}
open override func draw(_ rect: CGRect) {
super.draw(rect)
borderPath?.removeAllPoints()
if !hideBorder, let frame = textFieldContainerView?.frame {
borderPath = UIBezierPath()
borderPath?.move(to: CGPoint(x: frame.origin.x, y: frame.origin.y + frame.size.height))
borderPath?.addLine(to: CGPoint(x: frame.origin.x, y: frame.origin.y))
borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y))
borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y + frame.size.height))
borderPath?.lineWidth = 1
let strokeColor = showError ? UIColor.mfPumpkin() : UIColor.mfSilver()
strokeColor.setStroke()
borderPath?.stroke()
}
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
func showErrorDropdown(_ show: Bool) {
DispatchQueue.main.async { [weak self] in
self?.showError = show
self?.separatorHeightConstraint?.constant = show ? 4 : 1
self?.separatorView?.backgroundColor = show ? UIColor.mfPumpkin() : .black
self?.setNeedsDisplay()
self?.layoutIfNeeded()
}
}
func showErrorMessage(_ errorMessage: String?) {
guard isEnabled else { return }
DispatchQueue.main.async { [weak self] in
self?.separatorHeightConstraint?.constant = 4
self?.showError = true
self?.separatorView?.backgroundColor = UIColor.mfPumpkin()
self?.placeholderErrorLabel?.text = errorMessage
self?.placeholderErrorLabel?.numberOfLines = 0
self?.textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", self?.textField?.text ?? "", self?.errorMessage ?? "")
self?.setNeedsDisplay()
self?.layoutIfNeeded()
self?.showErrorDropdown(self?.showError ?? false)
}
}
public func hideError() {
DispatchQueue.main.async { [weak self] in
self?.separatorHeightConstraint?.constant = 1
self?.separatorView?.backgroundColor = .black
self?.layoutIfNeeded()
self?.showError = false
self?.placeholderErrorLabel?.textColor = .black
self?.placeholderErrorLabel?.text = ""
self?.textField?.accessibilityValue = nil
self?.setNeedsDisplay()
self?.layoutIfNeeded()
}
}
public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) {
mfTextFieldDelegate = delegate
uiTextFieldDelegate = delegate
}
public func setWithMap(_ map: [AnyHashable: Any]?) {
guard let map = map, !map.isEmpty else { return }
if let formText = map[KeyLabel] as? String {
self.formText = formText
}
if let text = map[KeyValue] as? String {
self.text = text
}
if let text = map[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) {
isEnabled(false)
}
if let errMessage = map[KeyErrorMessage] as? String {
self.errorMessage = errMessage
}
if let hideBorder = map["hideBorder"] as? Bool {
self.hideBorder = hideBorder
}
// Key used to send text value to server
if let fieldKey = map[KeyFieldKey] as? String {
self.fieldKey = fieldKey
}
switch map.stringForkey(KeyType) {
case "dropDown":
dropDownCarrotLabel?.isHidden = false
self.hasDropDown = true
case "password":
textField?.isSecureTextEntry = true
case "number":
textField?.keyboardType = .numberPad
case "email":
textField?.keyboardType = .emailAddress
default:
break
}
let regex = map.stringForkey("regex")
if !regex.isEmpty {
validationBlock = { enteredValue in
guard let value = enteredValue else { return false }
return MVMCoreUIUtility.validate(value, withRegularExpression: regex)
}
} else {
defaultValidationBlock()
}
}
public func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) {
guard let textField = textField else { return }
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate)
setBothTextFieldDelegates(delegate)
setWithMap(map)
}
public func defaultValidationBlock() {
validationBlock = { enteredValue in
return (enteredValue?.count ?? 0) > 0
}
}
open override func setLeftPinConstant(_ constant: CGFloat) {
textContainerLeftPin?.constant = constant
errorLableLeftPin?.constant = constant
formLabelLeftPin?.constant = constant
}
open override func setRightPinConstant(_ constant: CGFloat) {
textContainerRightPin?.constant = constant
errorLableRightPin?.constant = constant
formLabelRightPin?.constant = constant
}
public func showDropDown(_ show: Bool) {
if hasDropDown {
dropDownCarrotLabel?.isHidden = !show
dropDownCarrotWidth?.isActive = !show
setNeedsLayout()
layoutIfNeeded()
}
}
public func isEnabled(_ enable: Bool) {
// Set outside the dispatch so that registerAnimations can know about it
isEnabled = enable
DispatchQueue.main.async { [weak self] in
self?.isUserInteractionEnabled = enable
self?.textField?.isUserInteractionEnabled = enable
self?.textField?.isEnabled = enable
if enable {
self?.textField?.textColor = self?.enabledTextColor ?? .black
self?.formLabel?.textColor = UIColor.mfBattleshipGrey()
self?.placeholderErrorLabel?.textColor = .black
self?.separatorView?.backgroundColor = (self?.showError ?? false) ? UIColor.mfPumpkin() : .black
self?.showDropDown(true)
} else {
self?.textField?.textColor = self?.disabledTextColor ?? UIColor.mfSilver()
self?.formLabel?.textColor = UIColor.mfSilver()
self?.placeholderErrorLabel?.textColor = UIColor.mfSilver()
self?.showDropDown(false)
self?.hideError() // Should not have error if the field is disabled
self?.separatorView?.backgroundColor = UIColor.mfSilver()
}
}
}
public func showLabel(_ show: Bool) {
placeholderErrorLabel?.isHidden = !show
}
public func dashSeperatorView(_ dash: Bool) {
// Never hide seperator view because it could be possiblely used by other classes for positioning
dashLine?.isHidden = !dash
separatorView?.backgroundColor = dash ? .clear : .black
}
//--------------------------------------------------
// MARK: - Observing for change
//--------------------------------------------------
func valueChanged() {
// Update label for placeholder
if !showError {
placeholderErrorLabel?.text = ""
}
let previousValidity = isValid
// If validation not set, input will always be valid
isValid = validationBlock?(text) ?? true
if previousValidity && !isValid {
if let errMessage = errorMessage {
showErrorMessage(errMessage)
}
if let mfTextFieldDelegate = mfTextFieldDelegate {
mfTextFieldDelegate.isInvalid?(textfield: self)
}
} else if !previousValidity && isValid {
hideError()
if let mfTextFieldDelegate = mfTextFieldDelegate {
mfTextFieldDelegate.isValid?(textfield: self)
}
}
}
func endInputing() {
if isValid {
hideError()
separatorView?.backgroundColor = .black
} else if let errMessage = errorMessage {
showErrorMessage(errMessage)
}
}
func startEditing() {
textField?.becomeFirstResponder()
showErrorDropdown(!showError)
}
class func getEnabledTextfields(_ textFieldToDetermine: [MFTextField]?) -> [AnyHashable]? {
var enabledTextFields = [AnyHashable]()
for textfield in textFieldToDetermine ?? [] {
if textfield.isEnabled {
enabledTextFields.append(textfield)
}
}
return enabledTextFields
}
}
// MARK: - Date Picker
extension TextField {
private func createDatePicker() {
guard let textField = textField else { return }
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate)
datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField)
var calendar: Calendar = .current
calendar.timeZone = NSTimeZone.system
self.calendar = calendar
}
public func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) {
createDatePicker()
if show, let fromDate = fromDate {
if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
self.text = formatter.string(from: fromDate)
}
}
datePicker?.minimumDate = fromDate
datePicker?.maximumDate = toDate
}
public func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) {
if let fromDate = fromDate {
if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
text = formatter.string(from: fromDate)
}
}
datePicker?.minimumDate = fromDate
datePicker?.maximumDate = toDate
datePicker?.timeZone = NSTimeZone.system
}
public func dismissDatePicker() -> Date? {
let pickedDate = datePicker?.date
if let pickedDate = pickedDate {
if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
text = formatter.string(from: pickedDate)
}
}
textField?.resignFirstResponder()
return pickedDate
}
public func dismissPicker() {
textField?.resignFirstResponder()
}
}
// MARK: - Molecular
extension TextField {
override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
if let delegateObject = delegateObject {
FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol)
setWithMap(json)
if let formValidationProtocol = delegateObject.formValidationProtocol {
mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol)
}
uiTextFieldDelegate = delegateObject.uiTextFieldDelegate
if let textField = textField {
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate)
}
}
}
override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
return 76
}
}
// MARK: - Accessibility
extension TextField {
func pushAccessibilityNotification() {
DispatchQueue.main.async { [weak self] in
UIAccessibility.post(notification: .layoutChanged, argument: self?.textField)
}
}
/**
Adding missing accessibilityLabel value
if we have some value in accessibilityLabel,
then only can append regular and picker item
*/
func setAccessibilityString(_ accessibilityString: String?) {
guard let textField = textField else { return }
var accessibilityString = accessibilityString ?? ""
if hasDropDown, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") {
accessibilityString += txtPickerItem
} else if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") {
accessibilityString += txtRegular
}
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
}
}
// MARK: - Form Validation
extension TextField: FormValidationProtocol {
public func isValidField() -> Bool {
return isValid
}
public func formFieldName() -> String? {
return fieldKey
}
public func formFieldValue() -> Any? {
return text
}
}