diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 644e5488..83e2b9a1 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; - 0A21DB7F235DECC500C160A2 /* FieldEntryFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* FieldEntryFormView.swift */; }; + 0A21DB7F235DECC500C160A2 /* FormEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* FormEntryField.swift */; }; 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */; }; 0A21DB84235E06EF00C160A2 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; @@ -33,6 +33,7 @@ 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */; }; 0A21DB8E235E06EF00C160A2 /* MFDigitTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */; }; 0A21DB91235E0EDB00C160A2 /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; + 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; @@ -210,9 +211,10 @@ 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; - 0A21DB7E235DECC500C160A2 /* FieldEntryFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldEntryFormView.swift; sourceTree = ""; }; + 0A21DB7E235DECC500C160A2 /* FormEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormEntryField.swift; sourceTree = ""; }; 0A21DB80235DF87300C160A2 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryField.swift; sourceTree = ""; }; + 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; @@ -764,9 +766,10 @@ 0A8321A72355062F00CB7F00 /* MdnTextField.swift */, 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */, 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */, - 0A21DB7E235DECC500C160A2 /* FieldEntryFormView.swift */, + 0A21DB7E235DECC500C160A2 /* FormEntryField.swift */, 0A21DB80235DF87300C160A2 /* TextField.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, + 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, ); path = TextFields; sourceTree = ""; @@ -1045,7 +1048,7 @@ D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, - 0A21DB7F235DECC500C160A2 /* FieldEntryFormView.swift in Sources */, + 0A21DB7F235DECC500C160A2 /* FormEntryField.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, @@ -1112,6 +1115,7 @@ 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, + 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift new file mode 100644 index 00000000..346c1c9c --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -0,0 +1,528 @@ +// +// DigitEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +class DigitEntryField: TextEntryField, UITextFieldDelegate, DigitTextBoxDelegate { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + private weak var digitFieldsView: UIView? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + private var numberOfDigits = 0 + private var switchedAutomatically = false + public var digitFields: [DigitTextBox]? + + /// Setgs placeholder text in the textField. + public override var placeholder: String? { + get { + var string = "" + + for digitField in digitFields ?? [] { + if let placeholderText = digitField.attributedPlaceholder?.string { + string += placeholderText + } + } + + return !string.isEmpty ? string : nil + } + set { + guard let placeholderValue = newValue else { return } + + + + (digitFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + + if idx < (newValue?.count ?? 0) { + + let stringForIndex = (newValue as NSString?)?.substring(with: NSRange(location: idx, length: 1)) + obj.attributedPlaceholder = NSAttributedString(string: stringForIndex ?? "", attributes: [ + NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) + } else if stop != nil { + stop = true + } + }) + } + + // If there is already text in the textfield, set the place holder label below. + if placeholderErrorLabel.length > 0 && !showError { + placeholderErrorLabel.text = newValue + + } else if !showError { + placeholderErrorLabel.text = "" + } + + if label.text.length > 0 { + labelToTextFieldPin?.constant = 10 + } else { + labelToTextFieldPin?.constant = 0 + } + + // adding missing accessibilityLabel value + // if we have some value in accessibilityLabel, + // then only can append regular and picker item + textField.accessibilityLabel() = newValue ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular")) + + } + + public override var text: String? { + get { + var string = "" + + for digitField in digitFields ?? [] { + if let digitText = digitField.text { + string += digitText + } + } + + return string + } + set { + (digitFields as NSArray?)?.enumerateObjects( { obj, idx, stop in + + if idx < (text?.count ?? 0) { + let stringForIndex = (text as NSString?)?.substring(with: NSRange(location: idx, length: 1)) + obj.text = stringForIndex + } else if stop != nil { + stop = true + } + }) + valueChanged() + } + } + + public override var formText: String? { + get { + return formDescriptionLabel?.text + } + set { + if let formText = newValue, !formText.isEmpty > 0 { + messageToTextFieldPin?.constant = 10 + } else { + messageToTextFieldPin?.constant = 0 + } + super.formText = newValue + } + } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private weak var messageToTextFieldPin: NSLayoutConstraint? + private weak var labelToTextFieldPin: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") + } + + public init(numberOfDigits: Int) { + super.init(frame: .zero) + + self.numberOfDigits = numberOfDigits + buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) + } + + public init(numberOfDigits: Int, bothDelegates delegates: (UITextFieldDelegate & MFTextFieldDelegate)?) { + super.init(bothDelegates: delegates as? (TextFieldDelegate & UITextFieldDelegate)) + + self.numberOfDigits = numberOfDigits + buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) + } + + public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { + super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) + + self.numberOfDigits = numberOfDigits + buildTextFieldsView(size: size) + } + + open override func setupFieldContainerContent(_ container: UIView) { + + setupTextFieldsView(forSize: numberOfDigits) + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func createDigitField() -> DigitTextBox { + + let textField = DigitTextBox() + textField.delegate = self + textField.textBoxDelegate = self + + return textField + } + + func buildTextFieldsView(size: CGFloat) { + + // Remove all current UI. + if let digitFields = digitFields, !digitFields.isEmpty { + StackableViewController.remove(digitFields) + } + + if numberOfDigits > 0 { + + let digitFields = [DigitTextBox](repeating: createDigitField(), count: numberOfDigits) + + for digitField in digitFields { + digitField.updateView(size) + } + + self.digitFields = digitFields + setupTextFieldsView(forSize: size) + + } else { + digitFields = nil + } + } + + override func valueChanged() { + super.valueChanged() + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + if let placeholder = self.placeholder, !placeholder.isEmpty { + self.labelToTextFieldPin?.constant = 10 + + } else { + self.labelToTextFieldPin?.constant = 0 + } + } + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.formDescriptionLabel?.updateView(size) + + if let digitFields = self.digitFields, !digitFields.isEmpty { + + // Remove all current UI. + StackableViewController.remove(digitFields) + + // Update text boxes. + for digitField in digitFields { + digitField.updateView(size) + } + } + + // Layout text boxes. + self.setupTextFieldsView(forSize: size) + } + } + + open override func setupView() { + super.setupView() + + formDescriptionLabel?.styleB2(true) + self.formText = "" + alignCenterHorizontal() + } + + func setupTextFieldsView(forSize size: CGFloat) { + + guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize(), + let digitFieldsView = digitFieldsView, + let digitFields = digitFields + else { return } + + StackableViewController.populateViewHorizontally(digitFieldsView, withUIArray: digitFields, withSpacingBlock: { object in + + var inset = UIEdgeInsets(top: 0, left: space, bottom: 0, right: space) + + guard let digitFields = self.digitFields else { return inset } + + if digitFields.count == 1 { + inset.left = 0 + inset.right = 0 + + } else if let field = object as? UITextField, field == digitFields.first { + inset.left = 0 + + } else if let field = object as? UITextField, field == digitFields.last { + inset.right = 0 + } + + return inset + }) + } + + //-------------------------------------------------- + // MARK: - Molecule + //-------------------------------------------------- + + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + + guard let dictionary = json else { return } + + let digits = dictionary["digits"] as? Int ?? 4 + if digits != numberOfDigits { + numberOfDigits = digits + } + + buildTextFieldsView(size: MVMCoreUIUtility.getWidth()) + + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + open override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + + return 44 + } + + //-------------------------------------------------- + // MARK: - Setters + //-------------------------------------------------- + + func setAsSecureTextEntry(_ secureEntry: Bool) { + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + (self.digitFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + obj.isSecureTextEntry = secureEntry + + //accessibility - 33704 fix voice over will read what pin user is filling + obj.accessibilityLabel() = String(format: "PIN %lu of %lu", UInt(idx) + 1, UInt(self.digitFields?.count ?? 0)) + }) + } + } + + override public func showErrorMessage(_ errorMessage: String?) { + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + super.showErrorMessage (errorMessage) + + if self.showError { + self.labelToTextFieldPin?.constant = 10 + } + for field in self.digitFields ?? [] { + field.setAsError() + } + } + } + + public override func hideError() { + super.hideError() + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + for field in self.digitFields ?? [] { + field.hideError() + } + } + } + + func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + super.setWithMap(map, bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) + + if (map?.count ?? 0) > 0 { + for textField in digitFields ?? [] { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) + } + } + } + + func setDefaultValidationBlock() { + + weak var weakSelf = self + + self.validationBlock = { enteredValue in + if (enteredValue?.count ?? 0) > 0 && (enteredValue?.count ?? 0) == weakSelf?.digitFields?.count { + return true + } else { + return false + } + } + } + + func enable(_ enable: Bool) { + super.enable(enable) + + if enable { + formDescriptionLabel?.styleB2(true) + } else { + formDescriptionLabel?.textColor = UIColor.mfBattleshipGrey() + } + + for textField in digitFields ?? [] { + textField.isUserInteractionEnabled = enable + textField.isEnabled = enable + + if enable { + textField.textColor = UIColor.black + } else { + textField.textColor = UIColor.mfBattleshipGrey() + } + } + } + + //-------------------------------------------------- + // MARK: - Helpers + //-------------------------------------------------- + + func selectPreviousTextField(_ currentTextField: UITextField?, clear: Bool) { + + var selectNextField = false + + (digitFields as NSArray?)?.enumerateObjects(options: .reverse, using: { obj, idx, stop in + if obj == currentTextField { + selectNextField = true + } else if selectNextField { + if !clear { + self.switchedAutomatically = true + } + obj.becomeFirstResponder() + self.switchedAutomatically = false + stop = true + + //accessibility + UIAccessibility.post(notification: .layoutChanged, argument: obj) + } + }) + } + + func selectNextTextField(_ currentTextField: UITextField?, clear: Bool) { + + var selectNextField = false + (digitFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + + if obj == currentTextField { + selectNextField = true + + } else if selectNextField { + if !clear { + self.switchedAutomatically = true + } + obj.becomeFirstResponder() + self.switchedAutomatically = false + stop = true + + //accessibility + UIAccessibility.post(notification: .layoutChanged, argument: obj) + } + }) + } + + //-------------------------------------------------- + // MARK: - Accessinility + //-------------------------------------------------- + + open override var accessibilityElements: [Any]? { + + if let digitFields = self.digitFields { + return [digitFields] //return [self.digitFields arrayByAddingObject:(DigitTextBox *)self.label]; + } else { + return [placeholder] + } + } + + //-------------------------------------------------- + // MARK: - TextFieldDelegate + //-------------------------------------------------- + + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + + return uiTextFieldDelegate?.textFieldShouldReturn?(textField) ?? true + } + + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { + return false + } + + if (textField.text?.count ?? 0) >= range.length + range.location { + + let oldLength = textField.text?.count ?? 0 + let replacementLength = string.count + if replacementLength > 1 { + + // Too long (Check with AKQA if they want to allow pasting the digits. + return false + } else if replacementLength == 1 && (oldLength == 1 || oldLength == 0) { + + // One character, switch old value with new, select next textfield + textField.text = string + selectNextTextField(textField, clear: false) + valueChanged() + return false + } else if replacementLength == 0 && oldLength == 1 { + // non empty cell, clear and stay. + textField.text = "" + valueChanged() + return false + } + return true + } + + return false + } + + func textFieldDidDelete(_ textField: UITextField?) { + + // empty cell, go back to previous cell and clear. + selectPreviousTextField(textField, clear: true) + } + + @objc public func textFieldDidBeginEditing(_ textField: UITextField) { + + if !switchedAutomatically { + textField.text = "" + valueChanged() + } + + uiTextFieldDelegate?.textFieldDidBeginEditing?(textField) + } + + @objc public func textFieldDidEndEditing(_ textField: UITextField) { + + + uiTextFieldDelegate?.textFieldDidEndEditing?(textField) + } + + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + + selectPreviousTextField(textField, clear: false) + + return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true + } + + // MARK: - Passed Along TextField delegate + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + return uiTextFieldDelegate?.textFieldShouldBeginEditing?(textField) ?? true + } + + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + + return uiTextFieldDelegate?.textFieldShouldEndEditing?(textField) ?? true + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift index 0f9d5848..4ff01512 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -151,10 +151,6 @@ import UIKit buildTextFieldsView(size: size) } - private func setup() { - - } - //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/FieldEntryFormView.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift similarity index 87% rename from MVMCoreUI/Atoms/TextFields/FieldEntryFormView.swift rename to MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 53a3cda1..f3fce186 100644 --- a/MVMCoreUI/Atoms/TextFields/FieldEntryFormView.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -1,5 +1,5 @@ // -// FieldEntryForm.swift +// FormEntryField.swift // MVMCoreUI // // Created by Kevin Christiano on 10/21/19. @@ -13,7 +13,7 @@ import UIKit * This class is intended to be subclassed by a class that will add views subclassed under UIControl. * The FieldEntryForm provides the base logic for the description label, placeholder/error label and field container. */ -@objcMembers open class FieldEntryFormView: ViewConstrainingView { +@objcMembers open class FormEntryField: ViewConstrainingView { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -203,7 +203,7 @@ import UIKit /// Method to override. /// Intended to add the interactive content (textField) to the fieldContainer. open func setupFieldContainerContent(_ container: UIView) { - + // To Be Overridden } /// Configuration logic for the text container view. @@ -288,12 +288,13 @@ import UIKit open func showErrorDropdown(_ show: Bool) { DispatchQueue.main.async { [weak self] in + guard let self = self else { return } - self?.showError = show - self?.separatorHeightConstraint?.constant = show ? 4 : 1 - self?.separatorView?.backgroundColor = show ? UIColor.mfPumpkin() : .black - self?.setNeedsDisplay() - self?.layoutIfNeeded() + self.showError = show + self.separatorHeightConstraint?.constant = show ? 4 : 1 + self.separatorView?.backgroundColor = show ? UIColor.mfPumpkin() : .black + self.setNeedsDisplay() + self.layoutIfNeeded() } } @@ -302,29 +303,32 @@ import UIKit guard isEnabled else { return } DispatchQueue.main.async { [weak self] in + guard let self = self else { return } - self?.separatorHeightConstraint?.constant = 4 - self?.showError = true - self?.separatorView?.backgroundColor = UIColor.mfPumpkin() - self?.placeholderErrorLabel?.text = errorMessage - self?.placeholderErrorLabel?.numberOfLines = 0 - self?.setNeedsDisplay() - self?.layoutIfNeeded() - self?.showErrorDropdown(self?.showError ?? false) + self.separatorHeightConstraint?.constant = 4 + self.showError = true + self.separatorView?.backgroundColor = UIColor.mfPumpkin() + self.placeholderErrorLabel?.text = errorMessage + self.placeholderErrorLabel?.numberOfLines = 0 + self.setNeedsDisplay() + self.layoutIfNeeded() + self.showErrorDropdown(self.showError) } } open 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?.setNeedsDisplay() - self?.layoutIfNeeded() + guard let self = self else { return } + + self.separatorHeightConstraint?.constant = 1 + self.separatorView?.backgroundColor = .black + self.layoutIfNeeded() + self.showError = false + self.placeholderErrorLabel?.textColor = .black + self.placeholderErrorLabel?.text = "" + self.setNeedsDisplay() + self.layoutIfNeeded() } } @@ -384,11 +388,13 @@ import UIKit isEnabled = true DispatchQueue.main.async { [weak self] in - self?.isUserInteractionEnabled = true - self?.formDescriptionLabel?.textColor = UIColor.mfBattleshipGrey() - self?.placeholderErrorLabel?.textColor = .black - self?.separatorView?.backgroundColor = (self?.showError ?? false) ? UIColor.mfPumpkin() : .black - self?.showDropDown(true) + guard let self = self else { return } + + self.isUserInteractionEnabled = true + self.formDescriptionLabel?.textColor = UIColor.mfBattleshipGrey() + self.placeholderErrorLabel?.textColor = .black + self.separatorView?.backgroundColor = (self.showError) ? UIColor.mfPumpkin() : .black + self.showDropDown(true) } } @@ -398,12 +404,14 @@ import UIKit isEnabled = false DispatchQueue.main.async { [weak self] in - self?.isUserInteractionEnabled = false - self?.formDescriptionLabel?.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() + guard let self = self else { return } + + self.isUserInteractionEnabled = false + self.formDescriptionLabel?.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() } } @@ -421,7 +429,7 @@ import UIKit } // MARK: - Molecular -extension FieldEntryFormView { +extension FormEntryField { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) @@ -434,7 +442,7 @@ extension FieldEntryFormView { } // MARK: - Form Validation -extension FieldEntryFormView: FormValidationProtocol { +extension FormEntryField: FormValidationProtocol { public func isValidField() -> Bool { return isValid @@ -450,7 +458,7 @@ extension FieldEntryFormView: FormValidationProtocol { } // MARK: - Accessibility -extension FieldEntryFormView { +extension FormEntryField { @objc open func pushAccessibilityNotification() { // To Be Overriden diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index e4f68a6a..ff4316e9 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -12,221 +12,214 @@ import UIKit import MVMCore class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { -//-------------------------------------------------- -// MARK: - Properties -//-------------------------------------------------- - -public weak var customDelegate: UITextFieldDelegate? -public var isNationalMdn = false -public var shouldValidateMDN = false - -public var mdn: String? { - get { - guard let text = text else { return nil } - - return MVMCoreUIUtility.removeMdnFormat(text) - } - set { - guard let MDN = newValue else { return } - text = MVMCoreUIUtility.formatMdn(MDN) - } -} - -//-------------------------------------------------- -// MARK: - Initializers -//-------------------------------------------------- - -public override init(frame: CGRect) { - super.init(frame: .zero) - setup() -} - -public convenience init() { - self.init(frame: .zero) -} - -required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("init(coder:) has not been implemented") -} - -//-------------------------------------------------- -// MARK: - Setup -//-------------------------------------------------- - -private func setup() { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- - textField?.delegate = self - customDelegate = uiTextFieldDelegate - isNationalMdn = true - textField?.keyboardType = .numberPad + public weak var customDelegate: UITextFieldDelegate? + public var isNationalMdn = false + public var shouldValidateMDN = false - let toolbar = MVMCoreUICommonViewsUtility.makeEmptyToolbar() - let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - let contacts = UIBarButtonItem(title: MVMCoreUIUtility.hardcodedString(withKey: "textfield_contacts_barbutton"), style: .plain, target: self, action: #selector(getContacts(_:))) - let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFieldInput(_:))) - toolbar.items = [contacts, space, dismissButton] - textField?.inputAccessoryView = toolbar -} - -// If you're using a MFViewController, you must set this to it. -public override weak var uiTextFieldDelegate: UITextFieldDelegate? { - get { - return textField?.delegate - } - set { - super.uiTextFieldDelegate = newValue - customDelegate = uiTextFieldDelegate - - if newValue != nil { - textField?.delegate = self + public var mdn: String? { + get { return MVMCoreUIUtility.removeMdnFormat(text) } + set { + text = MVMCoreUIUtility.formatMdn(newValue) } } -} - -//-------------------------------------------------- -// MARK: - Methods -//-------------------------------------------------- - -func hasValidMdn() -> Bool { - guard let MDN = mdn else { return true } + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- - if MDN.isEmpty { + public override init(frame: CGRect) { + super.init(frame: .zero) + setup() + } + + public convenience init() { + self.init(frame: .zero) + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("MdnEntryField xib not supported.") + } + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + + private func setup() { + + textField?.delegate = self + customDelegate = uiTextFieldDelegate + isNationalMdn = true + textField?.keyboardType = .numberPad + + let toolbar = MVMCoreUICommonViewsUtility.makeEmptyToolbar() + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let contacts = UIBarButtonItem(title: MVMCoreUIUtility.hardcodedString(withKey: "textfield_contacts_barbutton"), style: .plain, target: self, action: #selector(getContacts(_:))) + let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFieldInput(_:))) + toolbar.items = [contacts, space, dismissButton] + textField?.inputAccessoryView = toolbar + } + + // If you're using a MFViewController, you must set this to it. + public override weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { return textField?.delegate } + set { + super.uiTextFieldDelegate = newValue + customDelegate = uiTextFieldDelegate + + if newValue != nil { + textField?.delegate = self + } + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func hasValidMdn() -> Bool { + + guard let MDN = mdn else { return true } + + if MDN.isEmpty { + return true + } + + if isNationalMdn { + return MVMCoreUIUtility.validateMDNString(MDN) + } + + return MVMCoreUIUtility.validateInternationalMDNString(MDN) + } + + func validateAndColor() -> Bool { + + if !shouldValidateMDN { + let isValid = hasValidMdn() + + if isValid { + hideError() + } else { + self.errorMessage = getErrorMessage() ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") + UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField) + } + + return isValid + } + return true } - if isNationalMdn { - return MVMCoreUIUtility.validateMDNString(MDN) + func getErrorMessage() -> String? { + + return nil } - return MVMCoreUIUtility.validateInternationalMDNString(MDN) -} - -func validateAndColor() -> Bool { - - if !shouldValidateMDN { - let isValid = hasValidMdn() + @objc func dismissFieldInput(_ sender: Any?) { - if isValid { - hideError() + if let delegate = uiTextFieldDelegate { + delegate.perform(#selector(dismissFieldInput(_:)), with: textField) } else { - self.errorMessage = getErrorMessage() ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") - UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField) + textField?.resignFirstResponder() } - - return isValid } - 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() + func getContacts(_ sender: Any?) { + + let picker = CNContactPickerViewController() + picker.delegate = self + picker.displayedPropertyKeys = ["phoneNumbers"] + picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0") + picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'") + MVMCoreNavigationHandler.shared()?.present(picker, animated: true) } -} - -func getContacts(_ sender: Any?) { - let picker = CNContactPickerViewController() - picker.delegate = self - picker.displayedPropertyKeys = ["phoneNumbers"] - picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0") - picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'") - MVMCoreNavigationHandler.shared()?.present(picker, animated: true) -} - -//-------------------------------------------------- -// MARK: - ContactPicker Delegate -//-------------------------------------------------- - -public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + //-------------------------------------------------- + // MARK: - ContactPicker Delegate + //-------------------------------------------------- - if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { + public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { - let phoneNumber = contactProperty.value as? CNPhoneNumber - let MDN = phoneNumber?.stringValue - var unformattedMDN = MVMCoreUIUtility.removeMdnFormat(MDN) - - // Sometimes user add extra 1 in front of mdn in their address book - if isNationalMdn, - let unformedMDN = unformattedMDN, - unformedMDN.count == 11, - unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" { + if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { - unformattedMDN = (unformedMDN as NSString).substring(from: 1) - } - - text = unformattedMDN - - if let textField = textField { - textFieldShouldReturn(textField) - textFieldDidEndEditing(textField) + let phoneNumber = contactProperty.value as? CNPhoneNumber + let MDN = phoneNumber?.stringValue + var unformattedMDN = MVMCoreUIUtility.removeMdnFormat(MDN) + + // Sometimes user add extra 1 in front of mdn in their address book + if isNationalMdn, + let unformedMDN = unformattedMDN, + unformedMDN.count == 11, + unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" { + + unformattedMDN = (unformedMDN as NSString).substring(from: 1) + } + + text = unformattedMDN + + if let textField = textField { + textFieldShouldReturn(textField) + textFieldDidEndEditing(textField) + } } } -} - -//-------------------------------------------------- -// MARK: - ImplementedTextField Delegate -//-------------------------------------------------- - -@discardableResult -@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() + //-------------------------------------------------- + // MARK: - ImplementedTextField Delegate + //-------------------------------------------------- - return customDelegate?.textFieldShouldReturn?(textField) ?? true -} - -@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { - return false + @discardableResult + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + + return customDelegate?.textFieldShouldReturn?(textField) ?? true } - return customDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true -} - -public func textFieldDidBeginEditing(_ textField: UITextField) { + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { + return false + } + + return customDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + } - textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) - customDelegate?.textFieldDidBeginEditing?(textField) -} - -public func textFieldDidEndEditing(_ textField: UITextField) { + public func textFieldDidBeginEditing(_ textField: UITextField) { + + textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + customDelegate?.textFieldDidBeginEditing?(textField) + } - customDelegate?.textFieldDidEndEditing?(textField) + public func textFieldDidEndEditing(_ textField: UITextField) { + + customDelegate?.textFieldDidEndEditing?(textField) + + if validateAndColor() && isNationalMdn { + textField.text = MVMCoreUIUtility.formatMdn(textField.text) + } + } - if validateAndColor() && isNationalMdn { - textField.text = MVMCoreUIUtility.formatMdn(textField.text) + //-------------------------------------------------- + // MARK: - Passed Along TextField delegate + //-------------------------------------------------- + + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + return customDelegate?.textFieldShouldBeginEditing?(textField) ?? true + } + + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + + return customDelegate?.textFieldShouldEndEditing?(textField) ?? true + } + + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + + return customDelegate?.textFieldShouldClear?(textField) ?? true } } - -//-------------------------------------------------- -// MARK: - Passed Along TextField delegate -//-------------------------------------------------- - -@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - - return customDelegate?.textFieldShouldBeginEditing?(textField) ?? true -} - -@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - - return customDelegate?.textFieldShouldEndEditing?(textField) ?? true -} - -@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { - - return customDelegate?.textFieldShouldClear?(textField) ?? true -} -} diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index a03367ec..b943beea 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -19,7 +19,7 @@ import UIKit } -@objcMembers open class TextEntryField: FieldEntryFormView { +@objcMembers open class TextEntryField: FormEntryField { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -500,7 +500,9 @@ extension TextEntryField { open override func pushAccessibilityNotification() { DispatchQueue.main.async { [weak self] in - UIAccessibility.post(notification: .layoutChanged, argument: self?.textField) + guard let self = self else { return } + + UIAccessibility.post(notification: .layoutChanged, argument: self.textField) } }