From 4915bda4b9eec1c747eaabae9008eb83ef934925 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 3 Oct 2019 10:44:56 -0400 Subject: [PATCH 001/112] Beginning stages of swiftification. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + MVMCoreUI/Atoms/TextFields/TextField.swift | 618 +++++++++++++++++++++ 2 files changed, 622 insertions(+) create mode 100644 MVMCoreUI/Atoms/TextFields/TextField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 17fc19f7..a080e482 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; 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 */; }; + 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; }; @@ -203,6 +204,7 @@ 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 = ""; }; + 0A41BA7E23453A6400D4C0BC /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; @@ -740,6 +742,7 @@ D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, + 0A41BA7E23453A6400D4C0BC /* TextField.swift */, ); path = TextFields; sourceTree = ""; @@ -1046,6 +1049,7 @@ D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, + 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift new file mode 100644 index 00000000..0205b76a --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -0,0 +1,618 @@ +// +// TextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import MVMCoreUI +import UIKit + +@objc protocol MFTextFieldDelegate: NSObjectProtocol { + // Called when the entered text becomes valid based on the validation block + @objc optional func entryIsValid(_ textfield: MFTextField?) + // Called when the entered text becomes invalid based on the validation block + @objc optional func entryIsInvalid(_ textfield: MFTextField?) + // Dismisses the keyboard. + @objc optional func dismissFieldInput(_ sender: Any?) +} + +class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormValidationProtocol { + weak var view: UIView? + @IBOutlet weak var textFieldContainerView: UIView? + @IBOutlet weak var backgroundView: UIView? + @IBOutlet weak var textField: UITextField? + @IBOutlet weak var formLabel: Label? + @IBOutlet weak var separatorView: UIView? + /*make it public so outsider class can know the posistion of it. */ @IBOutlet weak var heightConstraint: NSLayoutConstraint? + @IBOutlet weak var formLabelRightPin: NSLayoutConstraint? + var enabled = false + // To set the placeholder and text + weak var text: String? + weak var formText: String? + weak var fieldKey: String? + weak var placeholder: String? + /* will move out in Feb release */ var hideBorder = false + + weak var datePicker: UIDatePicker? + private(set) weak var toolbar: UIToolbar? + //helper + var formatter: DateFormatter? + // If you're using a MFViewController, you must set this to it + weak var uiTextFieldDelegate: UITextFieldDelegate? + // 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. + weak var mfTextFieldDelegate: MFTextFieldDelegate? + var valid = false + var validationBlock: ((_ enteredValue: String?) -> Bool)? + // custom text colors + var customEnabledTextColor: UIColor? + var customDisabledTextColor: UIColor? + //default error message + var errMessage: String? + var editCompleteAction: ((_ text: String?) -> Void)? + + + private var customPlaceHolderColor: UIColor? + @IBOutlet private weak var separatorHeightConstraint: NSLayoutConstraint! + private var borderPath: UIBezierPath? + private var calendar: Calendar? + @IBOutlet private weak var textContainerLeftPin: NSLayoutConstraint! + @IBOutlet private weak var errorLableLeftPin: NSLayoutConstraint! + @IBOutlet private weak var formLabelLeftPin: NSLayoutConstraint! + @IBOutlet private weak var textContainerRightPin: NSLayoutConstraint! + @IBOutlet private weak var errorLableRightPin: NSLayoutConstraint! + + // MARK: - setup + func updateView(_ size: CGFloat) { + super.updateView(size) + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.formLabel.updateView(size) + self.label.font = MFStyler.fontForTextFieldUnderLabel() + MFStyler.styleTextField(self.textField) + self.dashLine.updateView(size) + }) + } + + func setupView() { + if !self.view { + backgroundColor = UIColor.clear + let nib = getNib() + let views = nib?.instantiate(withOwner: self, options: nil) + let view = views?.first as? UIView + view?.translatesAutoresizingMaskIntoConstraints = false + view?.setContentHuggingPriority(.required, for: .vertical) + view?.setContentCompressionResistancePriority(.required, for: .vertical) + self.view = view + view?.frame = frame + if let view = view { + addSubview(view) + } + pinView(toSuperView: view) + + textField.font = MFStyler.fontForTextField() + translatesAutoresizingMaskIntoConstraints = false + + formLabel.font = MFStyler.fontB3() + formLabel.textColor = UIColor.mfBattleshipGrey() + + label.font = MFStyler.fontForTextFieldUnderLabel() + label.textColor = UIColor.black + + dropDownCarrotLabel.hidden = true + separatorView.backgroundColor = UIColor.black + dashLine.backgroundColor = UIColor.white + dashLine.hidden = true + MFStyler.styleTextField(textField) + + dropDownCarrotLabel.isUserInteractionEnabled = true + let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCarrotLabel.addGestureRecognizer(tapOnCarrot) + enabled = true + + // Disable SmartQuotes + if #available(iOS 11.0, *) { + textField.smartQuotesType = .no + textField.smartDashesType = .no + textField.smartInsertDeleteType = .no + } + } + } + + func getNib() -> UINib? { + return UINib(nibName: NSStringFromClass(type(of: self).self), bundle: MVMCoreUIUtility.bundleForMVMCoreUI()) + } + + class func mfTextField() -> Self? { + let view = self.init() + view?.hasDropDown = false + return view + } + + class func mfTextField(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + let textField = self.mfTextField() + textField?.bothTextFieldDelegates = delegate + return textField + } + + class func mfTextField(withMap map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + let textField = self.mfTextField() + textField?.translatesAutoresizingMaskIntoConstraints = false + textField?.setWithMap(map, bothDelegates: delegate) + return textField + } + + class func mfTextFieldForDropDown() -> Self? { + let textField = self.mfTextField() + textField?.dropDownCarrotLabel().hidden = false + textField?.hasDropDown = true + return textField + } + + class func mfTextFieldForDropDown(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + let textField = self.mfTextFieldForDropDown() + textField?.bothTextFieldDelegates = delegate + return textField + } + + // MARK: - Utilities + func createDatePicker() { + //tool bar + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) + + //date picker + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + + let calendar = Calendar.current + calendar.timeZone = NSTimeZone.system + self.calendar = calendar + } + + func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { + createDatePicker() + if show { + if let fromDate = fromDate { + if 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 + } + + func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) { + if let fromDate = fromDate { + if 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 + datePicker.timeZone = NSTimeZone.system + } + + func dismissDatePicker() -> Date? { + let pickedDate = datePicker.date + if let pickedDate = pickedDate { + if calendar.isDate(pickedDate, inSameDayAs: Date()) { + text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") + } else { + self.text = formatter.string(from: pickedDate) + } + } + textField.resignFirstResponder() + return pickedDate + } + + func dismissPicker() { + textField.resignFirstResponder() + } + + func setErrorMessage(_ errorMessage: String?) { + + MVMCoreDispatchUtility.performBlock(onMainThread: { + if self.enabled == true { + self.separatorHeightConstraint.constant = 4 + self.errorShowing = true + self.separatorView.backgroundColor = UIColor.mfPumpkin() + self.label.text() = errorMessage + self.label.numberOfLines = 0 + self.textField.accessibilityValue() = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message"), self.textField.text() ?? "", errorMessage ?? "") + self.setNeedsDisplay() + self.layoutIfNeeded() + } + }) + } + + func pushAccessibilityNotification() { + MVMCoreDispatchUtility.performBlock(onMainThread: { + UIAccessibilityPostNotification(UIAccessibility.Notification.layoutChanged, self.textField) + }) + } + + func hideError() { + + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.separatorHeightConstraint.constant = 1 + self.separatorView.backgroundColor = UIColor.black + self.layoutIfNeeded() + self.errorShowing = false + self.label.textColor = UIColor.black + self.label.text() = "" + self.textField.accessibilityValue() = nil + self.setNeedsDisplay() + self.layoutIfNeeded() + }) + } + + func placeholder() -> String? { + return textField.attributedPlaceholder.string + } + + func text() -> String? { + return textField.text() + } + + // MARK: - Setters + + func setPlaceholder(_ placeholder: String?, with color: UIColor?) { + customPlaceHolderColor = color + // fixed crash issue + if placeholder != nil { + if let color = color { + textField.attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: [ + NSAttributedString.Key.foregroundColor: color + ]) + } + } + if textField.text.length > 0 && !errorShowing { + label.text = placeholder + } else if !errorShowing { + label.text = "" + } + setAccessibilityString(placeholder) + } + + func setAccessibilityString(_ accessibilityString: String?) { + var accessibilityString = accessibilityString + // adding missing accessibilityLabel value + // if we have some value in accessibilityLabel, + // then only can append regular and picker item + if hasDropDown { + // MFDLog(@"Label: %@", self.textField.accessibilityLabel); + accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item")) + // MFDLog(@"Label: %@", self.textField.accessibilityLabel); + } else { + accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular")) + // MFDLog(@"Label: %@", self.textField.accessibilityLabel); + } + + textField.accessibilityLabel() = "\(accessibilityString ?? "") \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state"))" + } + + func setFormText(_ formText: String?) { + formLabel.text = formText + setAccessibilityString(formText) + } + + func setPlaceholder(_ placeholder: String?) { + setPlaceholder(placeholder, with: customPlaceHolderColor ?? UIColor.black) + } + + func setText(_ text: String?) { + textField.text = text + valueChanged() + } + + func setMfTextFieldDelegate(_ mfTextFieldDelegate: MFTextFieldDelegate?) { + self.mfTextFieldDelegate = mfTextFieldDelegate + 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) + } + } + + func setUiTextFieldDelegate(_ uiTextFieldDelegate: UITextFieldDelegate?) { + self.uiTextFieldDelegate = uiTextFieldDelegate + textField.delegate = uiTextFieldDelegate + } + + func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + mfTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + + func setWithMap(_ map: [AnyHashable : Any]?) { + if map?.count == 0 { + return + } + + var string = map?.string(KeyLabel) + if (string?.count ?? 0) > 0 { + formText = string + } + string = map?.string(KeyValue) + if (string?.count ?? 0) > 0 { + text = string + } + string = map?.string(forKey: KeyDisable) + if string?.isEqual(StringY) ?? false || map?.bool(forKey: KeyDisable) != nil { + enable(false) + } + string = map?.string(KeyErrorMessage) + if (string?.count ?? 0) > 0 { + errMessage = string + } + + // key used to send text value to server + string = map?.string(KeyFieldKey) + if (string?.count ?? 0) > 0 { + fieldKey = string + } + + string = map?.string(KeyType) + if (string == "dropDown") { + dropDownCarrotLabel().hidden = false + self.hasDropDown = true + } else if (string == "password") { + textField.isSecureTextEntry = true + } else if (string == "number") { + textField.keyboardType = .numberPad + } else if (string == "email") { + textField.keyboardType = .emailAddress + } + + string = map?.string("regex") + if (string?.count ?? 0) != 0 { + validationBlock = { enteredValue in + return MVMCoreUIUtility.validate(enteredValue, withRegularExpression: string) + } + } else { + setDefaultValidationBlock() + } + } + + func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) + self.bothTextFieldDelegates = delegate + setWithMap(map) + } + + func setValidationBlock(_ validationBlock: @escaping (String?) -> Bool) { + self.validationBlock = validationBlock + valueChanged() + } + + func setDefaultValidationBlock() { + self.validationBlock = { enteredValue in + if (enteredValue?.count ?? 0) > 0 { + return true + } else { + return false + } + } + } + + func setLeftPinConstant(_ constant: CGFloat) { + textContainerLeftPin.constant = constant + errorLableLeftPin.constant = constant + formLabelLeftPin.constant = constant + } + + func setRightPinConstant(_ constant: CGFloat) { + textContainerRightPin.constant = constant + errorLableRightPin.constant = constant + formLabelRightPin.constant = constant + } + + deinit { + self.bothTextFieldDelegates = nil + } + + // MARK: - XIB Helpers + func showDropDown(_ show: Bool) { + if hasDropDown { + dropDownCarrotLabel.hidden = !show + dropDownCarrotWidth.active = !show + setNeedsLayout() + layoutIfNeeded() + } + } + + func enable(_ enable: Bool) { + enabled = enable //Set outside the dispatch so that registerAnimations can know about it + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.isUserInteractionEnabled = enable + self.textField.userInteractionEnabled = enable + self.textField.isEnabled = enable + if enable { + self.textField.textColor = self.customEnabledTextColor ?? UIColor.black + self.formLabel.textColor = UIColor.mfBattleshipGrey() + self.label.textColor = UIColor.black + if self.errorShowing { + self.separatorView.backgroundColor = UIColor.mfPumpkin() + } else { + self.separatorView.backgroundColor = UIColor.black + } + self.showDropDown(true) + } else { + self.textField.textColor = self.customDisabledTextColor ?? UIColor.mfSilver() + self.formLabel.textColor = UIColor.mfSilver() + self.label.textColor = UIColor.mfSilver() + self.showDropDown(false) + self.hideError() //should not have error if the field is disabled + self.separatorView.backgroundColor = UIColor.mfSilver() + } + }) + } + + func showLabel(_ show: Bool) { + label.hidden = !show + } + + func dashSeperatorView(_ dash: Bool) { + if dash { + dashLine.hidden = false + //never hide seperator view because it could be possiblely used by other classes for positioning + separatorView.backgroundColor = UIColor.clear + } else { + dashLine.hidden = true + separatorView.backgroundColor = UIColor.black + } + } + + // MARK: - Observing for change + func validateBlock() { + valueChanged() + } + + func valueChanged() { + + // update label for placeholder + if !errorShowing { + label.text = "" + } + + // Check validity. + let previousValidity = valid + if validationBlock { + valid = validationBlock(text) + } else { + //if validation not set, input will always be valid + valid = true + } + + if previousValidity && !valid { + if errMessage { + self.errorMessage = errMessage + } + if mfTextFieldDelegate.responds(to: #selector(entryIsInvalid(_:))) { + mfTextFieldDelegate.entryIsInvalid(self) + } + } else if !previousValidity && valid { + hideError() + if mfTextFieldDelegate.responds(to: #selector(entryIsValid(_:))) { + mfTextFieldDelegate.entryIsValid(self) + } + } + } + + func endInputing() { + if isValid { + hideError() + separatorView.backgroundColor = UIColor.black + } else { + if errMessage { + self.errorMessage = errMessage + } + } + } + + // MARK: - helper + + func startEditing() { + textField.becomeFirstResponder() + if !errorShowing { + separatorView.backgroundColor = UIColor.black + separatorHeightConstraint.constant = 1 + } + } + + class func getEnabledTextfields(_ textFieldToDetermine: [MFTextField]?) -> [AnyHashable]? { + var enabledTextFields: [AnyHashable] = [] + for textfield in textFieldToDetermine ?? [] { + if textfield.isEnabled { + enabledTextFields.append(textfield) + } + } + return enabledTextFields + } + + //#pragma mark - Accessibility + + + func formatter() -> DateFormatter? { + if !formatter { + formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = NSTimeZone.system + formatter.locale = NSLocale.current + formatter.formatterBehavior = .default + } + return formatter + } + + 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 + + var strokeColor: UIColor? + if errorShowing { + strokeColor = UIColor.mfPumpkin() + } else { + strokeColor = UIColor.mfSilver() + } + + strokeColor?.setStroke() + + borderPath.stroke() + } + } + + ////////////////////// + + #pragma mark - MVMCoreUIMoleculeViewProtocol + + - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { + if ([delegateObject isKindOfClass:[MVMCoreUIDelegateObject class]]) { + [FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol]; + FormValidator *formValidator = [FormValidator getFormValidatorForDelegate:delegateObject.formValidationProtocol]; + + [self setWithMap:json]; + self.mfTextFieldDelegate = formValidator; + self.uiTextFieldDelegate = delegateObject.uiTextFieldDelegate; + [MVMCoreUICommonViewsUtility addDismissToolbar:self.textField delegate:self.uiTextFieldDelegate]; + } + } + + + + (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject { + return 76; + } + + #pragma mark - FormValidationProtocol + + + - (BOOL)isValidField { + return self.isValid; + } + + - (nullable NSString *)formFieldName { + return self.fieldKey; + } + + - (nullable id)formFieldValue { + return self.text; + } +} From d54c63b0197c3df058a0e4de5f8a6431b622a61c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 4 Oct 2019 11:39:37 -0400 Subject: [PATCH 002/112] beginning to convert the code. --- MVMCoreUI/Atoms/TextFields/TextField.swift | 541 ++++++++++++--------- 1 file changed, 316 insertions(+), 225 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index 0205b76a..4df53197 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -6,85 +6,179 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import MVMCoreUI import UIKit -@objc protocol MFTextFieldDelegate: NSObjectProtocol { - // Called when the entered text becomes valid based on the validation block - @objc optional func entryIsValid(_ textfield: MFTextField?) - // Called when the entered text becomes invalid based on the validation block - @objc optional func entryIsInvalid(_ textfield: MFTextField?) - // Dismisses the keyboard. + +@objc protocol TextFieldDelegate: NSObjectProtocol { + /// Called when the entered text becomes valid based on the validation block + @objc optional func entryIsValid(_ textfield: TextField?) + /// Called when the entered text becomes invalid based on the validation block + @objc optional func entryIsInvalid(_ textfield: TextField?) + /// Dismisses the keyboard. @objc optional func dismissFieldInput(_ sender: Any?) } -class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormValidationProtocol { - weak var view: UIView? - @IBOutlet weak var textFieldContainerView: UIView? - @IBOutlet weak var backgroundView: UIView? - @IBOutlet weak var textField: UITextField? - @IBOutlet weak var formLabel: Label? - @IBOutlet weak var separatorView: UIView? - /*make it public so outsider class can know the posistion of it. */ @IBOutlet weak var heightConstraint: NSLayoutConstraint? - @IBOutlet weak var formLabelRightPin: NSLayoutConstraint? - var enabled = false - // To set the placeholder and text +@objcMembers open class TextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormValidationProtocol { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + var textFieldContainerView: UIView? + var backgroundView: UIView? + var textField: UITextField? + var formLabel: Label? + var separatorView: UIView? + var view: UIView? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + weak var text: String? weak var formText: String? weak var fieldKey: String? weak var placeholder: String? - /* will move out in Feb release */ var hideBorder = false - weak var datePicker: UIDatePicker? - private(set) weak var toolbar: UIToolbar? - //helper - var formatter: DateFormatter? // If you're using a MFViewController, you must set this to it weak var uiTextFieldDelegate: UITextFieldDelegate? // 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. weak var mfTextFieldDelegate: MFTextFieldDelegate? + + /*make it public so outsider class can know the posistion of it. */ + + var enabled = false + // To set the placeholder and text + + /* will move out in Feb release */ + var hideBorder = false + + weak var datePicker: UIDatePicker? + private(set) weak var toolbar: UIToolbar? + + //helper + var formatter: DateFormatter? + var valid = false var validationBlock: ((_ enteredValue: String?) -> Bool)? // custom text colors var customEnabledTextColor: UIColor? var customDisabledTextColor: UIColor? + //default error message var errMessage: String? - var editCompleteAction: ((_ text: String?) -> Void)? + var editCompleteAction: ((_ text: String?) -> ())? - - private var customPlaceHolderColor: UIColor? - @IBOutlet private weak var separatorHeightConstraint: NSLayoutConstraint! - private var borderPath: UIBezierPath? - private var calendar: Calendar? - @IBOutlet private weak var textContainerLeftPin: NSLayoutConstraint! - @IBOutlet private weak var errorLableLeftPin: NSLayoutConstraint! - @IBOutlet private weak var formLabelLeftPin: NSLayoutConstraint! - @IBOutlet private weak var textContainerRightPin: NSLayoutConstraint! - @IBOutlet private weak var errorLableRightPin: NSLayoutConstraint! + private var customPlaceHolderColor: UIColor? + private var borderPath: UIBezierPath? + private var calendar: Calendar? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + var textContainerLeftPin: NSLayoutConstraint? + var errorLableLeftPin: NSLayoutConstraint? + var formLabelLeftPin: NSLayoutConstraint? + var textContainerRightPin: NSLayoutConstraint? + var errorLableRightPin: NSLayoutConstraint? + var separatorHeightConstraint: NSLayoutConstraint? + var heightConstraint: NSLayoutConstraint? + var formLabelRightPin: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializations + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + public convenience init() { + self.init(frame: .zero) + } + + /* + public convenience init() { + self.init(frame: .zero) + dropDownCarrotLabel().hidden = false + hasDropDown = false + } + + public convenience init(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + self.init(frame: .zero) + bothTextFieldDelegates = delegate + } + + public convenience init(withMap map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + self.init(frame: .zero) + translatesAutoresizingMaskIntoConstraints = false + setWithMap(map, bothDelegates: delegate) + } + + public convenience init(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + self.init(frame: .zero) + bothTextFieldDelegates = delegate + } + + */ + + class func mfTextField() -> Self? { - // MARK: - setup - func updateView(_ size: CGFloat) { - super.updateView(size) - MVMCoreDispatchUtility.performBlock(onMainThread: { - self.formLabel.updateView(size) - self.label.font = MFStyler.fontForTextFieldUnderLabel() - MFStyler.styleTextField(self.textField) - self.dashLine.updateView(size) - }) - } + let view = self.init() + view?.hasDropDown = false + return view + } - func setupView() { - if !self.view { - backgroundColor = UIColor.clear - let nib = getNib() - let views = nib?.instantiate(withOwner: self, options: nil) + class func mfTextField(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + + let textField = self.mfTextField() + textField?.bothTextFieldDelegates = delegate + return textField + } + + class func mfTextField(withMap map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + + let textField = self.mfTextField() + textField?.translatesAutoresizingMaskIntoConstraints = false + textField?.setWithMap(map, bothDelegates: delegate) + return textField + } + + class func mfTextFieldForDropDown() -> Self? { + + let textField = self.mfTextField() + textField?.dropDownCarrotLabel().hidden = false + textField?.hasDropDown = true + return textField + } + + class func mfTextFieldForDropDown(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + + let textField = self.mfTextFieldForDropDown() + textField?.bothTextFieldDelegates = delegate + return textField + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + override open func setupView() { + + guard subviews.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + + + backgroundColor = .clear + let view = views?.first as? UIView view?.translatesAutoresizingMaskIntoConstraints = false view?.setContentHuggingPriority(.required, for: .vertical) view?.setContentCompressionResistancePriority(.required, for: .vertical) self.view = view view?.frame = frame + if let view = view { addSubview(view) } @@ -116,54 +210,65 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali textField.smartDashesType = .no textField.smartInsertDeleteType = .no } + } + + func updateView(_ size: CGFloat) { + + super.updateView(size) + DispatchQueue.main.async { [weak self] in + self?.formLabel?.updateView(size) + self?.label.font = MFStyler.fontForTextFieldUnderLabel() + let textField = self?.textField { + MFStyler.styleTextField(textField) + } + self?.dashLine.updateView(size) } } - func getNib() -> UINib? { - return UINib(nibName: NSStringFromClass(type(of: self).self), bundle: MVMCoreUIUtility.bundleForMVMCoreUI()) + deinit { + mfTextFieldDelegate = nil + uiTextFieldDelegate = nil } - class func mfTextField() -> Self? { - let view = self.init() - view?.hasDropDown = false - return view + 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 + + var strokeColor: UIColor? + if errorShowing { + strokeColor = UIColor.mfPumpkin() + } else { + strokeColor = UIColor.mfSilver() + } + + strokeColor?.setStroke() + + borderPath?.stroke() + } } - class func mfTextField(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { - let textField = self.mfTextField() - textField?.bothTextFieldDelegates = delegate - return textField - } - - class func mfTextField(withMap map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { - let textField = self.mfTextField() - textField?.translatesAutoresizingMaskIntoConstraints = false - textField?.setWithMap(map, bothDelegates: delegate) - return textField - } - - class func mfTextFieldForDropDown() -> Self? { - let textField = self.mfTextField() - textField?.dropDownCarrotLabel().hidden = false - textField?.hasDropDown = true - return textField - } - - class func mfTextFieldForDropDown(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { - let textField = self.mfTextFieldForDropDown() - textField?.bothTextFieldDelegates = delegate - return textField - } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- // MARK: - Utilities func createDatePicker() { //tool bar - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) + MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: textField!.delegate) //date picker - datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField!) - let calendar = Calendar.current + var calendar = Calendar.current calendar.timeZone = NSTimeZone.system self.calendar = calendar } @@ -172,32 +277,32 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali createDatePicker() if show { if let fromDate = fromDate { - if calendar.isDate(fromDate, inSameDayAs: Date()) { + if (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 + datePicker?.minimumDate = fromDate + datePicker?.maximumDate = toDate } func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) { if let fromDate = fromDate { - if calendar.isDate(fromDate, inSameDayAs: Date()) { + if (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 - datePicker.timeZone = NSTimeZone.system + datePicker?.minimumDate = fromDate + datePicker?.maximumDate = toDate + datePicker?.timeZone = NSTimeZone.system } func dismissDatePicker() -> Date? { - let pickedDate = datePicker.date + let pickedDate = datePicker!.date if let pickedDate = pickedDate { if calendar.isDate(pickedDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") @@ -205,49 +310,49 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali self.text = formatter.string(from: pickedDate) } } - textField.resignFirstResponder() + textField?.resignFirstResponder() return pickedDate } func dismissPicker() { - textField.resignFirstResponder() + textField?.resignFirstResponder() } func setErrorMessage(_ errorMessage: String?) { - MVMCoreDispatchUtility.performBlock(onMainThread: { - if self.enabled == true { - self.separatorHeightConstraint.constant = 4 - self.errorShowing = true - self.separatorView.backgroundColor = UIColor.mfPumpkin() - self.label.text() = errorMessage - self.label.numberOfLines = 0 - self.textField.accessibilityValue() = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message"), self.textField.text() ?? "", errorMessage ?? "") - self.setNeedsDisplay() - self.layoutIfNeeded() + DispatchQueue.main.async { [weak self] in + if let enabled = self?.enabled { + self?.separatorHeightConstraint?.constant = 4 + self?.errorShowing = true + self?.separatorView?.backgroundColor = UIColor.mfPumpkin() + self?.label.text() = errorMessage + self?.label.numberOfLines = 0 + self?.textField.accessibilityValue() = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message"), self.textField.text() ?? "", errorMessage ?? "") + self?.setNeedsDisplay() + self?.layoutIfNeeded() } - }) + } } func pushAccessibilityNotification() { - MVMCoreDispatchUtility.performBlock(onMainThread: { - UIAccessibilityPostNotification(UIAccessibility.Notification.layoutChanged, self.textField) - }) + DispatchQueue.main.async { [weak self] in + UIAccessibilityPostNotification(UIAccessibility.Notification.layoutChanged, self?.textField) + } } func hideError() { - MVMCoreDispatchUtility.performBlock(onMainThread: { - self.separatorHeightConstraint.constant = 1 - self.separatorView.backgroundColor = UIColor.black - self.layoutIfNeeded() - self.errorShowing = false - self.label.textColor = UIColor.black - self.label.text() = "" - self.textField.accessibilityValue() = nil - self.setNeedsDisplay() - self.layoutIfNeeded() - }) + DispatchQueue.main.async { [weak self] in + self?.separatorHeightConstraint!.constant = 1 + self?.separatorView?.backgroundColor = UIColor.black + self?.layoutIfNeeded() + self?.errorShowing = false + self?.label.textColor = UIColor.black + self?.label.text() = "" + self?.textField?.accessibilityValue = nil + self?.setNeedsDisplay() + self?.layoutIfNeeded() + } } func placeholder() -> String? { @@ -265,7 +370,7 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali // fixed crash issue if placeholder != nil { if let color = color { - textField.attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: [ + textField!.attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: [ NSAttributedString.Key.foregroundColor: color ]) } @@ -285,18 +390,18 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali // then only can append regular and picker item if hasDropDown { // MFDLog(@"Label: %@", self.textField.accessibilityLabel); - accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item")) + accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item")!) // MFDLog(@"Label: %@", self.textField.accessibilityLabel); } else { - accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular")) + accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular")!) // MFDLog(@"Label: %@", self.textField.accessibilityLabel); } - textField.accessibilityLabel() = "\(accessibilityString ?? "") \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state"))" + textField!.accessibilityLabel() = "\(accessibilityString ?? "") \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state"))" } func setFormText(_ formText: String?) { - formLabel.text = formText + formLabel?.text = formText setAccessibilityString(formText) } @@ -305,7 +410,7 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali } func setText(_ text: String?) { - textField.text = text + textField?.text = text valueChanged() } @@ -326,31 +431,36 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali func setUiTextFieldDelegate(_ uiTextFieldDelegate: UITextFieldDelegate?) { self.uiTextFieldDelegate = uiTextFieldDelegate - textField.delegate = uiTextFieldDelegate + textField?.delegate = uiTextFieldDelegate } func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { mfTextFieldDelegate = delegate uiTextFieldDelegate = delegate } - - func setWithMap(_ map: [AnyHashable : Any]?) { - if map?.count == 0 { - return - } + + func setWithMap(_ map: [AnyHashable: Any]?) { + + guard let map = map, !map.isEmpty else { return } + + var string = map.string(KeyLabel) - var string = map?.string(KeyLabel) if (string?.count ?? 0) > 0 { formText = string } + string = map?.string(KeyValue) + if (string?.count ?? 0) > 0 { text = string } + string = map?.string(forKey: KeyDisable) + if string?.isEqual(StringY) ?? false || map?.bool(forKey: KeyDisable) != nil { enable(false) } + string = map?.string(KeyErrorMessage) if (string?.count ?? 0) > 0 { errMessage = string @@ -366,12 +476,15 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali if (string == "dropDown") { dropDownCarrotLabel().hidden = false self.hasDropDown = true + } else if (string == "password") { - textField.isSecureTextEntry = true + textField?.isSecureTextEntry = true + } else if (string == "number") { - textField.keyboardType = .numberPad + textField?.keyboardType = .numberPad + } else if (string == "email") { - textField.keyboardType = .emailAddress + textField?.keyboardType = .emailAddress } string = map?.string("regex") @@ -385,7 +498,7 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali } func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) + MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: delegate) self.bothTextFieldDelegates = delegate setWithMap(map) } @@ -405,23 +518,23 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali } } - func setLeftPinConstant(_ constant: CGFloat) { - textContainerLeftPin.constant = constant - errorLableLeftPin.constant = constant - formLabelLeftPin.constant = constant + override func setLeftPinConstant(_ constant: CGFloat) { + textContainerLeftPin?.constant = constant + errorLableLeftPin?.constant = constant + formLabelLeftPin?.constant = constant } func setRightPinConstant(_ constant: CGFloat) { - textContainerRightPin.constant = constant - errorLableRightPin.constant = constant - formLabelRightPin.constant = constant + textContainerRightPin?.constant = constant + errorLableRightPin?.constant = constant + formLabelRightPin?.constant = constant } - deinit { - self.bothTextFieldDelegates = nil - } + + //-------------------------------------------------- // MARK: - XIB Helpers + //-------------------------------------------------- func showDropDown(_ show: Bool) { if hasDropDown { dropDownCarrotLabel.hidden = !show @@ -433,47 +546,50 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali func enable(_ enable: Bool) { enabled = enable //Set outside the dispatch so that registerAnimations can know about it - MVMCoreDispatchUtility.performBlock(onMainThread: { - self.isUserInteractionEnabled = enable - self.textField.userInteractionEnabled = enable - self.textField.isEnabled = enable + DispatchQueue.main.async { [weak self] in + self?.isUserInteractionEnabled = enable + self?.textField.userInteractionEnabled = enable + self?.textField.isEnabled = enable if enable { - self.textField.textColor = self.customEnabledTextColor ?? UIColor.black - self.formLabel.textColor = UIColor.mfBattleshipGrey() - self.label.textColor = UIColor.black - if self.errorShowing { - self.separatorView.backgroundColor = UIColor.mfPumpkin() + self?.textField.textColor = self.customEnabledTextColor ?? .black + self?.formLabel.textColor = UIColor.mfBattleshipGrey() + self?.label.textColor = UIColor.black + if self!.errorShowing { + self?.separatorView.backgroundColor = UIColor.mfPumpkin() } else { - self.separatorView.backgroundColor = UIColor.black + self?.separatorView.backgroundColor = .black } - self.showDropDown(true) + self?.showDropDown(true) } else { - self.textField.textColor = self.customDisabledTextColor ?? UIColor.mfSilver() - self.formLabel.textColor = UIColor.mfSilver() - self.label.textColor = UIColor.mfSilver() - self.showDropDown(false) - self.hideError() //should not have error if the field is disabled - self.separatorView.backgroundColor = UIColor.mfSilver() + self?.textField.textColor = self!.customDisabledTextColor ?? UIColor.mfSilver() + self?.formLabel.textColor = UIColor.mfSilver() + self?.label.textColor = UIColor.mfSilver() + self?.showDropDown(false) + self?.hideError() //should not have error if the field is disabled + self?.separatorView.backgroundColor = UIColor.mfSilver() } - }) + } } func showLabel(_ show: Bool) { - label.hidden = !show + label?.hidden = !show } func dashSeperatorView(_ dash: Bool) { if dash { - dashLine.hidden = false + dashLine?.hidden = false //never hide seperator view because it could be possiblely used by other classes for positioning - separatorView.backgroundColor = UIColor.clear + separatorView?.backgroundColor = .clear } else { - dashLine.hidden = true - separatorView.backgroundColor = UIColor.black + dashLine?.hidden = true + separatorView.backgroundColor = .black } } + //-------------------------------------------------- // MARK: - Observing for change + //-------------------------------------------------- + func validateBlock() { valueChanged() } @@ -482,7 +598,7 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali // update label for placeholder if !errorShowing { - label.text = "" + label?.text = "" } // Check validity. @@ -499,7 +615,7 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali self.errorMessage = errMessage } if mfTextFieldDelegate.responds(to: #selector(entryIsInvalid(_:))) { - mfTextFieldDelegate.entryIsInvalid(self) + mfTextFieldDelegate?.entryIsInvalid(self) } } else if !previousValidity && valid { hideError() @@ -512,7 +628,7 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali func endInputing() { if isValid { hideError() - separatorView.backgroundColor = UIColor.black + separatorView.backgroundColor = .black } else { if errMessage { self.errorMessage = errMessage @@ -520,12 +636,10 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali } } - // MARK: - helper - func startEditing() { textField.becomeFirstResponder() if !errorShowing { - separatorView.backgroundColor = UIColor.black + separatorView?.backgroundColor = .black separatorHeightConstraint.constant = 1 } } @@ -540,8 +654,9 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali return enabledTextFields } - //#pragma mark - Accessibility - + //-------------------------------------------------- + // MARK: - Accessibility + //-------------------------------------------------- func formatter() -> DateFormatter? { if !formatter { @@ -554,65 +669,41 @@ class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormVali return formatter } - 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)) + + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + override open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + if (delegateObject is MVMCoreUIDelegateObject) { + FormValidator.setupValidation(withMolecule: self, delegate: delegateObject?.formValidationProtocol) + let formValidator = FormValidator.getForDelegate(delegateObject?.formValidationProtocol) - borderPath.lineWidth = 1 - - var strokeColor: UIColor? - if errorShowing { - strokeColor = UIColor.mfPumpkin() - } else { - strokeColor = UIColor.mfSilver() - } - - strokeColor?.setStroke() - - borderPath.stroke() + self.withMap = json + mfTextFieldDelegate = formValidator + uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) } } - ////////////////////// - - #pragma mark - MVMCoreUIMoleculeViewProtocol - - - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { - if ([delegateObject isKindOfClass:[MVMCoreUIDelegateObject class]]) { - [FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol]; - FormValidator *formValidator = [FormValidator getFormValidatorForDelegate:delegateObject.formValidationProtocol]; - - [self setWithMap:json]; - self.mfTextFieldDelegate = formValidator; - self.uiTextFieldDelegate = delegateObject.uiTextFieldDelegate; - [MVMCoreUICommonViewsUtility addDismissToolbar:self.textField delegate:self.uiTextFieldDelegate]; - } + override open class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 76 } + //-------------------------------------------------- + // MARK: - Form Validation + //-------------------------------------------------- - + (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject { - return 76; + public func isValidField() -> Bool { + return isValid } - #pragma mark - FormValidationProtocol - - - - (BOOL)isValidField { - return self.isValid; + public func formFieldName() -> String? { + return fieldKey } - - (nullable NSString *)formFieldName { - return self.fieldKey; - } - - - (nullable id)formFieldValue { - return self.text; + public func formFieldValue() -> Any? { + return text } } From d4df5745f439a5f4b75ac1e8a9d685d4053a55b5 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 11 Oct 2019 13:23:49 -0400 Subject: [PATCH 003/112] Further swiftification. --- MVMCoreUI/Atoms/TextFields/TextField.swift | 412 ++++++++++----------- 1 file changed, 198 insertions(+), 214 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index 4df53197..7fa50d44 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -18,7 +18,7 @@ import UIKit @objc optional func dismissFieldInput(_ sender: Any?) } -@objcMembers open class TextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormValidationProtocol { +@objcMembers open class TextField: ViewConstrainingView { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -30,41 +30,57 @@ import UIKit var separatorView: UIView? var view: UIView? + /// If you're using a MFViewController, you must set this to it + weak var mfTextFieldDelegate: MFTextFieldDelegate? + + // 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. + var uiTextFieldDelegate: UITextFieldDelegate? { + get { + return textField?.delegate + } + set (newValue) { + textField?.delegate = newValue + } + } + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - weak var text: String? - weak var formText: String? - weak var fieldKey: String? - weak var placeholder: String? + weak var label: UILabel? - // If you're using a MFViewController, you must set this to it - weak var uiTextFieldDelegate: UITextFieldDelegate? - // 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. - weak var mfTextFieldDelegate: MFTextFieldDelegate? + //accesories + weak var pickerView: UIPickerView? + + //utility + var observingForChanges = false + var errorShowing = false + var hasDropDown = false - /*make it public so outsider class can know the posistion of it. */ + var dashLine: DashLine? + var dropDownCarrotLabel: UILabel? + var dropDownCarrotWidth: NSLayoutConstraint? + + var text: String? + var formText: String? + var fieldKey: String? + var placeholder: String? var enabled = false - // To set the placeholder and text - - /* will move out in Feb release */ + var hideBorder = false weak var datePicker: UIDatePicker? private(set) weak var toolbar: UIToolbar? - //helper var formatter: DateFormatter? var valid = false var validationBlock: ((_ enteredValue: String?) -> Bool)? - // custom text colors + var customEnabledTextColor: UIColor? var customDisabledTextColor: UIColor? - //default error message var errMessage: String? var editCompleteAction: ((_ text: String?) -> ())? @@ -91,72 +107,47 @@ import UIKit public override init(frame: CGRect) { super.init(frame: frame) + + translatesAutoresizingMaskIntoConstraints = false + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") } public convenience init() { self.init(frame: .zero) + + hasDropDown = false + } + + public convenience init(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + self.init(frame: .zero) + + mfTextFieldDelegate = delegate + uiTextFieldDelegate = delegate } - /* - public convenience init() { - self.init(frame: .zero) - dropDownCarrotLabel().hidden = false - hasDropDown = false - } - - public convenience init(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - self.init(frame: .zero) - bothTextFieldDelegates = delegate - } - - public convenience init(withMap map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - self.init(frame: .zero) - translatesAutoresizingMaskIntoConstraints = false - setWithMap(map, bothDelegates: delegate) - } - - public convenience init(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - self.init(frame: .zero) - bothTextFieldDelegates = delegate - } - - */ - - class func mfTextField() -> Self? { + public convenience init(withMap map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + self.init(frame: .zero) - let view = self.init() - view?.hasDropDown = false - return view + setWithMap(map, bothDelegates: delegate) + } + + public convenience init(mfTextFieldForDropDown: Bool) { + self.init(frame: .zero) + + dropDownCarrotLabel?.isHidden = false + hasDropDown = true } - class func mfTextField(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + public convenience init(mfTextFieldForDropDownWithMap map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + self.init(frame: .zero) - let textField = self.mfTextField() - textField?.bothTextFieldDelegates = delegate - return textField - } - - class func mfTextField(withMap map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { - - let textField = self.mfTextField() - textField?.translatesAutoresizingMaskIntoConstraints = false - textField?.setWithMap(map, bothDelegates: delegate) - return textField - } - - class func mfTextFieldForDropDown() -> Self? { - - let textField = self.mfTextField() - textField?.dropDownCarrotLabel().hidden = false - textField?.hasDropDown = true - return textField - } - - class func mfTextFieldForDropDown(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { - - let textField = self.mfTextFieldForDropDown() - textField?.bothTextFieldDelegates = delegate - return textField + dropDownCarrotLabel?.isHidden = false + hasDropDown = true + setWithMap(map, bothDelegates: delegate) } //-------------------------------------------------- @@ -169,59 +160,59 @@ import UIKit translatesAutoresizingMaskIntoConstraints = false - - backgroundColor = .clear + backgroundColor = .clear - let view = views?.first as? UIView - view?.translatesAutoresizingMaskIntoConstraints = false - view?.setContentHuggingPriority(.required, for: .vertical) - view?.setContentCompressionResistancePriority(.required, for: .vertical) - self.view = view - view?.frame = frame - - if let view = view { - addSubview(view) - } - pinView(toSuperView: view) - - textField.font = MFStyler.fontForTextField() - translatesAutoresizingMaskIntoConstraints = false - - formLabel.font = MFStyler.fontB3() - formLabel.textColor = UIColor.mfBattleshipGrey() - - label.font = MFStyler.fontForTextFieldUnderLabel() - label.textColor = UIColor.black - - dropDownCarrotLabel.hidden = true - separatorView.backgroundColor = UIColor.black - dashLine.backgroundColor = UIColor.white - dashLine.hidden = true - MFStyler.styleTextField(textField) - - dropDownCarrotLabel.isUserInteractionEnabled = true - let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) - dropDownCarrotLabel.addGestureRecognizer(tapOnCarrot) - enabled = true - - // Disable SmartQuotes - if #available(iOS 11.0, *) { - textField.smartQuotesType = .no - textField.smartDashesType = .no - textField.smartInsertDeleteType = .no - } + let view = views?.first as? UIView + view?.translatesAutoresizingMaskIntoConstraints = false + view?.setContentHuggingPriority(.required, for: .vertical) + view?.setContentCompressionResistancePriority(.required, for: .vertical) + self.view = view + view?.frame = frame + + if let view = view { + addSubview(view) + } + pinView(toSuperView: view) + + textField?.font = MFStyler.fontForTextField() + translatesAutoresizingMaskIntoConstraints = false + + formLabel?.font = MFStyler.fontB3() + formLabel?.textColor = UIColor.mfBattleshipGrey() + + label?.font = MFStyler.fontForTextFieldUnderLabel() + label?.textColor = UIColor.black + + dropDownCarrotLabel?.isHidden = true + separatorView?.backgroundColor = UIColor.black + dashLine?.backgroundColor = UIColor.white + dashLine?.isHidden = true + MFStyler.styleTextField(textField!) + + dropDownCarrotLabel?.isUserInteractionEnabled = true + let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCarrotLabel?.addGestureRecognizer(tapOnCarrot) + enabled = true + + // Disable SmartQuotes + if #available(iOS 11.0, *) { + textField?.smartQuotesType = .no + textField?.smartDashesType = .no + textField?.smartInsertDeleteType = .no + } } - func updateView(_ size: CGFloat) { - + open override func updateView(_ size: CGFloat) { super.updateView(size) + DispatchQueue.main.async { [weak self] in + self?.formLabel?.updateView(size) - self?.label.font = MFStyler.fontForTextFieldUnderLabel() - let textField = self?.textField { - MFStyler.styleTextField(textField) - } - self?.dashLine.updateView(size) + self?.label?.font = MFStyler.fontForTextFieldUnderLabel() +// let textFieldView = { + MFStyler.styleTextField(self!.textField!) +// } + self?.dashLine?.updateView(size) } } @@ -230,28 +221,23 @@ import UIKit uiTextFieldDelegate = nil } - func draw(_ rect: CGRect) { + open override func draw(_ rect: CGRect) { super.draw(rect) - borderPath.removeAllPoints() + + 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)) + 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 - var strokeColor: UIColor? - if errorShowing { - strokeColor = UIColor.mfPumpkin() - } else { - strokeColor = UIColor.mfSilver() - } - - strokeColor?.setStroke() - + let strokeColor = errorShowing ? UIColor.mfPumpkin() : UIColor.mfSilver() + strokeColor.setStroke() borderPath?.stroke() } } @@ -260,6 +246,14 @@ import UIKit // MARK: - Methods //-------------------------------------------------- + func placeholder() -> String? { + return textField.attributedPlaceholder.string + } + + func text() -> String? { + return textField.text() + } + // MARK: - Utilities func createDatePicker() { //tool bar @@ -302,12 +296,12 @@ import UIKit } func dismissDatePicker() -> Date? { - let pickedDate = datePicker!.date - if let pickedDate = pickedDate { - if calendar.isDate(pickedDate, inSameDayAs: Date()) { + var pickedDate = datePicker!.date + if let pickedDatei = pickedDate { + if calendar.isDate(pickedDatei, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter.string(from: pickedDate) + self.text = formatter.string(from: pickedDatei) } } textField?.resignFirstResponder() @@ -325,9 +319,9 @@ import UIKit self?.separatorHeightConstraint?.constant = 4 self?.errorShowing = true self?.separatorView?.backgroundColor = UIColor.mfPumpkin() - self?.label.text() = errorMessage - self?.label.numberOfLines = 0 - self?.textField.accessibilityValue() = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message"), self.textField.text() ?? "", errorMessage ?? "") + self?.label?.text = errorMessage + self?.label?.numberOfLines = 0 + self?.textField?.accessibilityValue() = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message"), self.textField.text() ?? "", errorMessage ?? "") self?.setNeedsDisplay() self?.layoutIfNeeded() } @@ -336,7 +330,7 @@ import UIKit func pushAccessibilityNotification() { DispatchQueue.main.async { [weak self] in - UIAccessibilityPostNotification(UIAccessibility.Notification.layoutChanged, self?.textField) + UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: self?.textField) } } @@ -347,22 +341,14 @@ import UIKit self?.separatorView?.backgroundColor = UIColor.black self?.layoutIfNeeded() self?.errorShowing = false - self?.label.textColor = UIColor.black - self?.label.text() = "" + self?.label?.textColor = UIColor.black + self?.label?.text = "" self?.textField?.accessibilityValue = nil self?.setNeedsDisplay() self?.layoutIfNeeded() } } - func placeholder() -> String? { - return textField.attributedPlaceholder.string - } - - func text() -> String? { - return textField.text() - } - // MARK: - Setters func setPlaceholder(_ placeholder: String?, with color: UIColor?) { @@ -375,10 +361,10 @@ import UIKit ]) } } - if textField.text.length > 0 && !errorShowing { - label.text = placeholder + if textField?.text.length > 0 && !errorShowing { + label?.text = placeholder } else if !errorShowing { - label.text = "" + label?.text = "" } setAccessibilityString(placeholder) } @@ -430,7 +416,7 @@ import UIKit } func setUiTextFieldDelegate(_ uiTextFieldDelegate: UITextFieldDelegate?) { - self.uiTextFieldDelegate = uiTextFieldDelegate + uiTextFieldDelegate = uiTextFieldDelegate textField?.delegate = uiTextFieldDelegate } @@ -462,19 +448,22 @@ import UIKit } string = map?.string(KeyErrorMessage) + if (string?.count ?? 0) > 0 { errMessage = string } // key used to send text value to server string = map?.string(KeyFieldKey) + if (string?.count ?? 0) > 0 { fieldKey = string } string = map?.string(KeyType) + if (string == "dropDown") { - dropDownCarrotLabel().hidden = false + dropDownCarrotLabel?.isHidden = false self.hasDropDown = true } else if (string == "password") { @@ -488,6 +477,7 @@ import UIKit } string = map?.string("regex") + if (string?.count ?? 0) != 0 { validationBlock = { enteredValue in return MVMCoreUIUtility.validate(enteredValue, withRegularExpression: string) @@ -499,7 +489,8 @@ import UIKit func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: delegate) - self.bothTextFieldDelegates = delegate + uiTextFieldDelegate = delegate + textFieldDelegate = delegate setWithMap(map) } @@ -510,35 +501,29 @@ import UIKit func setDefaultValidationBlock() { self.validationBlock = { enteredValue in - if (enteredValue?.count ?? 0) > 0 { - return true - } else { - return false - } + return (enteredValue?.count ?? 0) > 0 } } - override func setLeftPinConstant(_ constant: CGFloat) { + open override func setLeftPinConstant(_ constant: CGFloat) { textContainerLeftPin?.constant = constant errorLableLeftPin?.constant = constant formLabelLeftPin?.constant = constant } - func setRightPinConstant(_ constant: CGFloat) { + open override func setRightPinConstant(_ constant: CGFloat) { textContainerRightPin?.constant = constant errorLableRightPin?.constant = constant formLabelRightPin?.constant = constant } - - //-------------------------------------------------- // MARK: - XIB Helpers //-------------------------------------------------- func showDropDown(_ show: Bool) { if hasDropDown { - dropDownCarrotLabel.hidden = !show - dropDownCarrotWidth.active = !show + dropDownCarrotLabel?.isHidden = !show + dropDownCarrotWidth?.isActive = !show setNeedsLayout() layoutIfNeeded() } @@ -548,41 +533,41 @@ import UIKit enabled = enable //Set outside the dispatch so that registerAnimations can know about it DispatchQueue.main.async { [weak self] in self?.isUserInteractionEnabled = enable - self?.textField.userInteractionEnabled = enable - self?.textField.isEnabled = enable + self?.textField?.isUserInteractionEnabled = enable + self?.textField?.isEnabled = enable if enable { - self?.textField.textColor = self.customEnabledTextColor ?? .black - self?.formLabel.textColor = UIColor.mfBattleshipGrey() - self?.label.textColor = UIColor.black + self?.textField?.textColor = self?.customEnabledTextColor ?? .black + self?.formLabel?.textColor = UIColor.mfBattleshipGrey() + self?.label?.textColor = UIColor.black if self!.errorShowing { - self?.separatorView.backgroundColor = UIColor.mfPumpkin() + self?.separatorView?.backgroundColor = UIColor.mfPumpkin() } else { - self?.separatorView.backgroundColor = .black + self?.separatorView?.backgroundColor = .black } self?.showDropDown(true) } else { - self?.textField.textColor = self!.customDisabledTextColor ?? UIColor.mfSilver() - self?.formLabel.textColor = UIColor.mfSilver() - self?.label.textColor = UIColor.mfSilver() + self?.textField?.textColor = self!.customDisabledTextColor ?? UIColor.mfSilver() + self?.formLabel?.textColor = UIColor.mfSilver() + self?.label?.textColor = UIColor.mfSilver() self?.showDropDown(false) self?.hideError() //should not have error if the field is disabled - self?.separatorView.backgroundColor = UIColor.mfSilver() + self?.separatorView?.backgroundColor = UIColor.mfSilver() } } } func showLabel(_ show: Bool) { - label?.hidden = !show + label?.isHidden = !show } func dashSeperatorView(_ dash: Bool) { if dash { - dashLine?.hidden = false + dashLine?.isHidden = false //never hide seperator view because it could be possiblely used by other classes for positioning separatorView?.backgroundColor = .clear } else { - dashLine?.hidden = true - separatorView.backgroundColor = .black + dashLine?.isHidden = true + separatorView?.backgroundColor = .black } } @@ -603,7 +588,7 @@ import UIKit // Check validity. let previousValidity = valid - if validationBlock { + if (validationBlock != nil) { valid = validationBlock(text) } else { //if validation not set, input will always be valid @@ -611,7 +596,7 @@ import UIKit } if previousValidity && !valid { - if errMessage { + if (errMessage != nil) { self.errorMessage = errMessage } if mfTextFieldDelegate.responds(to: #selector(entryIsInvalid(_:))) { @@ -653,29 +638,13 @@ import UIKit } return enabledTextFields } +} + +// MARK: - Molecular +extension TextField { - //-------------------------------------------------- - // MARK: - Accessibility - //-------------------------------------------------- - - func formatter() -> DateFormatter? { - if !formatter { - formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeZone = NSTimeZone.system - formatter.locale = NSLocale.current - formatter.formatterBehavior = .default - } - return formatter - } - - - - //-------------------------------------------------- - // MARK: - Molecular - //-------------------------------------------------- - - override open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + if (delegateObject is MVMCoreUIDelegateObject) { FormValidator.setupValidation(withMolecule: self, delegate: delegateObject?.formValidationProtocol) let formValidator = FormValidator.getForDelegate(delegateObject?.formValidationProtocol) @@ -683,17 +652,32 @@ import UIKit self.withMap = json mfTextFieldDelegate = formValidator uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) + MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: uiTextFieldDelegate) } } override open class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 76 } +} + +// MARK: - Accessibility +extension TextField { - //-------------------------------------------------- - // MARK: - Form Validation - //-------------------------------------------------- + func formatter() -> DateFormatter? { + if !formatter { + formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = .system + formatter.locale = .current + formatter.formatterBehavior = .default + } + return formatter + } +} + +// MARK: - Form Validation +extension TextField: FormValidationProtocol { public func isValidField() -> Bool { return isValid From 10692ee21ee79a1f2dda12fcefd8f376f19aab76 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Sun, 13 Oct 2019 12:00:36 -0400 Subject: [PATCH 004/112] more changes. --- MVMCoreUI/Atoms/TextFields/TextField.swift | 34 ++++++++++------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index 7fa50d44..09e170ca 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -9,7 +9,7 @@ import UIKit -@objc protocol TextFieldDelegate: NSObjectProtocol { +@objc public protocol MFTextFieldDelegate: NSObjectProtocol { /// Called when the entered text becomes valid based on the validation block @objc optional func entryIsValid(_ textfield: TextField?) /// Called when the entered text becomes invalid based on the validation block @@ -49,10 +49,8 @@ import UIKit weak var label: UILabel? - //accesories weak var pickerView: UIPickerView? - //utility var observingForChanges = false var errorShowing = false var hasDropDown = false @@ -338,10 +336,10 @@ import UIKit DispatchQueue.main.async { [weak self] in self?.separatorHeightConstraint!.constant = 1 - self?.separatorView?.backgroundColor = UIColor.black + self?.separatorView?.backgroundColor = .black self?.layoutIfNeeded() self?.errorShowing = false - self?.label?.textColor = UIColor.black + self?.label?.textColor = .black self?.label?.text = "" self?.textField?.accessibilityValue = nil self?.setNeedsDisplay() @@ -349,11 +347,9 @@ import UIKit } } - // MARK: - Setters - func setPlaceholder(_ placeholder: String?, with color: UIColor?) { customPlaceHolderColor = color - // fixed crash issue + if placeholder != nil { if let color = color { textField!.attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: [ @@ -600,12 +596,12 @@ import UIKit self.errorMessage = errMessage } if mfTextFieldDelegate.responds(to: #selector(entryIsInvalid(_:))) { - mfTextFieldDelegate?.entryIsInvalid(self) + mfTextFieldDelegate?.entryIsInvalid!(self) } } else if !previousValidity && valid { hideError() - if mfTextFieldDelegate.responds(to: #selector(entryIsValid(_:))) { - mfTextFieldDelegate.entryIsValid(self) + if mfTextFieldDelegate?.responds(to: #selector(entryIsValid(_:))) { + mfTextFieldDelegate?.entryIsValid!(self) } } } @@ -613,19 +609,19 @@ import UIKit func endInputing() { if isValid { hideError() - separatorView.backgroundColor = .black + separatorView!.backgroundColor = .black } else { - if errMessage { + if (errMessage != nil) { self.errorMessage = errMessage } } } func startEditing() { - textField.becomeFirstResponder() + textField?.becomeFirstResponder() if !errorShowing { separatorView?.backgroundColor = .black - separatorHeightConstraint.constant = 1 + separatorHeightConstraint?.constant = 1 } } @@ -645,13 +641,13 @@ extension TextField { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - if (delegateObject is MVMCoreUIDelegateObject) { - FormValidator.setupValidation(withMolecule: self, delegate: delegateObject?.formValidationProtocol) - let formValidator = FormValidator.getForDelegate(delegateObject?.formValidationProtocol) + if let delegateObject = delegateObject{ + FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol) + let formValidator = FormValidator.getForDelegate(delegateObject.formValidationProtocol) self.withMap = json mfTextFieldDelegate = formValidator - uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate + uiTextFieldDelegate = delegateObject.uiTextFieldDelegate MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: uiTextFieldDelegate) } } From 403f0b261b613b4b0a0def3af32c59e362de1a44 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 14 Oct 2019 15:24:48 -0400 Subject: [PATCH 005/112] Major swiftification of MFTextField to TextField. --- MVMCoreUI/Atoms/TextFields/TextField.swift | 741 ++++++++++-------- MVMCoreUI/Atoms/Views/DashLine.swift | 21 +- .../MVMCoreUIMoleculeMappingObject.m | 2 +- 3 files changed, 436 insertions(+), 328 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index 09e170ca..fb4867bf 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -9,7 +9,7 @@ import UIKit -@objc public protocol MFTextFieldDelegate: NSObjectProtocol { +@objc public protocol TextFieldDelegate: NSObjectProtocol { /// Called when the entered text becomes valid based on the validation block @objc optional func entryIsValid(_ textfield: TextField?) /// Called when the entered text becomes invalid based on the validation block @@ -23,66 +23,141 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - var textFieldContainerView: UIView? - var backgroundView: UIView? - var textField: UITextField? - var formLabel: Label? - var separatorView: UIView? - var view: UIView? + 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 view: UIView? - /// If you're using a MFViewController, you must set this to it - weak var mfTextFieldDelegate: MFTextFieldDelegate? - - // 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. - var uiTextFieldDelegate: UITextFieldDelegate? { - get { - return textField?.delegate - } - set (newValue) { - textField?.delegate = newValue - } - } + public var dashLine: DashLine? + public var dropDownCarrotLabel: UILabel? + public weak var datePicker: UIDatePicker? + private(set) weak var toolbar: UIToolbar? //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - weak var label: UILabel? + private var _mfTextFieldDelegate: TextFieldDelegate? + private var _uiTextFieldDelegate: UITextFieldDelegate? - weak var pickerView: UIPickerView? - - var observingForChanges = false - var errorShowing = false - var hasDropDown = false + /// 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? { + get { + return _mfTextFieldDelegate + } + set { + _mfTextFieldDelegate = newValue + 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) + } + } + } - var dashLine: DashLine? - var dropDownCarrotLabel: UILabel? - var dropDownCarrotWidth: NSLayoutConstraint? + // If you're using a MFViewController, you must set this to it + public weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { + return textField?.delegate + } + set { + _uiTextFieldDelegate = newValue + textField?.delegate = uiTextFieldDelegate + } + } - var text: String? - var formText: String? - var fieldKey: String? - var placeholder: String? + public weak var pickerView: UIPickerView? - var enabled = false - - var hideBorder = false + public var observingForChanges = false + public var errorShowing = false + public var hasDropDown = false - weak var datePicker: UIDatePicker? - private(set) weak var toolbar: UIToolbar? + public var text: String? { + get { + return textField?.text + } + set { + textField?.text = newValue + valueChanged() + } + } - var formatter: DateFormatter? + public var formText: String? { + get { + return formLabel?.text + } + set { + formLabel?.text = newValue + setAccessibilityString(newValue) + } + } - var valid = false - var validationBlock: ((_ enteredValue: String?) -> Bool)? - - var customEnabledTextColor: UIColor? - var customDisabledTextColor: UIColor? - var errMessage: String? - var editCompleteAction: ((_ text: String?) -> ())? + public var fieldKey: String? + + public var placeholderTextColor: UIColor = .black + + public var placeholder: String? { + get { + guard let attributedPlaceholder = textField?.attributedPlaceholder else { return nil } + return attributedPlaceholder.string + } + set { + guard let newPlaceholderText = newValue else { return } + + textField?.attributedPlaceholder = NSAttributedString(string: newPlaceholderText, attributes: [NSAttributedString.Key.foregroundColor: placeholderTextColor]) + + if !errorShowing { + placeholderErrorLabel?.text = (textField?.text?.count ?? 0) > 0 ? newPlaceholderText : "" + } + + setAccessibilityString(newPlaceholderText) + } + } + + public var enabled = false + + public var hideBorder = false + + 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 + + private var _validationBlock: ((_ enteredValue: String?) -> Bool)? + public var validationBlock: ((_ enteredValue: String?) -> Bool)? { + get { + return _validationBlock + } + set { + _validationBlock = newValue + valueChanged() + } + } + + public var customEnabledTextColor: UIColor? + public var customDisabledTextColor: UIColor? + + public var errMessage: String? + public var editCompleteAction: ((_ text: String?) -> ())? - private var customPlaceHolderColor: UIColor? private var borderPath: UIBezierPath? private var calendar: Calendar? @@ -90,23 +165,29 @@ import UIKit // MARK: - Constraints //-------------------------------------------------- - var textContainerLeftPin: NSLayoutConstraint? - var errorLableLeftPin: NSLayoutConstraint? - var formLabelLeftPin: NSLayoutConstraint? - var textContainerRightPin: NSLayoutConstraint? - var errorLableRightPin: NSLayoutConstraint? - var separatorHeightConstraint: NSLayoutConstraint? - var heightConstraint: NSLayoutConstraint? - var formLabelRightPin: NSLayoutConstraint? + 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: - Initializations + // MARK: - Initializers //-------------------------------------------------- public override init(frame: CGRect) { super.init(frame: frame) - translatesAutoresizingMaskIntoConstraints = false + setupView() } required public init?(coder: NSCoder) { @@ -116,40 +197,33 @@ import UIKit public convenience init() { self.init(frame: .zero) - + hasDropDown = false } - - public convenience init(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - self.init(frame: .zero) - - mfTextFieldDelegate = delegate - uiTextFieldDelegate = delegate - } - public convenience init(withMap map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + public convenience init(with bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { self.init(frame: .zero) - setWithMap(map, bothDelegates: delegate) + mfTextFieldDelegate = bothDelegates + uiTextFieldDelegate = bothDelegates } - - public convenience init(mfTextFieldForDropDown: Bool) { + + public convenience init(map: [AnyHashable: Any]?, with bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + self.init(frame: .zero) + + setWithMap(map, bothDelegates: bothDelegates) + } + + public convenience init(dropDownWithMap map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { self.init(frame: .zero) dropDownCarrotLabel?.isHidden = false hasDropDown = true - } - - public convenience init(mfTextFieldForDropDownWithMap map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - self.init(frame: .zero) - - dropDownCarrotLabel?.isHidden = false - hasDropDown = true - setWithMap(map, bothDelegates: delegate) + setWithMap(map, bothDelegates: bothDelegates) } //-------------------------------------------------- - // MARK: - Lifecycle + // MARK: - Setup //-------------------------------------------------- override open func setupView() { @@ -157,47 +231,124 @@ import UIKit guard subviews.isEmpty else { return } translatesAutoresizingMaskIntoConstraints = false - + setContentHuggingPriority(.required, for: .vertical) + setContentCompressionResistancePriority(.required, for: .vertical) backgroundColor = .clear - - let view = views?.first as? UIView - view?.translatesAutoresizingMaskIntoConstraints = false - view?.setContentHuggingPriority(.required, for: .vertical) - view?.setContentCompressionResistancePriority(.required, for: .vertical) - self.view = view - view?.frame = frame - - if let view = view { - addSubview(view) - } - pinView(toSuperView: view) - - textField?.font = MFStyler.fontForTextField() - translatesAutoresizingMaskIntoConstraints = false - - formLabel?.font = MFStyler.fontB3() - formLabel?.textColor = UIColor.mfBattleshipGrey() - - label?.font = MFStyler.fontForTextFieldUnderLabel() - label?.textColor = UIColor.black - - dropDownCarrotLabel?.isHidden = true - separatorView?.backgroundColor = UIColor.black - dashLine?.backgroundColor = UIColor.white - dashLine?.isHidden = true - MFStyler.styleTextField(textField!) - - dropDownCarrotLabel?.isUserInteractionEnabled = true - let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) - dropDownCarrotLabel?.addGestureRecognizer(tapOnCarrot) enabled = true - // Disable SmartQuotes - if #available(iOS 11.0, *) { - textField?.smartQuotesType = .no - textField?.smartDashesType = .no - textField?.smartInsertDeleteType = .no - } + let formLabel = Label() + self.formLabel = formLabel + formLabel.font = MFStyler.fontB3() + formLabel.textColor = UIColor.mfBattleshipGrey() + + 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) + constrainTextContainerContent() + + 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 + + addSubview(placeholderErrorLabel) + + placeholderErrorLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 15).isActive = true + 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 + } + + private func constrainTextContainerContent() { + + guard let parentView = textFieldContainerView else { return } + + 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.font = MFStyler.fontForTextField() + textField.smartQuotesType = .no + textField.smartDashesType = .no + textField.smartInsertDeleteType = .no + MFStyler.styleTextField(textField) + + addSubview(textField) + + textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10).isActive = true + textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16).isActive = true + parentView.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10).isActive = true + + let dropDownCarrotLabel = Label() + self.dropDownCarrotLabel = dropDownCarrotLabel + dropDownCarrotLabel.isHidden = true + dropDownCarrotLabel.isUserInteractionEnabled = true + let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCarrotLabel.addGestureRecognizer(tapOnCarrot) + + addSubview(dropDownCarrotLabel) + + textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10).isActive = true + textField.leadingAnchor.constraint(equalTo: textField.leadingAnchor, 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.backgroundColor = .white + dashLine.isHidden = true + + addSubview(dashLine) + + dashLine.centerYAnchor.constraint(equalTo: separatorView.centerYAnchor).isActive = true + dashLine.centerXAnchor.constraint(equalTo: separatorView.centerXAnchor).isActive = true + dashLine.topAnchor.constraint(equalTo: separatorView.topAnchor).isActive = true + dashLine.leadingAnchor.constraint(equalTo: separatorView.leadingAnchor).isActive = true + separatorView.bottomAnchor.constraint(equalTo: dashLine.bottomAnchor).isActive = true } open override func updateView(_ size: CGFloat) { @@ -206,10 +357,10 @@ import UIKit DispatchQueue.main.async { [weak self] in self?.formLabel?.updateView(size) - self?.label?.font = MFStyler.fontForTextFieldUnderLabel() -// let textFieldView = { - MFStyler.styleTextField(self!.textField!) -// } + self?.placeholderErrorLabel?.font = MFStyler.fontForTextFieldUnderLabel() + if let textField = self?.textField { + MFStyler.styleTextField(textField) + } self?.dashLine?.updateView(size) } } @@ -224,8 +375,7 @@ import UIKit borderPath?.removeAllPoints() - if !hideBorder { - let frame = textFieldContainerView!.frame + if !hideBorder, let frame = textFieldContainerView?.frame { borderPath = UIBezierPath() borderPath?.move(to: CGPoint(x: frame.origin.x, y: frame.origin.y + frame.size.height)) @@ -236,6 +386,7 @@ import UIKit let strokeColor = errorShowing ? UIColor.mfPumpkin() : UIColor.mfSilver() strokeColor.setStroke() + borderPath?.stroke() } } @@ -244,64 +395,61 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - func placeholder() -> String? { - return textField.attributedPlaceholder.string - } - - func text() -> String? { - return textField.text() - } - - // MARK: - Utilities func createDatePicker() { - //tool bar - MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: textField!.delegate) - //date picker - datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField!) + guard let textField = textField else { return } - var calendar = Calendar.current + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + + var calendar: Calendar = .current calendar.timeZone = NSTimeZone.system self.calendar = calendar } func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { + createDatePicker() - if show { - if let fromDate = fromDate { - if (calendar?.isDate(fromDate, inSameDayAs: Date()))! { - text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") - } else { - self.text = formatter.string(from: fromDate) - } + + 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 } func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) { + if let fromDate = fromDate { - if (calendar?.isDate(fromDate, inSameDayAs: Date()))! { + if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter.string(from: fromDate) + self.text = formatter?.string(from: fromDate) } } + datePicker?.minimumDate = fromDate datePicker?.maximumDate = toDate datePicker?.timeZone = NSTimeZone.system } func dismissDatePicker() -> Date? { - var pickedDate = datePicker!.date - if let pickedDatei = pickedDate { - if calendar.isDate(pickedDatei, inSameDayAs: Date()) { + + var pickedDate = datePicker?.date + + if let pickedDate = pickedDate { + if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter.string(from: pickedDatei) + self.text = formatter?.string(from: pickedDate) } } + textField?.resignFirstResponder() return pickedDate } @@ -313,13 +461,16 @@ import UIKit func setErrorMessage(_ errorMessage: String?) { DispatchQueue.main.async { [weak self] in - if let enabled = self?.enabled { + + if let enabled = self?.enabled, enabled { self?.separatorHeightConstraint?.constant = 4 self?.errorShowing = true self?.separatorView?.backgroundColor = UIColor.mfPumpkin() - self?.label?.text = errorMessage - self?.label?.numberOfLines = 0 - self?.textField?.accessibilityValue() = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message"), self.textField.text() ?? "", errorMessage ?? "") + self?.placeholderErrorLabel?.text = errorMessage + self?.placeholderErrorLabel?.numberOfLines = 0 + self?.textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", + self?.textField?.text ?? "", + errorMessage ?? "") self?.setNeedsDisplay() self?.layoutIfNeeded() } @@ -327,96 +478,47 @@ import UIKit } func pushAccessibilityNotification() { + DispatchQueue.main.async { [weak self] in - UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: self?.textField) + UIAccessibility.post(notification: .layoutChanged, argument: self?.textField) } } func hideError() { DispatchQueue.main.async { [weak self] in - self?.separatorHeightConstraint!.constant = 1 + self?.separatorHeightConstraint?.constant = 1 self?.separatorView?.backgroundColor = .black self?.layoutIfNeeded() self?.errorShowing = false - self?.label?.textColor = .black - self?.label?.text = "" + self?.placeholderErrorLabel?.textColor = .black + self?.placeholderErrorLabel?.text = "" self?.textField?.accessibilityValue = nil self?.setNeedsDisplay() self?.layoutIfNeeded() } } - func setPlaceholder(_ placeholder: String?, with color: UIColor?) { - customPlaceHolderColor = color - - if placeholder != nil { - if let color = color { - textField!.attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: [ - NSAttributedString.Key.foregroundColor: color - ]) - } - } - if textField?.text.length > 0 && !errorShowing { - label?.text = placeholder - } else if !errorShowing { - label?.text = "" - } - setAccessibilityString(placeholder) - } - + /** + Adding missing accessibilityLabel value + if we have some value in accessibilityLabel, + then only can append regular and picker item + */ func setAccessibilityString(_ accessibilityString: String?) { - var accessibilityString = accessibilityString - // adding missing accessibilityLabel value - // if we have some value in accessibilityLabel, - // then only can append regular and picker item - if hasDropDown { - // MFDLog(@"Label: %@", self.textField.accessibilityLabel); - accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item")!) - // MFDLog(@"Label: %@", self.textField.accessibilityLabel); - } else { - accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular")!) - // MFDLog(@"Label: %@", self.textField.accessibilityLabel); + + 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"))" + textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" } - func setFormText(_ formText: String?) { - formLabel?.text = formText - setAccessibilityString(formText) - } - - func setPlaceholder(_ placeholder: String?) { - setPlaceholder(placeholder, with: customPlaceHolderColor ?? UIColor.black) - } - - func setText(_ text: String?) { - textField?.text = text - valueChanged() - } - - func setMfTextFieldDelegate(_ mfTextFieldDelegate: MFTextFieldDelegate?) { - self.mfTextFieldDelegate = mfTextFieldDelegate - 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) - } - } - - func setUiTextFieldDelegate(_ uiTextFieldDelegate: UITextFieldDelegate?) { - uiTextFieldDelegate = uiTextFieldDelegate - textField?.delegate = uiTextFieldDelegate - } - - func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { mfTextFieldDelegate = delegate uiTextFieldDelegate = delegate } @@ -425,98 +527,90 @@ import UIKit guard let map = map, !map.isEmpty else { return } - var string = map.string(KeyLabel) - - if (string?.count ?? 0) > 0 { - formText = string + if let formText = map[KeyLabel] as? String { + self.formText = formText } - string = map?.string(KeyValue) - - if (string?.count ?? 0) > 0 { - text = string + if let text = map[KeyValue] as? String { + self.text = text } - string = map?.string(forKey: KeyDisable) - - if string?.isEqual(StringY) ?? false || map?.bool(forKey: KeyDisable) != nil { + if let text = map[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) { enable(false) } - string = map?.string(KeyErrorMessage) - - if (string?.count ?? 0) > 0 { - errMessage = string + if let errMessage = map[KeyErrorMessage] as? String { + self.errMessage = errMessage } // key used to send text value to server - string = map?.string(KeyFieldKey) - - if (string?.count ?? 0) > 0 { - fieldKey = string + if let fieldKey = map[KeyFieldKey] as? String { + self.fieldKey = fieldKey } - string = map?.string(KeyType) - - if (string == "dropDown") { + switch map.stringForkey(KeyType) { + case "dropDown": dropDownCarrotLabel?.isHidden = false self.hasDropDown = true - } else if (string == "password") { + case "password": textField?.isSecureTextEntry = true - } else if (string == "number") { + case "number": textField?.keyboardType = .numberPad - } else if (string == "email") { + case "email": textField?.keyboardType = .emailAddress + + default: + break } - string = map?.string("regex") + let regex = map.stringForkey("regex") - if (string?.count ?? 0) != 0 { + if regex.count != 0 { validationBlock = { enteredValue in - return MVMCoreUIUtility.validate(enteredValue, withRegularExpression: string) + guard let value = enteredValue else { return false } + return MVMCoreUIUtility.validate(value, withRegularExpression: regex) } } else { - setDefaultValidationBlock() + defaultValidationBlock() } } - func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: delegate) + func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + + guard let textField = textField else { return } + + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) + mfTextFieldDelegate = delegate uiTextFieldDelegate = delegate - textFieldDelegate = delegate setWithMap(map) } - func setValidationBlock(_ validationBlock: @escaping (String?) -> Bool) { - self.validationBlock = validationBlock - valueChanged() - } - - func setDefaultValidationBlock() { - self.validationBlock = { enteredValue in + 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 } - //-------------------------------------------------- - // MARK: - XIB Helpers - //-------------------------------------------------- func showDropDown(_ show: Bool) { + if hasDropDown { dropDownCarrotLabel?.isHidden = !show dropDownCarrotWidth?.isActive = !show @@ -526,99 +620,93 @@ import UIKit } func enable(_ enable: Bool) { - enabled = enable //Set outside the dispatch so that registerAnimations can know about it + + // Set outside the dispatch so that registerAnimations can know about it + enabled = enable + DispatchQueue.main.async { [weak self] in + self?.isUserInteractionEnabled = enable self?.textField?.isUserInteractionEnabled = enable self?.textField?.isEnabled = enable + if enable { self?.textField?.textColor = self?.customEnabledTextColor ?? .black self?.formLabel?.textColor = UIColor.mfBattleshipGrey() - self?.label?.textColor = UIColor.black - if self!.errorShowing { - self?.separatorView?.backgroundColor = UIColor.mfPumpkin() - } else { - self?.separatorView?.backgroundColor = .black - } + self?.placeholderErrorLabel?.textColor = .black + self?.separatorView?.backgroundColor = (self?.errorShowing ?? false) ? UIColor.mfPumpkin() : .black self?.showDropDown(true) + } else { - self?.textField?.textColor = self!.customDisabledTextColor ?? UIColor.mfSilver() + self?.textField?.textColor = self?.customDisabledTextColor ?? UIColor.mfSilver() self?.formLabel?.textColor = UIColor.mfSilver() - self?.label?.textColor = UIColor.mfSilver() + self?.placeholderErrorLabel?.textColor = UIColor.mfSilver() self?.showDropDown(false) - self?.hideError() //should not have error if the field is disabled + self?.hideError() // Should not have error if the field is disabled self?.separatorView?.backgroundColor = UIColor.mfSilver() } } } func showLabel(_ show: Bool) { - label?.isHidden = !show + + placeholderErrorLabel?.isHidden = !show } func dashSeperatorView(_ dash: Bool) { - if dash { - dashLine?.isHidden = false - //never hide seperator view because it could be possiblely used by other classes for positioning - separatorView?.backgroundColor = .clear - } else { - dashLine?.isHidden = true - separatorView?.backgroundColor = .black - } + + // 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 validateBlock() { - valueChanged() - } - func valueChanged() { - // update label for placeholder + // Update label for placeholder if !errorShowing { - label?.text = "" + placeholderErrorLabel?.text = "" } - // Check validity. - let previousValidity = valid - if (validationBlock != nil) { - valid = validationBlock(text) - } else { - //if validation not set, input will always be valid - valid = true - } + let previousValidity = isValid - if previousValidity && !valid { - if (errMessage != nil) { - self.errorMessage = errMessage + // If validation not set, input will always be valid + isValid = validationBlock?(text) ?? true + + if previousValidity && !isValid { + if let errMessage = errMessage { + setErrorMessage(errMessage) } - if mfTextFieldDelegate.responds(to: #selector(entryIsInvalid(_:))) { - mfTextFieldDelegate?.entryIsInvalid!(self) + + if let mfTextFieldDelegate = mfTextFieldDelegate { + mfTextFieldDelegate.entryIsInvalid?(self) } - } else if !previousValidity && valid { + } else if !previousValidity && isValid { hideError() - if mfTextFieldDelegate?.responds(to: #selector(entryIsValid(_:))) { - mfTextFieldDelegate?.entryIsValid!(self) + + if let mfTextFieldDelegate = mfTextFieldDelegate { + mfTextFieldDelegate.entryIsValid?(self) } } } func endInputing() { + if isValid { hideError() - separatorView!.backgroundColor = .black - } else { - if (errMessage != nil) { - self.errorMessage = errMessage - } + separatorView?.backgroundColor = .black + } else if let errMessage = errMessage { + setErrorMessage(errMessage) } } func startEditing() { + textField?.becomeFirstResponder() + if !errorShowing { separatorView?.backgroundColor = .black separatorHeightConstraint?.constant = 1 @@ -626,12 +714,15 @@ import UIKit } class func getEnabledTextfields(_ textFieldToDetermine: [MFTextField]?) -> [AnyHashable]? { - var enabledTextFields: [AnyHashable] = [] + + var enabledTextFields = [AnyHashable]() + for textfield in textFieldToDetermine ?? [] { if textfield.isEnabled { enabledTextFields.append(textfield) } } + return enabledTextFields } } @@ -640,15 +731,21 @@ import UIKit 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{ + if let delegateObject = delegateObject { FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol) - let formValidator = FormValidator.getForDelegate(delegateObject.formValidationProtocol) + setWithMap(json) + + if let formValidationProtocol = delegateObject.formValidationProtocol { + mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) as? TextFieldDelegate + } - self.withMap = json - mfTextFieldDelegate = formValidator uiTextFieldDelegate = delegateObject.uiTextFieldDelegate - MVMCoreUICommonViewsUtility.addDismissToolbar(textField!, delegate: uiTextFieldDelegate) + + if let textField = textField { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) + } } } @@ -660,16 +757,10 @@ extension TextField { // MARK: - Accessibility extension TextField { - func formatter() -> DateFormatter? { - if !formatter { - formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeZone = .system - formatter.locale = .current - formatter.formatterBehavior = .default - } - return formatter - } +// open override class func accessibilityElements() -> [Any]? { +// return [self.textField] +// } + } // MARK: - Form Validation diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 512b48cd..926fb791 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -17,14 +17,31 @@ open class DashLine: MFView { @objc public var dashColor: UIColor? + //------------------------------------------------------ + // MARK: - Initializer + //------------------------------------------------------ + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public init() { + super.init(frame: .zero) + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") + } + //------------------------------------------------------ // MARK: - Lifecycle //------------------------------------------------------ @objc override open func updateView(_ size: CGFloat) { - MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + DispatchQueue.main.async { [weak self] in self?.setNeedsDisplay() - }) + } } @objc override open func draw(_ rect: CGRect) { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 64dbe2e6..ffafdc82 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -36,7 +36,7 @@ @"footer": StandardFooterView.class, @"caretView": CaretView.class, @"caretButton": CaretButton.class, - @"textField" : MFTextField.class, + @"textField" : TextField.class, @"digitTextField" : MFDigitTextField.class, @"checkbox" : Checkbox.class, @"checkboxWithLabelView" : CheckboxWithLabelView.class, From d4154ab0d26a0815d417b2441b36873e5c30ed76 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 14 Oct 2019 15:31:28 -0400 Subject: [PATCH 006/112] removed view. renamed. --- MVMCoreUI/Atoms/TextFields/TextField.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index fb4867bf..66a3ab4a 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -29,8 +29,6 @@ import UIKit public var formLabel: Label? public var separatorView: UIView? public var placeholderErrorLabel: Label? - public var view: UIView? - public var dashLine: DashLine? public var dropDownCarrotLabel: UILabel? public weak var datePicker: UIDatePicker? @@ -152,11 +150,10 @@ import UIKit } } - public var customEnabledTextColor: UIColor? - public var customDisabledTextColor: UIColor? + public var enabledTextColor: UIColor? + public var disabledTextColor: UIColor? public var errMessage: String? - public var editCompleteAction: ((_ text: String?) -> ())? private var borderPath: UIBezierPath? private var calendar: Calendar? @@ -631,14 +628,14 @@ import UIKit self?.textField?.isEnabled = enable if enable { - self?.textField?.textColor = self?.customEnabledTextColor ?? .black + self?.textField?.textColor = self?.enabledTextColor ?? .black self?.formLabel?.textColor = UIColor.mfBattleshipGrey() self?.placeholderErrorLabel?.textColor = .black self?.separatorView?.backgroundColor = (self?.errorShowing ?? false) ? UIColor.mfPumpkin() : .black self?.showDropDown(true) } else { - self?.textField?.textColor = self?.customDisabledTextColor ?? UIColor.mfSilver() + self?.textField?.textColor = self?.disabledTextColor ?? UIColor.mfSilver() self?.formLabel?.textColor = UIColor.mfSilver() self?.placeholderErrorLabel?.textColor = UIColor.mfSilver() self?.showDropDown(false) From 208577b0a1e9f052ad8154ae7e2129c70b68a756 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 14 Oct 2019 15:36:30 -0400 Subject: [PATCH 007/112] small stuff. --- MVMCoreUI/Atoms/TextFields/TextField.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index 66a3ab4a..a7f3294a 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -437,7 +437,7 @@ import UIKit func dismissDatePicker() -> Date? { - var pickedDate = datePicker?.date + let pickedDate = datePicker?.date if let pickedDate = pickedDate { if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { @@ -540,7 +540,7 @@ import UIKit self.errMessage = errMessage } - // key used to send text value to server + // Key used to send text value to server if let fieldKey = map[KeyFieldKey] as? String { self.fieldKey = fieldKey } @@ -565,7 +565,7 @@ import UIKit let regex = map.stringForkey("regex") - if regex.count != 0 { + if !regex.isEmpty { validationBlock = { enteredValue in guard let value = enteredValue else { return false } return MVMCoreUIUtility.validate(value, withRegularExpression: regex) From e92616a7f5a91b5c19ba2521a77c04fa16a7fca4 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 15 Oct 2019 13:46:50 -0400 Subject: [PATCH 008/112] latest state. --- MVMCoreUI.xcodeproj/project.pbxproj | 30 +- MVMCoreUI/Atoms/TextFields/DigitTextBox.swift | 21 ++ .../Atoms/TextFields/DigitTextField.swift | 133 +++++++++ MVMCoreUI/Atoms/TextFields/MdnTextField.swift | 253 +++++++++++++++++ MVMCoreUI/Atoms/TextFields/TextField.swift | 257 +++++++++--------- .../FormValidator+TextFields.swift | 4 + MVMCoreUI/FormUIHelpers/FormValidator.swift | 1 - 7 files changed, 559 insertions(+), 140 deletions(-) create mode 100644 MVMCoreUI/Atoms/TextFields/DigitTextBox.swift create mode 100644 MVMCoreUI/Atoms/TextFields/DigitTextField.swift create mode 100644 MVMCoreUI/Atoms/TextFields/MdnTextField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 5b3f7c8b..f270063e 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -20,8 +20,14 @@ 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; - 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 0A8321A82355062F00CB7F00 /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; + 0A8321AF2355FE9500CB7F00 /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; + 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; + 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; }; + 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; }; + 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; @@ -97,17 +103,13 @@ D29DF18121E69E50003B2FB9 /* MFView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF17F21E69E2E003B2FB9 /* MFView.m */; }; D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF15921E697DA003B2FB9 /* SeparatorView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF15A21E697DA003B2FB9 /* SeparatorView.m */; }; - D29DF24D21E6A177003B2FB9 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; D29DF24E21E6A177003B2FB9 /* MFDigitTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF24F21E6A177003B2FB9 /* MFTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24421E6A176003B2FB9 /* MFTextField.xib */; }; D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */; }; - D29DF25221E6A177003B2FB9 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */; }; - D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; D29DF25521E6A177003B2FB9 /* MFDigitTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */; }; D29DF25621E6A177003B2FB9 /* MFTextFieldSubclassExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF25721E6A177003B2FB9 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26821E6AA0B003B2FB9 /* FLAnimatedImage.m */; }; D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26921E6AA0B003B2FB9 /* FLAnimatedImageView.m */; }; @@ -208,9 +210,12 @@ 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 = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; + 0A41BA7E23453A6400D4C0BC /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; - 0A41BA7E23453A6400D4C0BC /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; + 0A8321A72355062F00CB7F00 /* MdnTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnTextField.swift; sourceTree = ""; }; + 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitTextField.swift; sourceTree = ""; }; + 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitTextBox.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; @@ -752,6 +757,9 @@ D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, 0A41BA7E23453A6400D4C0BC /* TextField.swift */, + 0A8321A72355062F00CB7F00 /* MdnTextField.swift */, + 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */, + 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */, ); path = TextFields; sourceTree = ""; @@ -867,12 +875,12 @@ D29DF2B021E7B3A4003B2FB9 /* MFTextView.h in Headers */, D29DF2A921E7B2F9003B2FB9 /* MVMCoreUIConstants.h in Headers */, 0198F7A62256A80B0066C936 /* MFRadioButton.h in Headers */, - D29DF25221E6A177003B2FB9 /* MFMdnTextField.h in Headers */, D22D1F1A220341F60077CEC0 /* MVMCoreUICheckBox.h in Headers */, D29DF29921E7ADB8003B2FB9 /* ProgrammaticScrollViewController.h in Headers */, D29DF11C21E684A9003B2FB9 /* MVMCoreUISplitViewController.h in Headers */, D29DF29B21E7ADB9003B2FB9 /* StackableViewController.h in Headers */, D29770F421F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.h in Headers */, + 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */, D29DF15421E69760003B2FB9 /* MVMCoreUIPanelButtonProtocol.h in Headers */, D2A514582211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h in Headers */, D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */, @@ -882,6 +890,7 @@ D29DF2BC21E7BEA4003B2FB9 /* TopTabbar.h in Headers */, D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */, D22D1F46220496A30077CEC0 /* MVMCoreUISwitch.h in Headers */, + 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */, D22D1F1E220343560077CEC0 /* MVMCoreUICheckMarkView.h in Headers */, D29DF28421E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.h in Headers */, D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */, @@ -894,7 +903,6 @@ D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */, D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */, D29DF2C621E7BF57003B2FB9 /* MFTabBarInteractor.h in Headers */, - D29DF25721E6A177003B2FB9 /* MFTextField.h in Headers */, D29DF17521E69E1F003B2FB9 /* ButtonDelegateProtocol.h in Headers */, D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */, D29DF26E21E6AA0B003B2FB9 /* FLAnimatedImage.h in Headers */, @@ -1038,10 +1046,9 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, - D29DF24D21E6A177003B2FB9 /* MFTextField.m in Sources */, + 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, - D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, @@ -1054,6 +1061,7 @@ D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */, + 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, @@ -1103,6 +1111,7 @@ D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A8321A82355062F00CB7F00 /* MdnTextField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -1111,6 +1120,7 @@ D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, + 0A8321AF2355FE9500CB7F00 /* DigitTextBox.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift new file mode 100644 index 00000000..48d6734d --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift @@ -0,0 +1,21 @@ +// +// DigitTextBox.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/15/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class DigitTextBox: UITextField { + + /* + // Only override draw() if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func draw(_ rect: CGRect) { + // Drawing code + } + */ + +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift new file mode 100644 index 00000000..bf2c2572 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -0,0 +1,133 @@ +// +// DigitTextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/15/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class DigitTextField: TextField, UITextFieldDelegate, MFDigitTextBoxDelegate { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + private weak var textFieldsView: UIView? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + private var numberOfDigits = 0 + private var switchedAutomatically = false + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private weak var messageToTextFieldPin: NSLayoutConstraint? + private weak var labelToTextFieldPin: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + func digitField() -> MFDigitTextBox? { + let textField = MFDigitTextBox() + textField.delegate = self + textField.mfTextBoxDelegate = self + return textField + } + + class func withNumberOfDigits(_ numberOfDigits: Int) -> Self? { + let field = self.mf() + field?.numberOfDigits = numberOfDigits + field?.buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + return field + } + + class func withNumberOfDigits(_ numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + let field = self.mfTextField(withBothDelegates: delegate) + field?.numberOfDigits = numberOfDigits + field?.buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + return field + } + + class func withNumberOfDigits(_ numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) -> Self? { + let field = self.mfTextField(withBothDelegates: delegate) + field?.numberOfDigits = numberOfDigits + field?.buildTextFieldsView(forSize: size) + return field + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func buildTextFieldsView(forSize size: CGFloat) { + + // Remove all current UI. + if let textFields = textFields, !textFields.isEmpt { + StackableViewController.removeUIViews(self.textFields) + } + + if numberOfDigits > 0 { + + // Create text boxes. + var textFields = [AnyHashable](repeating: 0, count: numberOfDigits) + + for i in 0.. 0 { + self.labelToTextFieldPin.constant = 10 + + } else { + self.labelToTextFieldPin.constant = 0 + } + } + } +} diff --git a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift new file mode 100644 index 00000000..5cbf814d --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift @@ -0,0 +1,253 @@ +// +// MdnTextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/14/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import AddressBookUI +import ContactsUI +import UIKit +import MVMCore + + +@objcMembers class MdnTextField: TextField, UITextFieldDelegate, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public weak var customDelegate: UITextFieldDelegate? + public var isNationalMdn = false + public var shouldValidateMDN = false + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: .zero) + setup() + } + + public convenience init() { + self.init(frame: .zero) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // 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 = getMdn() else { return true } + + if mdn.isEmpty { + return true + + } else 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 + } + + func getErrorMessage() -> String? { + + return nil + } + + func getMdn() -> String? { + + return MVMCoreUIUtility.removeMdnFormat(text! as String) + } + + func setMdn(_ mdn: String?) { + + guard let MDN = mdn else { return } + + text = MVMCoreUIUtility.formatMdn(MDN) + } + + @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) + } + + //-------------------------------------------------- + // MARK: - ContactPicker Delegate + //-------------------------------------------------- + + func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + + if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { + + 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 func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldReturn?(textField) ?? false + } + + return true + } + + @objc func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { + return false + } + + if let customDelegate = customDelegate { + return customDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + } + + return true + } + + func textFieldDidBeginEditing(_ textField: UITextField) { + + textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + customDelegate?.textFieldDidBeginEditing?(textField) + } + + func textFieldDidEndEditing(_ textField: UITextField) { + + customDelegate?.textFieldDidEndEditing?(textField) + + if validateAndColor() && isNationalMdn { + textField.text = MVMCoreUIUtility.formatMdn(textField.text) + } + } + + //-------------------------------------------------- + // MARK: - Passed Along TextField delegate + //-------------------------------------------------- + + @objc func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldBeginEditing?(textField) ?? true + } + + return true + } + + @objc func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldEndEditing?(textField) ?? true + } + + return true + } + + @objc func textFieldShouldClear(_ textField: UITextField) -> Bool { + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldClear?(textField) ?? true + } + + return true + } + +} diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index a7f3294a..20df71c5 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -35,19 +35,12 @@ import UIKit private(set) weak var toolbar: UIToolbar? //-------------------------------------------------- - // MARK: - Properties + // MARK: - Delegate Properties //-------------------------------------------------- - private var _mfTextFieldDelegate: TextFieldDelegate? - private var _uiTextFieldDelegate: UITextFieldDelegate? - /// 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? { - get { - return _mfTextFieldDelegate - } - set { - _mfTextFieldDelegate = newValue + didSet { if mfTextFieldDelegate != nil && !observingForChanges { observingForChanges = true NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextField.textDidChangeNotification, object: textField) @@ -68,16 +61,22 @@ import UIKit return textField?.delegate } set { - _uiTextFieldDelegate = newValue - textField?.delegate = uiTextFieldDelegate + textField?.delegate = newValue } } - public weak var pickerView: UIPickerView? + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + private var borderPath: UIBezierPath? + private var calendar: Calendar? + public var pickerView: UIPickerView? public var observingForChanges = false public var errorShowing = false public var hasDropDown = false + public var enabled = true + public var hideBorder = false public var text: String? { get { @@ -99,9 +98,6 @@ import UIKit } } - - public var fieldKey: String? - public var placeholderTextColor: UIColor = .black public var placeholder: String? { @@ -110,7 +106,10 @@ import UIKit return attributedPlaceholder.string } set { - guard let newPlaceholderText = newValue else { return } + guard let newPlaceholderText = newValue else { + textField?.attributedPlaceholder = nil + return + } textField?.attributedPlaceholder = NSAttributedString(string: newPlaceholderText, attributes: [NSAttributedString.Key.foregroundColor: placeholderTextColor]) @@ -122,10 +121,6 @@ import UIKit } } - public var enabled = false - - public var hideBorder = false - public var formatter: DateFormatter? { let formatter = DateFormatter() @@ -138,14 +133,9 @@ import UIKit } public var isValid = false - - private var _validationBlock: ((_ enteredValue: String?) -> Bool)? + public var fieldKey: String? public var validationBlock: ((_ enteredValue: String?) -> Bool)? { - get { - return _validationBlock - } - set { - _validationBlock = newValue + didSet { valueChanged() } } @@ -153,10 +143,27 @@ import UIKit public var enabledTextColor: UIColor? public var disabledTextColor: UIColor? - public var errMessage: String? - - private var borderPath: UIBezierPath? - private var calendar: Calendar? + public var errorMessage: String? { + didSet { + DispatchQueue.main.async { [weak self] in + + guard let errorMessage = self?.errorMessage else { return } + + if let enabled = self?.enabled, enabled { + self?.separatorHeightConstraint?.constant = 4 + self?.errorShowing = 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 ?? "", + errorMessage) + self?.setNeedsDisplay() + self?.layoutIfNeeded() + } + } + } + } //-------------------------------------------------- // MARK: - Constraints @@ -192,35 +199,31 @@ import UIKit fatalError("init(coder:) has not been implemented") } + /// Basic initializer. public convenience init() { self.init(frame: .zero) - - hasDropDown = false } - public convenience init(with bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. + public convenience init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { self.init(frame: .zero) - mfTextFieldDelegate = bothDelegates - uiTextFieldDelegate = bothDelegates + setBothTextFieldDelegates(bothDelegates) } - public convenience init(map: [AnyHashable: Any]?, with bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + /// - parameter hasDropDown: tbd + /// - parameter map: Dictionary of values to setup this TextField + /// - parameter bothDelegate: Sets both MF/UI Text Field Delegates. + public convenience init(hasDropDown: Bool = false, map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { self.init(frame: .zero) - setWithMap(map, bothDelegates: bothDelegates) - } - - public convenience init(dropDownWithMap map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - self.init(frame: .zero) - - dropDownCarrotLabel?.isHidden = false - hasDropDown = true + dropDownCarrotLabel?.isHidden = hasDropDown + self.hasDropDown = !hasDropDown setWithMap(map, bothDelegates: bothDelegates) } //-------------------------------------------------- - // MARK: - Setup + // MARK: - Lifecycle //-------------------------------------------------- override open func setupView() { @@ -231,12 +234,13 @@ import UIKit setContentHuggingPriority(.required, for: .vertical) setContentCompressionResistancePriority(.required, for: .vertical) backgroundColor = .clear - enabled = true 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) addSubview(formLabel) @@ -251,7 +255,7 @@ import UIKit textFieldContainerView.translatesAutoresizingMaskIntoConstraints = false addSubview(textFieldContainerView) - constrainTextContainerContent() + constrainContent(textFieldContainerView) textFieldContainerView.topAnchor.constraint(equalTo: formLabel.bottomAnchor, constant: 4).isActive = true textContainerLeftPin = textFieldContainerView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) @@ -263,6 +267,9 @@ import UIKit self.placeholderErrorLabel = placeholderErrorLabel placeholderErrorLabel.font = MFStyler.fontForTextFieldUnderLabel() placeholderErrorLabel.textColor = .black + placeholderErrorLabel.setContentHuggingPriority(.required, for: .vertical) + placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + placeholderErrorLabel.setContentCompressionResistancePriority(.required, for: .vertical) addSubview(placeholderErrorLabel) @@ -275,9 +282,7 @@ import UIKit layoutMarginsGuide.bottomAnchor.constraint(equalTo: placeholderErrorLabel.bottomAnchor).isActive = true } - private func constrainTextContainerContent() { - - guard let parentView = textFieldContainerView else { return } + private func constrainContent(_ parentView: UIView) { let backgroundView = UIView(frame: .zero) self.backgroundView = backgroundView @@ -294,6 +299,7 @@ import UIKit let textField = UITextField(frame: .zero) self.textField = textField textField.translatesAutoresizingMaskIntoConstraints = false + textField.setContentHuggingPriority(.required, for: .vertical) textField.font = MFStyler.fontForTextField() textField.smartQuotesType = .no textField.smartDashesType = .no @@ -302,12 +308,16 @@ import UIKit addSubview(textField) - textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10).isActive = true - textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16).isActive = true - parentView.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10).isActive = true + NSLayoutConstraint.activate([ + textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10), + textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16), + parentView.trailingAnchor.constraint(equalTo: textField.trailingAnchor, 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)) @@ -341,11 +351,12 @@ import UIKit addSubview(dashLine) - dashLine.centerYAnchor.constraint(equalTo: separatorView.centerYAnchor).isActive = true - dashLine.centerXAnchor.constraint(equalTo: separatorView.centerXAnchor).isActive = true - dashLine.topAnchor.constraint(equalTo: separatorView.topAnchor).isActive = true - dashLine.leadingAnchor.constraint(equalTo: separatorView.leadingAnchor).isActive = true - separatorView.bottomAnchor.constraint(equalTo: dashLine.bottomAnchor).isActive = true + 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), + separatorView.bottomAnchor.constraint(equalTo: dashLine.bottomAnchor)]) } open override func updateView(_ size: CGFloat) { @@ -389,10 +400,10 @@ import UIKit } //-------------------------------------------------- - // MARK: - Methods + // MARK: - Date Picker //-------------------------------------------------- - func createDatePicker() { + private func createDatePicker() { guard let textField = textField else { return } @@ -404,7 +415,7 @@ import UIKit self.calendar = calendar } - func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { + public func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { createDatePicker() @@ -420,13 +431,13 @@ import UIKit datePicker?.maximumDate = toDate } - func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) { + 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 { - self.text = formatter?.string(from: fromDate) + text = formatter?.string(from: fromDate) } } @@ -435,7 +446,7 @@ import UIKit datePicker?.timeZone = NSTimeZone.system } - func dismissDatePicker() -> Date? { + public func dismissDatePicker() -> Date? { let pickedDate = datePicker?.date @@ -443,7 +454,7 @@ import UIKit if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter?.string(from: pickedDate) + text = formatter?.string(from: pickedDate) } } @@ -451,37 +462,16 @@ import UIKit return pickedDate } - func dismissPicker() { + public func dismissPicker() { + textField?.resignFirstResponder() } - func setErrorMessage(_ errorMessage: String?) { - - DispatchQueue.main.async { [weak self] in - - if let enabled = self?.enabled, enabled { - self?.separatorHeightConstraint?.constant = 4 - self?.errorShowing = 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 ?? "", - errorMessage ?? "") - self?.setNeedsDisplay() - self?.layoutIfNeeded() - } - } - } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- - func pushAccessibilityNotification() { - - DispatchQueue.main.async { [weak self] in - UIAccessibility.post(notification: .layoutChanged, argument: self?.textField) - } - } - - func hideError() { + public func hideError() { DispatchQueue.main.async { [weak self] in self?.separatorHeightConstraint?.constant = 1 @@ -496,31 +486,13 @@ import UIKit } } - /** - Adding missing accessibilityLabel value - if we have some value in accessibilityLabel, - then only can append regular and picker item - */ - func setAccessibilityString(_ accessibilityString: String?) { + public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { - 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") ?? "")" - } - - func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { mfTextFieldDelegate = delegate uiTextFieldDelegate = delegate } - func setWithMap(_ map: [AnyHashable: Any]?) { + public func setWithMap(_ map: [AnyHashable: Any]?) { guard let map = map, !map.isEmpty else { return } @@ -537,7 +509,7 @@ import UIKit } if let errMessage = map[KeyErrorMessage] as? String { - self.errMessage = errMessage + self.errorMessage = errMessage } // Key used to send text value to server @@ -575,17 +547,16 @@ import UIKit } } - func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + public func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) { guard let textField = textField else { return } MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) - mfTextFieldDelegate = delegate - uiTextFieldDelegate = delegate + setBothTextFieldDelegates(delegate) setWithMap(map) } - func defaultValidationBlock() { + public func defaultValidationBlock() { validationBlock = { enteredValue in return (enteredValue?.count ?? 0) > 0 @@ -606,7 +577,7 @@ import UIKit formLabelRightPin?.constant = constant } - func showDropDown(_ show: Bool) { + public func showDropDown(_ show: Bool) { if hasDropDown { dropDownCarrotLabel?.isHidden = !show @@ -616,7 +587,7 @@ import UIKit } } - func enable(_ enable: Bool) { + public func enable(_ enable: Bool) { // Set outside the dispatch so that registerAnimations can know about it enabled = enable @@ -645,12 +616,12 @@ import UIKit } } - func showLabel(_ show: Bool) { + public func showLabel(_ show: Bool) { placeholderErrorLabel?.isHidden = !show } - func dashSeperatorView(_ dash: Bool) { + public func dashSeperatorView(_ dash: Bool) { // Never hide seperator view because it could be possiblely used by other classes for positioning dashLine?.isHidden = !dash @@ -674,8 +645,8 @@ import UIKit isValid = validationBlock?(text) ?? true if previousValidity && !isValid { - if let errMessage = errMessage { - setErrorMessage(errMessage) + if let errMessage = errorMessage { + self.errorMessage = errMessage } if let mfTextFieldDelegate = mfTextFieldDelegate { @@ -695,8 +666,8 @@ import UIKit if isValid { hideError() separatorView?.backgroundColor = .black - } else if let errMessage = errMessage { - setErrorMessage(errMessage) + } else if let errMessage = errorMessage { + self.errorMessage = errMessage } } @@ -754,9 +725,37 @@ extension TextField { // MARK: - Accessibility extension TextField { -// open override class func accessibilityElements() -> [Any]? { -// return [self.textField] -// } + // open override class func accessibilityElements() -> [Any]? { + // return [self.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") ?? "")" + } } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift index b2b18ebe..27d6c18a 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift @@ -9,13 +9,16 @@ import Foundation @objc extension FormValidator: MFTextFieldDelegate { + public func dismissFieldInput(_ sender: Any?) { + if let delegate = delegate as? MFTextFieldDelegate { delegate.dismissFieldInput?(sender) } } public func entryIsValid(_ textfield: MFTextField?) { + MVMCoreDispatchUtility.performBlock(onMainThread: { self.enableByValidation() if let delegate = self.delegate as? MFTextFieldDelegate { @@ -25,6 +28,7 @@ import Foundation } public func entryIsInvalid(_ textfield: MFTextField?) { + MVMCoreDispatchUtility.performBlock(onMainThread: { self.enableByValidation() if let delegate = self.delegate as? MFTextFieldDelegate { diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 1b546d69..5275b633 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -6,7 +6,6 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import Foundation import UIKit @objcMembers public class FormValidator: NSObject { From 7e1d23db3d1ccd2fd7f1030c22fc24ae76d9be35 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 16 Oct 2019 10:43:45 -0400 Subject: [PATCH 009/112] latest text. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 +- MVMCoreUI/Atoms/TextFields/DigitTextBox.swift | 136 ++++++++++++++++-- MVMCoreUI/Atoms/TextFields/MFDigitTextField.m | 2 + MVMCoreUI/Atoms/TextFields/TextField.swift | 10 +- .../FormValidator+TextFields.swift | 31 ++++ 5 files changed, 166 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index f270063e..fb1472f4 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -25,8 +25,8 @@ 0A8321A82355062F00CB7F00 /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; 0A8321AF2355FE9500CB7F00 /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; - 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; }; - 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; }; + 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift index 48d6734d..f87d2189 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift @@ -8,14 +8,130 @@ import UIKit -class DigitTextBox: UITextField { - - /* - // Only override draw() if you perform custom drawing. - // An empty implementation adversely affects performance during animation. - override func draw(_ rect: CGRect) { - // Drawing code - } - */ - +@objc protocol MFDigitTextBoxDelegate: NSObjectProtocol { + @objc optional func textFieldDidDelete(_ textField: UITextField?) +} + +class DigitTextBox: UITextField, MVMCoreViewProtocol { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + private weak var bottomBar: SeparatorView? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + weak var mfTextBoxDelegate: MFDigitTextBoxDelegate? + + private var previousSize: CGFloat = 0.0 + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private weak var widthConstraint: NSLayoutConstraint? + private weak var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + required init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") + } + + public override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + public convenience init() { + self.init(frame: .zero) + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + func setup() { + + if constraints.count == 0 { + + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .white + textAlignment = .center + keyboardType = .numberPad + layer.borderWidth = 1 + hideError() + + widthConstraint = widthAnchor.constraint(equalToConstant: 40) + widthConstraint?.isActive = true + + heightConstraint = heightAnchor.constraint(equalToConstant: 60) + heightConstraint?.isActive = true + + bottomBar = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot) + bottomBar?.setAsMedium() + + updateView(MVMCoreUISplitViewController.getDetailViewWidth()) + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + override func deleteBackward() { + super.deleteBackward() + + if let delegate = mfTextBoxDelegate { + delegate.textFieldDidDelete?(self) + } + } + + func updateView(_ size: CGFloat) { + + DispatchQueue.main.async { + if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(self.previousSize)) { + MFStyler.styleTextField(self) + self.font = MFFonts.mfFont55Rg(28) + + var digitWidth: CGFloat = 0 + var digitHeight: CGFloat = 0 + + let sizeObject = MFSizeObject(standardBlock: { + digitWidth = 39 + digitHeight = 44 + + }, smalliPhone: { + digitWidth = 35 + digitHeight = 38 + + }, standardiPadPortraitBlock: { + digitWidth = 59 + digitHeight = 74 + }) + + sizeObject?.performBlockBase(onSize: size) + self.widthConstraint?.constant = digitWidth + self.heightConstraint?.constant = digitHeight + self.previousSize = size + } + } + } + + func setAsError() { + + layer.borderColor = UIColor.mfPumpkin().cgColor + bottomBar?.backgroundColor = UIColor.mfPumpkin() + bottomBar?.height?.constant = 4 + } + + func hideError() { + layer.borderColor = UIColor.mfSilver().cgColor + bottomBar?.setAsMedium() + } } diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m b/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m index 5f76e88b..dcc08512 100644 --- a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m +++ b/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m @@ -118,6 +118,8 @@ }]; } +// CONTINUE + - (void)updateView:(CGFloat)size { [super updateView:size]; [MVMCoreDispatchUtility performBlockOnMainThread:^{ diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index 20df71c5..d5bf2c72 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -11,11 +11,11 @@ import UIKit @objc public protocol TextFieldDelegate: NSObjectProtocol { /// Called when the entered text becomes valid based on the validation block - @objc optional func entryIsValid(_ textfield: TextField?) + @objc optional func isValid(_ textfield: TextField?) /// Called when the entered text becomes invalid based on the validation block - @objc optional func entryIsInvalid(_ textfield: TextField?) + @objc optional func isInvalid(_ textfield: TextField?) /// Dismisses the keyboard. - @objc optional func dismissFieldInput(_ sender: Any?) + @objc optional func dismissField(_ sender: Any?) } @objcMembers open class TextField: ViewConstrainingView { @@ -650,13 +650,13 @@ import UIKit } if let mfTextFieldDelegate = mfTextFieldDelegate { - mfTextFieldDelegate.entryIsInvalid?(self) + mfTextFieldDelegate.isInvalid?(self) } } else if !previousValidity && isValid { hideError() if let mfTextFieldDelegate = mfTextFieldDelegate { - mfTextFieldDelegate.entryIsValid?(self) + mfTextFieldDelegate.isValid?(self) } } } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift index 27d6c18a..e8d79245 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift @@ -37,3 +37,34 @@ import Foundation }) } } + +// Temporary: Looking to either combine or separate entirely with MFTextFieldDelegate. +extension FormValidator: TextFieldDelegate { + + public func dismissField(_ sender: Any?) { + + if let delegate = delegate as? MFTextFieldDelegate { + delegate.dismissFieldInput?(sender) + } + } + + @nonobjc public func isValid(_ textfield: MFTextField?) { + + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.enableByValidation() + if let delegate = self.delegate as? MFTextFieldDelegate { + delegate.entryIsValid?(textfield) + } + }) + } + + public func isInvalid(_ textfield: MFTextField?) { + + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.enableByValidation() + if let delegate = self.delegate as? MFTextFieldDelegate { + delegate.entryIsInvalid?(textfield) + } + }) + } +} From 4a833abc31f764a0b9d47ce50cdbb130f237792d Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 16 Oct 2019 10:44:16 -0400 Subject: [PATCH 010/112] mase public. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index f270063e..67d0519a 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -22,11 +22,9 @@ 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; - 0A8321A82355062F00CB7F00 /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; - 0A8321AF2355FE9500CB7F00 /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; - 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; }; - 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; }; + 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; @@ -1111,7 +1109,6 @@ D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, - 0A8321A82355062F00CB7F00 /* MdnTextField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -1120,7 +1117,6 @@ D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, - 0A8321AF2355FE9500CB7F00 /* DigitTextBox.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, From 19310bff893d9c34c6035249f97b7555dafb6814 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 16 Oct 2019 13:51:55 -0400 Subject: [PATCH 011/112] generally functioning. some loose ends with TextField. --- MVMCoreUI/Atoms/TextFields/DigitTextBox.swift | 47 +++++++------- .../Atoms/TextFields/DigitTextField.swift | 2 +- MVMCoreUI/Atoms/TextFields/MdnTextField.swift | 63 +++++++++---------- MVMCoreUI/Atoms/TextFields/TextField.swift | 19 +++--- MVMCoreUI/Atoms/Views/DashLine.swift | 2 +- 5 files changed, 68 insertions(+), 65 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift index f87d2189..9bed633c 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift @@ -12,7 +12,7 @@ import UIKit @objc optional func textFieldDidDelete(_ textField: UITextField?) } -class DigitTextBox: UITextField, MVMCoreViewProtocol { +@objcMembers open class DigitTextBox: UITextField, MVMCoreViewProtocol { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -38,7 +38,7 @@ class DigitTextBox: UITextField, MVMCoreViewProtocol { // MARK: - Initializers //-------------------------------------------------- - required init?(coder: NSCoder) { + required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("init(coder:) has not been implemented") } @@ -58,33 +58,32 @@ class DigitTextBox: UITextField, MVMCoreViewProtocol { func setup() { - if constraints.count == 0 { - - translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .white - textAlignment = .center - keyboardType = .numberPad - layer.borderWidth = 1 - hideError() - - widthConstraint = widthAnchor.constraint(equalToConstant: 40) - widthConstraint?.isActive = true - - heightConstraint = heightAnchor.constraint(equalToConstant: 60) - heightConstraint?.isActive = true - - bottomBar = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot) - bottomBar?.setAsMedium() - - updateView(MVMCoreUISplitViewController.getDetailViewWidth()) - } + guard constraints.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .white + textAlignment = .center + keyboardType = .numberPad + layer.borderWidth = 1 + hideError() + + widthConstraint = widthAnchor.constraint(equalToConstant: 40) + widthConstraint?.isActive = true + + heightConstraint = heightAnchor.constraint(equalToConstant: 60) + heightConstraint?.isActive = true + + bottomBar = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot) + bottomBar?.setAsMedium() + + updateView(MVMCoreUISplitViewController.getDetailViewWidth()) } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - override func deleteBackward() { + override open func deleteBackward() { super.deleteBackward() if let delegate = mfTextBoxDelegate { @@ -92,7 +91,7 @@ class DigitTextBox: UITextField, MVMCoreViewProtocol { } } - func updateView(_ size: CGFloat) { + public func updateView(_ size: CGFloat) { DispatchQueue.main.async { if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(self.previousSize)) { diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift index bf2c2572..c2f5c290 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -8,7 +8,7 @@ import UIKit -class DigitTextField: TextField, UITextFieldDelegate, MFDigitTextBoxDelegate { +@objcMembers open class DigitTextField: TextField, UITextFieldDelegate, MFDigitTextBoxDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift index 5cbf814d..93ee73b7 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift @@ -12,7 +12,7 @@ import UIKit import MVMCore -@objcMembers class MdnTextField: TextField, UITextFieldDelegate, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { +@objcMembers open class MdnTextField: TextField, UITextFieldDelegate, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -25,9 +25,9 @@ import MVMCore // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + open override init(frame: CGRect) { super.init(frame: .zero) - setup() + setup() } public convenience init() { @@ -42,14 +42,14 @@ import MVMCore //-------------------------------------------------- // 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(_:))) @@ -72,13 +72,13 @@ import MVMCore } } } - + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- func hasValidMdn() -> Bool { - + guard let mdn = getMdn() else { return true } if mdn.isEmpty { @@ -92,7 +92,7 @@ import MVMCore } func validateAndColor() -> Bool { - + if !shouldValidateMDN { let isValid = hasValidMdn() @@ -108,24 +108,24 @@ import MVMCore return true } - + func getErrorMessage() -> String? { return nil } - + func getMdn() -> String? { return MVMCoreUIUtility.removeMdnFormat(text! as String) } - + func setMdn(_ mdn: String?) { guard let MDN = mdn else { return } text = MVMCoreUIUtility.formatMdn(MDN) } - + @objc func dismissFieldInput(_ sender: Any?) { if let delegate = uiTextFieldDelegate { @@ -134,7 +134,7 @@ import MVMCore textField?.resignFirstResponder() } } - + func getContacts(_ sender: Any?) { let picker = CNContactPickerViewController() @@ -144,19 +144,19 @@ import MVMCore picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'") MVMCoreNavigationHandler.shared()?.present(picker, animated: true) } - + //-------------------------------------------------- // MARK: - ContactPicker Delegate //-------------------------------------------------- - + func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { - + if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { - + 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, @@ -165,7 +165,7 @@ import MVMCore unformattedMDN = (unformedMDN as NSString).substring(from: 1) } - + text = unformattedMDN if let textField = textField { @@ -174,7 +174,7 @@ import MVMCore } } } - + //-------------------------------------------------- // MARK: - ImplementedTextField Delegate //-------------------------------------------------- @@ -183,27 +183,27 @@ import MVMCore @objc func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() - + if let customDelegate = customDelegate { return customDelegate.textFieldShouldReturn?(textField) ?? false } return true } - + @objc func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false } - + if let customDelegate = customDelegate { return customDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } - + return true } - + func textFieldDidBeginEditing(_ textField: UITextField) { textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) @@ -213,16 +213,16 @@ import MVMCore func textFieldDidEndEditing(_ textField: UITextField) { customDelegate?.textFieldDidEndEditing?(textField) - + if validateAndColor() && isNationalMdn { textField.text = MVMCoreUIUtility.formatMdn(textField.text) } } - + //-------------------------------------------------- // MARK: - Passed Along TextField delegate //-------------------------------------------------- - + @objc func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { if let customDelegate = customDelegate { @@ -231,7 +231,7 @@ import MVMCore return true } - + @objc func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { if let customDelegate = customDelegate { @@ -240,7 +240,7 @@ import MVMCore return true } - + @objc func textFieldShouldClear(_ textField: UITextField) -> Bool { if let customDelegate = customDelegate { @@ -249,5 +249,4 @@ import MVMCore return true } - } diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index d5bf2c72..d7722f7c 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -226,6 +226,7 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- + /// Initial configuration of class and view. override open func setupView() { guard subviews.isEmpty else { return } @@ -241,6 +242,8 @@ import UIKit formLabel.textColor = UIColor.mfBattleshipGrey() formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + formLabel.setContentCompressionResistancePriority(.required, for: .vertical) + formLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true addSubview(formLabel) @@ -282,6 +285,7 @@ import UIKit layoutMarginsGuide.bottomAnchor.constraint(equalTo: placeholderErrorLabel.bottomAnchor).isActive = true } + /// Configuration logic for the text container view. private func constrainContent(_ parentView: UIView) { let backgroundView = UIView(frame: .zero) @@ -299,7 +303,8 @@ import UIKit let textField = UITextField(frame: .zero) self.textField = textField textField.translatesAutoresizingMaskIntoConstraints = false - textField.setContentHuggingPriority(.required, for: .vertical) + textField.setContentCompressionResistancePriority(.required, for: .vertical) + textField.heightAnchor.constraint(equalToConstant: 24).isActive = true textField.font = MFStyler.fontForTextField() textField.smartQuotesType = .no textField.smartDashesType = .no @@ -311,7 +316,7 @@ import UIKit NSLayoutConstraint.activate([ textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10), textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16), - parentView.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10)]) + parentView.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)]) let dropDownCarrotLabel = Label() self.dropDownCarrotLabel = dropDownCarrotLabel @@ -325,8 +330,8 @@ import UIKit addSubview(dropDownCarrotLabel) - textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10).isActive = true - textField.leadingAnchor.constraint(equalTo: textField.leadingAnchor, constant: 6).isActive = true + 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) @@ -346,6 +351,7 @@ import UIKit parentView.bottomAnchor.constraint(equalTo: separatorView.bottomAnchor).isActive = true let dashLine = DashLine() + dashLine.translatesAutoresizingMaskIntoConstraints = false dashLine.backgroundColor = .white dashLine.isHidden = true @@ -355,8 +361,7 @@ import UIKit dashLine.centerYAnchor.constraint(equalTo: separatorView.centerYAnchor), dashLine.centerXAnchor.constraint(equalTo: separatorView.centerXAnchor), dashLine.topAnchor.constraint(equalTo: separatorView.topAnchor), - dashLine.leadingAnchor.constraint(equalTo: separatorView.leadingAnchor), - separatorView.bottomAnchor.constraint(equalTo: dashLine.bottomAnchor)]) + dashLine.leadingAnchor.constraint(equalTo: separatorView.leadingAnchor)]) } open override func updateView(_ size: CGFloat) { @@ -706,7 +711,7 @@ extension TextField { setWithMap(json) if let formValidationProtocol = delegateObject.formValidationProtocol { - mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) as? TextFieldDelegate + mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) } uiTextFieldDelegate = delegateObject.uiTextFieldDelegate diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 926fb791..6197afe5 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -31,7 +31,6 @@ open class DashLine: MFView { required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("init(coder:) has not been implemented") } //------------------------------------------------------ @@ -39,6 +38,7 @@ open class DashLine: MFView { //------------------------------------------------------ @objc override open func updateView(_ size: CGFloat) { + DispatchQueue.main.async { [weak self] in self?.setNeedsDisplay() } From d0264d45eba08e3574ae2f4eefc6e526b025af4d Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 16 Oct 2019 16:27:27 -0400 Subject: [PATCH 012/112] funcational, --- MVMCoreUI/Atoms/TextFields/TextField.swift | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index d7722f7c..ad0de57d 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -145,22 +145,22 @@ import UIKit public var errorMessage: String? { didSet { - DispatchQueue.main.async { [weak self] in - - guard let errorMessage = self?.errorMessage else { return } - - if let enabled = self?.enabled, enabled { - self?.separatorHeightConstraint?.constant = 4 - self?.errorShowing = 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 ?? "", - errorMessage) - self?.setNeedsDisplay() - self?.layoutIfNeeded() - } + + guard enabled else { return } + + self.separatorHeightConstraint?.constant = 4 + self.errorShowing = true + self.separatorView?.backgroundColor = UIColor.mfPumpkin() + + if let errorMessage = self.errorMessage { + self.placeholderErrorLabel?.text = errorMessage + self.placeholderErrorLabel?.numberOfLines = 0 + self.textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", self.textField?.text ?? "", errorMessage) + } + + DispatchQueue.main.async { + self.setNeedsDisplay() + self.layoutIfNeeded() } } } @@ -243,7 +243,6 @@ import UIKit formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) formLabel.setContentCompressionResistancePriority(.required, for: .vertical) - formLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true addSubview(formLabel) @@ -258,7 +257,7 @@ import UIKit textFieldContainerView.translatesAutoresizingMaskIntoConstraints = false addSubview(textFieldContainerView) - constrainContent(textFieldContainerView) + constrainTextFieldContent(textFieldContainerView) textFieldContainerView.topAnchor.constraint(equalTo: formLabel.bottomAnchor, constant: 4).isActive = true textContainerLeftPin = textFieldContainerView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) @@ -270,23 +269,24 @@ import UIKit self.placeholderErrorLabel = placeholderErrorLabel placeholderErrorLabel.font = MFStyler.fontForTextFieldUnderLabel() placeholderErrorLabel.textColor = .black - placeholderErrorLabel.setContentHuggingPriority(.required, for: .vertical) + placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) placeholderErrorLabel.setContentCompressionResistancePriority(.required, for: .vertical) addSubview(placeholderErrorLabel) - placeholderErrorLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 15).isActive = true 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 constrainContent(_ parentView: UIView) { + private func constrainTextFieldContent(_ parentView: UIView) { let backgroundView = UIView(frame: .zero) self.backgroundView = backgroundView @@ -510,7 +510,7 @@ import UIKit } if let text = map[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) { - enable(false) + isEnabled(false) } if let errMessage = map[KeyErrorMessage] as? String { @@ -592,7 +592,7 @@ import UIKit } } - public func enable(_ enable: Bool) { + public func isEnabled(_ enable: Bool) { // Set outside the dispatch so that registerAnimations can know about it enabled = enable From d2fddaf769fa4a3c0211e8c4e73402e580733625 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 17 Oct 2019 11:42:34 -0400 Subject: [PATCH 013/112] latest textfield development. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + MVMCoreUI/Atoms/TextFields/DigitTextBox.swift | 27 +-- .../Atoms/TextFields/DigitTextField.swift | 127 ++++++++++---- MVMCoreUI/Atoms/TextFields/MFDigitTextField.m | 4 +- MVMCoreUI/Atoms/TextFields/MdnTextField.swift | 89 ++++------ MVMCoreUI/Atoms/TextFields/TextField.swift | 165 ++++++++++-------- 6 files changed, 238 insertions(+), 178 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 67d0519a..7c1fac20 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; + 0A52C1492357B5380051AECD /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; + 0A52C14A2358B57E0051AECD /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1109,6 +1111,7 @@ D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A52C1492357B5380051AECD /* MdnTextField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -1117,6 +1120,7 @@ D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, + 0A52C14A2358B57E0051AECD /* DigitTextBox.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift index 9bed633c..30b2b16d 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift @@ -8,7 +8,7 @@ import UIKit -@objc protocol MFDigitTextBoxDelegate: NSObjectProtocol { +@objc protocol DigitTextBoxDelegate: NSObjectProtocol { @objc optional func textFieldDidDelete(_ textField: UITextField?) } @@ -23,7 +23,7 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - weak var mfTextBoxDelegate: MFDigitTextBoxDelegate? + weak var textBoxDelegate: DigitTextBoxDelegate? private var previousSize: CGFloat = 0.0 @@ -85,18 +85,18 @@ import UIKit override open func deleteBackward() { super.deleteBackward() - - if let delegate = mfTextBoxDelegate { - delegate.textFieldDidDelete?(self) - } + + textBoxDelegate?.textFieldDidDelete?(self) } public func updateView(_ size: CGFloat) { - DispatchQueue.main.async { - if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(self.previousSize)) { - MFStyler.styleTextField(self) - self.font = MFFonts.mfFont55Rg(28) + DispatchQueue.main.async { [weak self] in + guard let wSelf = self else { return } + + if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(wSelf.previousSize)) { + MFStyler.styleTextField(wSelf) + self?.font = MFFonts.mfFont55Rg(28) var digitWidth: CGFloat = 0 var digitHeight: CGFloat = 0 @@ -115,9 +115,9 @@ import UIKit }) sizeObject?.performBlockBase(onSize: size) - self.widthConstraint?.constant = digitWidth - self.heightConstraint?.constant = digitHeight - self.previousSize = size + self?.widthConstraint?.constant = digitWidth + self?.heightConstraint?.constant = digitHeight + self?.previousSize = size } } } @@ -130,6 +130,7 @@ import UIKit } func hideError() { + layer.borderColor = UIColor.mfSilver().cgColor bottomBar?.setAsMedium() } diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift index c2f5c290..7fff621d 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -8,7 +8,8 @@ import UIKit -@objcMembers open class DigitTextField: TextField, UITextFieldDelegate, MFDigitTextBoxDelegate { + +@objcMembers open class DigitTextField: TextField, UITextFieldDelegate, DigitTextBoxDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -21,6 +22,7 @@ import UIKit private var numberOfDigits = 0 private var switchedAutomatically = false + public var textFields: [DigitTextBox]? //-------------------------------------------------- // MARK: - Constraints @@ -33,43 +35,49 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - func digitField() -> MFDigitTextBox? { - let textField = MFDigitTextBox() - textField.delegate = self - textField.mfTextBoxDelegate = self - return textField + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") } - - class func withNumberOfDigits(_ numberOfDigits: Int) -> Self? { - let field = self.mf() - field?.numberOfDigits = numberOfDigits - field?.buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) - return field + + public init(dinumberOfDigits: Int) { + super.init(frame: .zero) + + numberOfDigits = dinumberOfDigits + buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) } - - class func withNumberOfDigits(_ numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { - let field = self.mfTextField(withBothDelegates: delegate) - field?.numberOfDigits = numberOfDigits - field?.buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) - return field + + public init(numberOfDigits: Int, bothDelegates delegates: (UITextFieldDelegate & MFTextFieldDelegate)?) { + super.init(bothDelegates: delegates as? (TextFieldDelegate & UITextFieldDelegate)) + + self.numberOfDigits = numberOfDigits + buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) } - - class func withNumberOfDigits(_ numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) -> Self? { - let field = self.mfTextField(withBothDelegates: delegate) - field?.numberOfDigits = numberOfDigits - field?.buildTextFieldsView(forSize: size) - return field + + public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { + super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) + + self.numberOfDigits = numberOfDigits + buildTextFieldsView(forSize: size) } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- + func digitField() -> DigitTextBox? { + + let textField = DigitTextBox() + textField.delegate = self + textField.textBoxDelegate = self + return textField + } + func buildTextFieldsView(forSize size: CGFloat) { // Remove all current UI. - if let textFields = textFields, !textFields.isEmpt { - StackableViewController.removeUIViews(self.textFields) + if let textFields = textFields, !textFields.isEmpty { + StackableViewController.remove(textFields) } if numberOfDigits > 0 { @@ -85,9 +93,7 @@ import UIKit textFields.append(textField) } } - self.textFields = textFields as? [UITextField] - - // Layout text boxes. + self.textFields = textFields as? [DigitTextBox] setupTextFieldsView(forSize: size) } else { @@ -97,11 +103,16 @@ import UIKit func setupTextFieldsView(forSize size: CGFloat) { - let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3).getValueBasedOnScreenSize() + + + guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize(), + let textFieldsView = textFieldsView, + let textFields = textFields + else { return } StackableViewController.populateViewHorizontally(textFieldsView, withUIArray: textFields, withSpacingBlock: { object in - let inset = UIEdgeInsets(top: 0, left: space, bottom: 0, right: space) + var inset = UIEdgeInsets(top: 0, left: space, bottom: 0, right: space) if self.textFields?.count == 1 { inset.left = 0 @@ -113,21 +124,67 @@ import UIKit } else if (object as? UITextField) == self.textFields?.last { inset.right = 0 } + return inset }) } - func valueChanged() { + override func valueChanged() { super.valueChanged() - DispatchQueue.main.async { + DispatchQueue.main.async { [weak self] in if self.label.text.length > 0 { - self.labelToTextFieldPin.constant = 10 + self?.labelToTextFieldPin?.constant = 10 } else { - self.labelToTextFieldPin.constant = 0 + self?.labelToTextFieldPin?.constant = 0 } } } + + func updateView(_ size: CGFloat) { + super.updateView(size) + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.formLabel.updateView(size) + + // Remove all current UI. + if (self.textFields?.count ?? 0) > 0 { + StackableViewController.removeUIViews(self.textFields) + } + + // Update text boxes. + for textField in self.textFields ?? [] { + guard let textField = textField as? MFDigitTextBox else { + continue + } + textField.updateView(size) + } + + // Layout text boxes. + self.setupTextFieldsView(forSize: size) + }) + } + + func setupView() { + super.setupView() + formLabel.styleB2(true) + self.formText = "" + alignCenterHorizontal() + } + + // MARK: - Molecule + func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + let digitsNumber = json?.optionalNumber(forKey: "digits") + let digits = digitsNumber != nil ? digitsNumber?.intValue ?? 0 : 4 + if digits != numberOfDigits { + numberOfDigits = digits + buildTextFieldsView(forSize: MVMCoreUIUtility.getWidth()) + } + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 44 + } } diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m b/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m index dcc08512..3e00d6f5 100644 --- a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m +++ b/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m @@ -118,8 +118,6 @@ }]; } -// CONTINUE - - (void)updateView:(CGFloat)size { [super updateView:size]; [MVMCoreDispatchUtility performBlockOnMainThread:^{ @@ -163,6 +161,8 @@ return 44; } +// CONTINUE + #pragma mark - Getters - (NSString *)placeholder { diff --git a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift index 93ee73b7..35ab49d7 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift @@ -21,11 +21,23 @@ import MVMCore 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 //-------------------------------------------------- - open override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: .zero) setup() } @@ -34,7 +46,7 @@ import MVMCore self.init(frame: .zero) } - required init?(coder: NSCoder) { + required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("init(coder:) has not been implemented") } @@ -58,7 +70,7 @@ import MVMCore textField?.inputAccessoryView = toolbar } - // If you're using a MFViewController, you must set this to it + // If you're using a MFViewController, you must set this to it. public override weak var uiTextFieldDelegate: UITextFieldDelegate? { get { return textField?.delegate @@ -79,16 +91,17 @@ import MVMCore func hasValidMdn() -> Bool { - guard let mdn = getMdn() else { return true } + guard let MDN = mdn else { return true } - if mdn.isEmpty { + if MDN.isEmpty { return true - - } else if isNationalMdn { - return MVMCoreUIUtility.validateMDNString(mdn) } - return MVMCoreUIUtility.validateInternationalMDNString(mdn) + if isNationalMdn { + return MVMCoreUIUtility.validateMDNString(MDN) + } + + return MVMCoreUIUtility.validateInternationalMDNString(MDN) } func validateAndColor() -> Bool { @@ -114,18 +127,6 @@ import MVMCore return nil } - func getMdn() -> String? { - - return MVMCoreUIUtility.removeMdnFormat(text! as String) - } - - func setMdn(_ mdn: String?) { - - guard let MDN = mdn else { return } - - text = MVMCoreUIUtility.formatMdn(MDN) - } - @objc func dismissFieldInput(_ sender: Any?) { if let delegate = uiTextFieldDelegate { @@ -149,7 +150,7 @@ import MVMCore // MARK: - ContactPicker Delegate //-------------------------------------------------- - func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { @@ -180,37 +181,29 @@ import MVMCore //-------------------------------------------------- @discardableResult - @objc func textFieldShouldReturn(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() - if let customDelegate = customDelegate { - return customDelegate.textFieldShouldReturn?(textField) ?? false - } - - return true + return customDelegate?.textFieldShouldReturn?(textField) ?? true } - @objc func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false } - if let customDelegate = customDelegate { - return customDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true - } - - return true + return customDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } - func textFieldDidBeginEditing(_ textField: UITextField) { + public func textFieldDidBeginEditing(_ textField: UITextField) { textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) customDelegate?.textFieldDidBeginEditing?(textField) } - func textFieldDidEndEditing(_ textField: UITextField) { + public func textFieldDidEndEditing(_ textField: UITextField) { customDelegate?.textFieldDidEndEditing?(textField) @@ -223,30 +216,18 @@ import MVMCore // MARK: - Passed Along TextField delegate //-------------------------------------------------- - @objc func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - if let customDelegate = customDelegate { - return customDelegate.textFieldShouldBeginEditing?(textField) ?? true - } - - return true + return customDelegate?.textFieldShouldBeginEditing?(textField) ?? true } - @objc func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - if let customDelegate = customDelegate { - return customDelegate.textFieldShouldEndEditing?(textField) ?? true - } - - return true + return customDelegate?.textFieldShouldEndEditing?(textField) ?? true } - @objc func textFieldShouldClear(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { - if let customDelegate = customDelegate { - return customDelegate.textFieldShouldClear?(textField) ?? true - } - - return true + return customDelegate?.textFieldShouldClear?(textField) ?? true } } diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index ad0de57d..ef22419b 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -11,11 +11,11 @@ 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?) + @objc optional func isValid(textfield: TextField?) /// Called when the entered text becomes invalid based on the validation block - @objc optional func isInvalid(_ textfield: TextField?) + @objc optional func isInvalid(textfield: TextField?) /// Dismisses the keyboard. - @objc optional func dismissField(_ sender: Any?) + @objc optional func dismissField(sender: Any?) } @objcMembers open class TextField: ViewConstrainingView { @@ -31,6 +31,11 @@ import UIKit 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? @@ -55,7 +60,7 @@ import UIKit } } - // If you're using a MFViewController, you must set this to it + /// If you're using a MFViewController, you must set this to it public weak var uiTextFieldDelegate: UITextFieldDelegate? { get { return textField?.delegate @@ -69,14 +74,21 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - private var borderPath: UIBezierPath? private var calendar: Calendar? public var pickerView: UIPickerView? public var observingForChanges = false - public var errorShowing = false + public var showError = false public var hasDropDown = false - public var enabled = true - public var hideBorder = 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 { @@ -100,6 +112,7 @@ import UIKit public var placeholderTextColor: UIColor = .black + /// Setgs placeholder text in the textField. public var placeholder: String? { get { guard let attributedPlaceholder = textField?.attributedPlaceholder else { return nil } @@ -113,7 +126,7 @@ import UIKit textField?.attributedPlaceholder = NSAttributedString(string: newPlaceholderText, attributes: [NSAttributedString.Key.foregroundColor: placeholderTextColor]) - if !errorShowing { + if !showError { placeholderErrorLabel?.text = (textField?.text?.count ?? 0) > 0 ? newPlaceholderText : "" } @@ -121,7 +134,7 @@ import UIKit } } - public var formatter: DateFormatter? { + public var formatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .medium @@ -130,7 +143,7 @@ import UIKit formatter.formatterBehavior = .default return formatter - } + }() public var isValid = false public var fieldKey: String? @@ -143,27 +156,7 @@ import UIKit public var enabledTextColor: UIColor? public var disabledTextColor: UIColor? - public var errorMessage: String? { - didSet { - - guard enabled else { return } - - self.separatorHeightConstraint?.constant = 4 - self.errorShowing = true - self.separatorView?.backgroundColor = UIColor.mfPumpkin() - - if let errorMessage = self.errorMessage { - self.placeholderErrorLabel?.text = errorMessage - self.placeholderErrorLabel?.numberOfLines = 0 - self.textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", self.textField?.text ?? "", errorMessage) - } - - DispatchQueue.main.async { - self.setNeedsDisplay() - self.layoutIfNeeded() - } - } - } + public var errorMessage: String? //-------------------------------------------------- // MARK: - Constraints @@ -190,33 +183,32 @@ import UIKit public override init(frame: CGRect) { super.init(frame: frame) - setupView() } - required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("init(coder:) has not been implemented") - } - /// 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 convenience init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - self.init(frame: .zero) - + 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 convenience init(hasDropDown: Bool = false, map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - self.init(frame: .zero) - + 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) @@ -367,15 +359,14 @@ import UIKit open override func updateView(_ size: CGFloat) { super.updateView(size) - DispatchQueue.main.async { [weak self] in - - self?.formLabel?.updateView(size) - self?.placeholderErrorLabel?.font = MFStyler.fontForTextFieldUnderLabel() - if let textField = self?.textField { - MFStyler.styleTextField(textField) - } - self?.dashLine?.updateView(size) + formLabel?.updateView(size) + placeholderErrorLabel?.font = MFStyler.fontForTextFieldUnderLabel() + if let textField = textField { + MFStyler.styleTextField(textField) } + dashLine?.updateView(size) + + layoutIfNeeded() } deinit { @@ -397,7 +388,7 @@ import UIKit borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y + frame.size.height)) borderPath?.lineWidth = 1 - let strokeColor = errorShowing ? UIColor.mfPumpkin() : UIColor.mfSilver() + let strokeColor = showError ? UIColor.mfPumpkin() : UIColor.mfSilver() strokeColor.setStroke() borderPath?.stroke() @@ -428,7 +419,7 @@ import UIKit if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter?.string(from: fromDate) + self.text = formatter.string(from: fromDate) } } @@ -442,7 +433,7 @@ import UIKit if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - text = formatter?.string(from: fromDate) + text = formatter.string(from: fromDate) } } @@ -459,7 +450,7 @@ import UIKit if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - text = formatter?.string(from: pickedDate) + text = formatter.string(from: pickedDate) } } @@ -476,13 +467,43 @@ import UIKit // 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?.errorShowing = false + self?.showError = false self?.placeholderErrorLabel?.textColor = .black self?.placeholderErrorLabel?.text = "" self?.textField?.accessibilityValue = nil @@ -517,6 +538,10 @@ import UIKit 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 @@ -595,7 +620,7 @@ import UIKit public func isEnabled(_ enable: Bool) { // Set outside the dispatch so that registerAnimations can know about it - enabled = enable + isEnabled = enable DispatchQueue.main.async { [weak self] in @@ -607,7 +632,7 @@ import UIKit self?.textField?.textColor = self?.enabledTextColor ?? .black self?.formLabel?.textColor = UIColor.mfBattleshipGrey() self?.placeholderErrorLabel?.textColor = .black - self?.separatorView?.backgroundColor = (self?.errorShowing ?? false) ? UIColor.mfPumpkin() : .black + self?.separatorView?.backgroundColor = (self?.showError ?? false) ? UIColor.mfPumpkin() : .black self?.showDropDown(true) } else { @@ -640,7 +665,7 @@ import UIKit func valueChanged() { // Update label for placeholder - if !errorShowing { + if !showError { placeholderErrorLabel?.text = "" } @@ -651,17 +676,17 @@ import UIKit if previousValidity && !isValid { if let errMessage = errorMessage { - self.errorMessage = errMessage + showErrorMessage(errMessage) } if let mfTextFieldDelegate = mfTextFieldDelegate { - mfTextFieldDelegate.isInvalid?(self) + mfTextFieldDelegate.isInvalid?(textfield: self) } } else if !previousValidity && isValid { hideError() if let mfTextFieldDelegate = mfTextFieldDelegate { - mfTextFieldDelegate.isValid?(self) + mfTextFieldDelegate.isValid?(textfield: self) } } } @@ -672,18 +697,14 @@ import UIKit hideError() separatorView?.backgroundColor = .black } else if let errMessage = errorMessage { - self.errorMessage = errMessage + showErrorMessage(errMessage) } } func startEditing() { textField?.becomeFirstResponder() - - if !errorShowing { - separatorView?.backgroundColor = .black - separatorHeightConstraint?.constant = 1 - } + showErrorDropdown(!showError) } class func getEnabledTextfields(_ textFieldToDetermine: [MFTextField]?) -> [AnyHashable]? { @@ -722,7 +743,7 @@ extension TextField { } } - override open class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 76 } } @@ -730,10 +751,6 @@ extension TextField { // MARK: - Accessibility extension TextField { - // open override class func accessibilityElements() -> [Any]? { - // return [self.textField] - // } - func pushAccessibilityNotification() { DispatchQueue.main.async { [weak self] in From 929b825e117afd5844983fa6f48dddaa18347f46 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 18 Oct 2019 10:26:47 -0400 Subject: [PATCH 014/112] more to uopdate. --- MVMCoreUI.xcodeproj/project.pbxproj | 2 + .../Atoms/TextFields/DigitTextField.swift | 359 ++++++++++++++++-- MVMCoreUI/Atoms/TextFields/MFDigitTextField.m | 2 - 3 files changed, 338 insertions(+), 25 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 7c1fac20..cea24a21 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -19,6 +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 */; }; + 0A21DB6E2359EEF800C160A2 /* DigitTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; 0A52C1492357B5380051AECD /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; @@ -1099,6 +1100,7 @@ 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, + 0A21DB6E2359EEF800C160A2 /* DigitTextField.swift in Sources */, D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift index 7fff621d..79ff733e 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -40,10 +40,10 @@ import UIKit fatalError("init(coder:) has not been implemented") } - public init(dinumberOfDigits: Int) { + public init(numberOfDigits: Int) { super.init(frame: .zero) - numberOfDigits = dinumberOfDigits + self.numberOfDigits = numberOfDigits buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) } @@ -54,7 +54,7 @@ import UIKit buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) } - public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { + public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFie.ldDelegate & MFTextFieldDelegate)?, size: CGFloat) { super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) self.numberOfDigits = numberOfDigits @@ -65,7 +65,7 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - func digitField() -> DigitTextBox? { + func createDigitField() -> DigitTextBox? { let textField = DigitTextBox() textField.delegate = self @@ -82,11 +82,10 @@ import UIKit if numberOfDigits > 0 { - // Create text boxes. var textFields = [AnyHashable](repeating: 0, count: numberOfDigits) for i in 0.. 0 { + if (self?.placeholder!.count)! > 0 { self?.labelToTextFieldPin?.constant = 10 } else { @@ -143,38 +140,42 @@ import UIKit } } - func updateView(_ size: CGFloat) { + open override func updateView(_ size: CGFloat) { super.updateView(size) - MVMCoreDispatchUtility.performBlock(onMainThread: { - self.formLabel.updateView(size) + + DispatchQueue.main.async { [weak self] in + self.formLabel!.updateView(size) // Remove all current UI. if (self.textFields?.count ?? 0) > 0 { - StackableViewController.removeUIViews(self.textFields) + StackableViewController.remove(self.textFields) } // Update text boxes. for textField in self.textFields ?? [] { - guard let textField = textField as? MFDigitTextBox else { - continue - } + guard let textField = textField as? MFDigitTextBox else { continue } textField.updateView(size) } // Layout text boxes. self.setupTextFieldsView(forSize: size) - }) + } } - func setupView() { + open override func setupView() { super.setupView() - formLabel.styleB2(true) + + formLabel?.styleB2(true) self.formText = "" alignCenterHorizontal() } - + + //-------------------------------------------------- // MARK: - Molecule - func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + //-------------------------------------------------- + + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + let digitsNumber = json?.optionalNumber(forKey: "digits") let digits = digitsNumber != nil ? digitsNumber?.intValue ?? 0 : 4 if digits != numberOfDigits { @@ -184,7 +185,319 @@ import UIKit super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } - class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + open override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 44 } + + //-------------------------------------------------- + // MARK: - Getters + //-------------------------------------------------- + + func placeholder() -> String? { + + var string = "" + for textField in textFields ?? [] { + if textField.attributedPlaceholder?.string != nil { + string += textField.attributedPlaceholder?.string ?? "" + } + } + return string.count > 0 ? string : nil + } + + func text() -> String? { + + var string = "" + for textField in textFields ?? [] { + if textField.text != nil { + string += textField.text ?? "" + } + } + return string + } + + //-------------------------------------------------- + // MARK: - Setters + //-------------------------------------------------- + + func setFormText(_ formText: String?) { + + if (formText?.count ?? 0) > 0 { + messageToTextFieldPin?.constant = 10 + } else { + messageToTextFieldPin?.constant = 0 + } + super.setFormText(formText) + } + + func setAsSecureTextEntry(_ secureTextEntry: Bool) { + + DispatchQueue.main.async { [weak self] in + + (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + obj.isSecureTextEntry = secureTextEntry + + //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.textFields?.count ?? 0)) + }) + } + } + + func setPlaceholder(_ placeholder: String?) { + + if placeholder != nil { + (textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + + if idx < (placeholder?.count ?? 0) { + + let stringForIndex = (placeholder 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 text.length > 0 && !errorShowing { + label.text = placeholder + + } else if !errorShowing { + label.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() = placeholder ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular")) + } + + func setErrorMessage(_ errorMessage: String?) { + DispatchQueue.main.async { [weak self] in + super.setErrorMessage(errorMessage) + if self.errorShowing { + self.labelToTextFieldPin?.constant = 10 + } + (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + obj.setAsError() + }) + } + } + + public override func hideError() { + DispatchQueue.main.async { [weak self] in + super.hideError() + (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + obj.hideError() + }) + } + } + + func setText(_ text: String?) { + (textFields 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() + } + + func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { + super.setWithMap(map, bothDelegates: delegate) + if (map?.count ?? 0) > 0 { + for textField in textFields ?? [] { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) + } + } + } + + func setDefaultValidationBlock() { + + weak var weakSelf = self + self.validationBlock = { enteredValue in + if (enteredValue?.count ?? 0) > 0 && (enteredValue?.count ?? 0) == weakSelf?.textFields?.count { + return true + } else { + return false + } + } + } + + func enable(_ enable: Bool) { + super.enable(enable) + + if enable { + formLabel?.styleB2(true) + } else { + formLabel?.textColor = UIColor.mfBattleshipGrey() + } + + for textField in textFields ?? [] { + 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 + (textFields 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 + (textFields 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 (self.textFields) { + return [self.textFields arrayByAddingObject:(MFDigitTextBox *)self.label]; + } else { + return @[self.label]; + } + } + + //-------------------------------------------------- + // MARK: - TextFieldDelegate + //-------------------------------------------------- + + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + + if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) { + return uiTextFieldDelegate!.textFieldShouldReturn!(textField) + } + return 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 + } else { + 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() + } + if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldDidBeginEditing(_:))) { + uiTextFieldDelegate?.textFieldDidBeginEditing(textField) + } + } + + @objc public func textFieldDidEndEditing(_ textField: UITextField) { + if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldDidEndEditing(_:))) { + uiTextFieldDelegate!.textFieldDidEndEditing(textField) + } + } + + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + selectPreviousTextField(textField, clear: false) + + if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) { + return uiTextFieldDelegate?.textFieldShouldClear(textField) + } + return true + } + + // MARK: - Passed Along TextField delegate + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) { + return uiTextFieldDelegate?.textFieldShouldBeginEditing(textField) + } + return true + } + + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) { + return uiTextFieldDelegate?.textFieldShouldEndEditing(textField) + } + return true + } } diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m b/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m index 3e00d6f5..5f76e88b 100644 --- a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m +++ b/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m @@ -161,8 +161,6 @@ return 44; } -// CONTINUE - #pragma mark - Getters - (NSString *)placeholder { From 6c8ced47c9e542cbe259671912b1dc7ad09f361e Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 21 Oct 2019 11:47:13 -0400 Subject: [PATCH 015/112] trying out a new subclass. --- MVMCoreUI.xcodeproj/project.pbxproj | 68 +-- .../Atoms/TextFields/DigitTextField.swift | 317 ++++++----- .../Atoms/TextFields/FieldEntryForm.swift | 466 ++++++++++++++++ .../Atoms/TextFields/MdnEntryField.swift | 232 ++++++++ .../Atoms/TextFields/TextEntryField.swift | 528 ++++++++++++++++++ MVMCoreUI/Atoms/TextFields/TextField.swift | 136 ++--- .../MVMCoreUIMoleculeMappingObject.m | 2 +- 7 files changed, 1505 insertions(+), 244 deletions(-) create mode 100644 MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift create mode 100644 MVMCoreUI/Atoms/TextFields/MdnEntryField.swift create mode 100644 MVMCoreUI/Atoms/TextFields/TextEntryField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index cea24a21..57508395 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -19,16 +19,22 @@ 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 */; }; - 0A21DB6E2359EEF800C160A2 /* DigitTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */; }; + 0A21DB7F235DECC500C160A2 /* FieldEntryForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* FieldEntryForm.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 */; }; + 0A21DB86235E06EF00C160A2 /* MFTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24421E6A176003B2FB9 /* MFTextField.xib */; }; + 0A21DB87235E06EF00C160A2 /* MFTextFieldSubclassExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB88235E06EF00C160A2 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; + 0A21DB8A235E06EF00C160A2 /* MFDigitTextBox.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */; }; + 0A21DB8C235E06EF00C160A2 /* MFDigitTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */; }; + 0A21DB8E235E06EF00C160A2 /* MFDigitTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; - 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; - 0A52C1492357B5380051AECD /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; - 0A52C14A2358B57E0051AECD /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; + 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; - 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; - 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; @@ -104,13 +110,6 @@ D29DF18121E69E50003B2FB9 /* MFView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF17F21E69E2E003B2FB9 /* MFView.m */; }; D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF15921E697DA003B2FB9 /* SeparatorView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF15A21E697DA003B2FB9 /* SeparatorView.m */; }; - D29DF24E21E6A177003B2FB9 /* MFDigitTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF24F21E6A177003B2FB9 /* MFTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24421E6A176003B2FB9 /* MFTextField.xib */; }; - D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */; }; - D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */; }; - D29DF25521E6A177003B2FB9 /* MFDigitTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */; }; - D29DF25621E6A177003B2FB9 /* MFTextFieldSubclassExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26821E6AA0B003B2FB9 /* FLAnimatedImage.m */; }; D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26921E6AA0B003B2FB9 /* FLAnimatedImageView.m */; }; @@ -210,8 +209,11 @@ 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 /* FieldEntryForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldEntryForm.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 = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; - 0A41BA7E23453A6400D4C0BC /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.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 = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; 0A8321A72355062F00CB7F00 /* MdnTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnTextField.swift; sourceTree = ""; }; @@ -757,10 +759,13 @@ D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, - 0A41BA7E23453A6400D4C0BC /* TextField.swift */, + 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, 0A8321A72355062F00CB7F00 /* MdnTextField.swift */, 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */, 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */, + 0A21DB7E235DECC500C160A2 /* FieldEntryForm.swift */, + 0A21DB80235DF87300C160A2 /* TextField.swift */, + 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, ); path = TextFields; sourceTree = ""; @@ -881,27 +886,27 @@ D29DF11C21E684A9003B2FB9 /* MVMCoreUISplitViewController.h in Headers */, D29DF29B21E7ADB9003B2FB9 /* StackableViewController.h in Headers */, D29770F421F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.h in Headers */, - 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */, D29DF15421E69760003B2FB9 /* MVMCoreUIPanelButtonProtocol.h in Headers */, D2A514582211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h in Headers */, D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */, D29DF29A21E7ADB8003B2FB9 /* MFProgrammaticTableViewController.h in Headers */, - D29DF25621E6A177003B2FB9 /* MFTextFieldSubclassExtension.h in Headers */, D29DF11521E6805F003B2FB9 /* UIColor+MFConvenience.h in Headers */, D29DF2BC21E7BEA4003B2FB9 /* TopTabbar.h in Headers */, D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */, D22D1F46220496A30077CEC0 /* MVMCoreUISwitch.h in Headers */, - 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */, D22D1F1E220343560077CEC0 /* MVMCoreUICheckMarkView.h in Headers */, D29DF28421E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.h in Headers */, + 0A21DB87235E06EF00C160A2 /* MFTextFieldSubclassExtension.h in Headers */, D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */, D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */, + 0A21DB84235E06EF00C160A2 /* MFTextField.h in Headers */, D29DF12A21E6851E003B2FB9 /* MVMCoreUITopAlertView.h in Headers */, D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */, D29DF28B21E7AC2B003B2FB9 /* ViewConstrainingView.h in Headers */, D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */, + 0A21DB8A235E06EF00C160A2 /* MFDigitTextBox.h in Headers */, D29DF32521ED0DA2003B2FB9 /* TextButtonView.h in Headers */, - D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */, + 0A21DB8C235E06EF00C160A2 /* MFDigitTextField.h in Headers */, D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */, D29DF2C621E7BF57003B2FB9 /* MFTabBarInteractor.h in Headers */, D29DF17521E69E1F003B2FB9 /* ButtonDelegateProtocol.h in Headers */, @@ -911,6 +916,7 @@ D29DF17721E69E1F003B2FB9 /* MFTextButton.h in Headers */, 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */, D29DF16221E69996003B2FB9 /* MFViewController.h in Headers */, + 0A21DB88235E06EF00C160A2 /* MFMdnTextField.h in Headers */, D29DF13121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h in Headers */, D29DF2C421E7BF57003B2FB9 /* MFTabBarSwipeAnimator.h in Headers */, D2A5145D2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h in Headers */, @@ -920,7 +926,6 @@ D29DF2BD21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.h in Headers */, D29DF2EE21ECEADF003B2FB9 /* MFFonts.h in Headers */, D29DF12D21E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h in Headers */, - D29DF24E21E6A177003B2FB9 /* MFDigitTextField.h in Headers */, D29770F321F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.h in Headers */, D296E1412295EBBA0051EBE7 /* MoleculeDelegateProtocol.h in Headers */, D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */, @@ -1002,12 +1007,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D29DF25521E6A177003B2FB9 /* MFDigitTextField.xib in Resources */, D29DF2AF21E7B3A4003B2FB9 /* MFTextView.xib in Resources */, D29DF31C21ECECC0003B2FB9 /* NHaasGroteskDSStd-75Bd.otf in Resources */, - D29DF24F21E6A177003B2FB9 /* MFTextField.xib in Resources */, D29DF31D21ECECC0003B2FB9 /* NHaasGroteskDSStd-55Rg.otf in Resources */, + 0A21DB8E235E06EF00C160A2 /* MFDigitTextField.xib in Resources */, D29DF32C21EE8736003B2FB9 /* Localizable.strings in Resources */, + 0A21DB86235E06EF00C160A2 /* MFTextField.xib in Resources */, D29DF31A21ECECC0003B2FB9 /* NHaasGroteskDSStd-45Lt.otf in Resources */, D29DF32E21EE8C3D003B2FB9 /* Media.xcassets in Resources */, D29DF31B21ECECC0003B2FB9 /* OCRAExtended.ttf in Resources */, @@ -1033,13 +1038,13 @@ D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, - D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, D224798C231450C8003FCCF9 /* HeadlineBodySwitch.swift in Sources */, D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, + 0A21DB7F235DECC500C160A2 /* FieldEntryForm.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, @@ -1047,9 +1052,9 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, - 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, + 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */, B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, @@ -1058,11 +1063,11 @@ D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, + 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */, - 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, @@ -1070,7 +1075,7 @@ D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, - 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */, + 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */, @@ -1091,6 +1096,7 @@ D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, + 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */, D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */, D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, @@ -1100,20 +1106,19 @@ 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, - 0A21DB6E2359EEF800C160A2 /* DigitTextField.swift in Sources */, D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, + 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, + 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, - D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, - 0A52C1492357B5380051AECD /* MdnTextField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -1122,7 +1127,6 @@ D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, - 0A52C14A2358B57E0051AECD /* DigitTextBox.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift index 79ff733e..0f9d5848 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -14,7 +14,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - private weak var textFieldsView: UIView? + private weak var digitFieldsView: UIView? //-------------------------------------------------- // MARK: - Properties @@ -22,7 +22,97 @@ import UIKit private var numberOfDigits = 0 private var switchedAutomatically = false - public var textFields: [DigitTextBox]? + 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 && !errorShowing { + placeholderErrorLabel.text = newValue + + } else if !errorShowing { + 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 { + (textFields 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 formLabel?.text + } + set { + if let formText = newValue, !formText.isEmpty > 0 { + messageToTextFieldPin?.constant = 10 + } else { + messageToTextFieldPin?.constant = 0 + } + super.formText = newValue + } + } //-------------------------------------------------- // MARK: - Constraints @@ -44,81 +134,84 @@ import UIKit super.init(frame: .zero) self.numberOfDigits = numberOfDigits - buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } public init(numberOfDigits: Int, bothDelegates delegates: (UITextFieldDelegate & MFTextFieldDelegate)?) { super.init(bothDelegates: delegates as? (TextFieldDelegate & UITextFieldDelegate)) self.numberOfDigits = numberOfDigits - buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } - public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFie.ldDelegate & MFTextFieldDelegate)?, size: CGFloat) { + public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) self.numberOfDigits = numberOfDigits - buildTextFieldsView(forSize: size) + buildTextFieldsView(size: size) + } + + private func setup() { + } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - func createDigitField() -> DigitTextBox? { + func createDigitField() -> DigitTextBox { let textField = DigitTextBox() textField.delegate = self textField.textBoxDelegate = self + return textField } - func buildTextFieldsView(forSize size: CGFloat) { + func buildTextFieldsView(size: CGFloat) { // Remove all current UI. - if let textFields = textFields, !textFields.isEmpty { - StackableViewController.remove(textFields) + if let digitFields = digitFields, !digitFields.isEmpty { + StackableViewController.remove(digitFields) } if numberOfDigits > 0 { - var textFields = [AnyHashable](repeating: 0, count: numberOfDigits) + let digitFields = [DigitTextBox](repeating: createDigitField(), count: numberOfDigits) - for i in 0.. 0 { - self?.labelToTextFieldPin?.constant = 10 - + guard let self = self else { return } + + if let placeholder = self.placeholder, !placeholder.isEmpty { + self.labelToTextFieldPin?.constant = 10 + } else { - self?.labelToTextFieldPin?.constant = 0 + self.labelToTextFieldPin?.constant = 0 } } } @@ -144,24 +238,26 @@ import UIKit super.updateView(size) DispatchQueue.main.async { [weak self] in - self.formLabel!.updateView(size) - - // Remove all current UI. - if (self.textFields?.count ?? 0) > 0 { - StackableViewController.remove(self.textFields) + guard let self = self else { return } + + self.formLabel?.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) + } } - - // Update text boxes. - for textField in self.textFields ?? [] { - guard let textField = textField as? MFDigitTextBox else { continue } - textField.updateView(size) - } - + // Layout text boxes. self.setupTextFieldsView(forSize: size) } } - + open override func setupView() { super.setupView() @@ -174,14 +270,17 @@ import UIKit // MARK: - Molecule //-------------------------------------------------- - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - let digitsNumber = json?.optionalNumber(forKey: "digits") - let digits = digitsNumber != nil ? digitsNumber?.intValue ?? 0 : 4 + guard let dictionary = json else { return } + + let digits = dictionary["digits"] as? Int ?? 4 if digits != numberOfDigits { numberOfDigits = digits - buildTextFieldsView(forSize: MVMCoreUIUtility.getWidth()) } + + buildTextFieldsView(size: MVMCoreUIUtility.getWidth()) + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } @@ -190,99 +289,28 @@ import UIKit return 44 } - //-------------------------------------------------- - // MARK: - Getters - //-------------------------------------------------- - - func placeholder() -> String? { - - var string = "" - for textField in textFields ?? [] { - if textField.attributedPlaceholder?.string != nil { - string += textField.attributedPlaceholder?.string ?? "" - } - } - return string.count > 0 ? string : nil - } - - func text() -> String? { - - var string = "" - for textField in textFields ?? [] { - if textField.text != nil { - string += textField.text ?? "" - } - } - return string - } - //-------------------------------------------------- // MARK: - Setters //-------------------------------------------------- - func setFormText(_ formText: String?) { - - if (formText?.count ?? 0) > 0 { - messageToTextFieldPin?.constant = 10 - } else { - messageToTextFieldPin?.constant = 0 - } - super.setFormText(formText) - } - - func setAsSecureTextEntry(_ secureTextEntry: Bool) { + func setAsSecureTextEntry(_ secureEntry: Bool) { DispatchQueue.main.async { [weak self] in - (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - obj.isSecureTextEntry = secureTextEntry + (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.textFields?.count ?? 0)) }) } } - - func setPlaceholder(_ placeholder: String?) { - if placeholder != nil { - (textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - - if idx < (placeholder?.count ?? 0) { - - let stringForIndex = (placeholder 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 text.length > 0 && !errorShowing { - label.text = placeholder - - } else if !errorShowing { - label.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() = placeholder ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular")) - } - - func setErrorMessage(_ errorMessage: String?) { + override public func showErrorMessage(_ errorMessage: String?) { + DispatchQueue.main.async { [weak self] in - super.setErrorMessage(errorMessage) + super.showErrorMessage (errorMessage) + if self.errorShowing { self.labelToTextFieldPin?.constant = 10 } @@ -293,28 +321,19 @@ import UIKit } public override func hideError() { + DispatchQueue.main.async { [weak self] in + super.hideError() (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in obj.hideError() }) } } - - func setText(_ text: String?) { - (textFields 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() - } func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - super.setWithMap(map, bothDelegates: delegate) + super.setWithMap(map, bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) + if (map?.count ?? 0) > 0 { for textField in textFields ?? [] { MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) @@ -325,6 +344,7 @@ import UIKit func setDefaultValidationBlock() { weak var weakSelf = self + self.validationBlock = { enteredValue in if (enteredValue?.count ?? 0) > 0 && (enteredValue?.count ?? 0) == weakSelf?.textFields?.count { return true @@ -346,6 +366,7 @@ import UIKit for textField in textFields ?? [] { textField.isUserInteractionEnabled = enable textField.isEnabled = enable + if enable { textField.textColor = UIColor.black } else { @@ -361,6 +382,7 @@ import UIKit func selectPreviousTextField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false + (textFields as NSArray?)?.enumerateObjects(options: .reverse, using: { obj, idx, stop in if obj == currentTextField { selectNextField = true @@ -382,8 +404,10 @@ import UIKit var selectNextField = false (textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + if obj == currentTextField { selectNextField = true + } else if selectNextField { if !clear { self.switchedAutomatically = true @@ -403,8 +427,9 @@ import UIKit //-------------------------------------------------- open override var accessibilityElements: [Any]? { + if (self.textFields) { - return [self.textFields arrayByAddingObject:(MFDigitTextBox *)self.label]; + return [self.textFields arrayByAddingObject:(DigitTextBox *)self.label]; } else { return @[self.label]; } @@ -457,27 +482,31 @@ import UIKit } 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() } - if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldDidBeginEditing(_:))) { - uiTextFieldDelegate?.textFieldDidBeginEditing(textField) + if (uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldDidBeginEditing(_:))))! { + uiTextFieldDelegate?.textFieldDidBeginEditing!(textField) } } @objc public func textFieldDidEndEditing(_ textField: UITextField) { + if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldDidEndEditing(_:))) { - uiTextFieldDelegate!.textFieldDidEndEditing(textField) + uiTextFieldDelegate!.textFieldDidEndEditing!(textField) } } @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + selectPreviousTextField(textField, clear: false) if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) { @@ -488,6 +517,7 @@ import UIKit // MARK: - Passed Along TextField delegate @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) { return uiTextFieldDelegate?.textFieldShouldBeginEditing(textField) } @@ -495,6 +525,7 @@ import UIKit } @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) { return uiTextFieldDelegate?.textFieldShouldEndEditing(textField) } diff --git a/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift b/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift new file mode 100644 index 00000000..592051f6 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift @@ -0,0 +1,466 @@ +// +// FieldEntryForm.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +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. + */ +open class FieldEntryForm: ViewConstrainingView { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public var backgroundView: UIView? + public var formDescriptionLabel: Label? + public var fieldContainer: UIView? + public var placeholderErrorLabel: Label? + public var separatorView: UIView? + public var dashLine: DashLine? + public var dropDownCaretLabel: UILabel? + + //-------------------------------------------------- + // MARK: - Accessories + //-------------------------------------------------- + + public weak var datePicker: UIDatePicker? + public var pickerView: UIPickerView? + private(set) weak var toolbar: UIToolbar? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var showError = false + public var hasDropDown = false + private var borderPath: UIBezierPath? + public var isEnabled = true + + /// Determines if a border should be drawn. + public var hideBorder = false { + didSet { setNeedsLayout() } + } + + public var formText: String? { + get { return formDescriptionLabel?.text } + set { + formDescriptionLabel?.text = newValue + setAccessibilityString(newValue) + } + } + + // Override this with logic of the textfield(s) that are of focus in this form. + public var text: String? { + get { return "" } + set { } + } + + public var placeholderTextColor: UIColor = .black + + /// Setgs placeholder text in the textField. + public var placeholder: String? { + get { return placeholderErrorLabel?.text } + set { + guard let newPlaceholderText = newValue else { return } + + if !showError { + placeholderErrorLabel?.text = 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 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 formDescriptionLabelLeftPin: NSLayoutConstraint? + public var formDescriptionLabelRightPin: NSLayoutConstraint? + + public var separatorHeightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + /// This must be overriden by a subclass. + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + self.hasDropDown = false + } + + /// This must be overriden by a subclass. + public convenience init() { + self.init(frame: .zero) + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("TextEntryField does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + /// Initial configuration of class and view. + final public override func setupView() { + + guard subviews.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + setContentHuggingPriority(.required, for: .vertical) + setContentCompressionResistancePriority(.required, for: .vertical) + backgroundColor = .clear + + let formDescriptionLabel = Label() + self.formDescriptionLabel = formDescriptionLabel + formDescriptionLabel.font = MFStyler.fontB3() + formDescriptionLabel.textColor = UIColor.mfBattleshipGrey() + formDescriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + formDescriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + formDescriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) + + addSubview(formDescriptionLabel) + + formDescriptionLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + formDescriptionLabelLeftPin = formDescriptionLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + formDescriptionLabelLeftPin?.isActive = true + formDescriptionLabelRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: formDescriptionLabel.trailingAnchor) + formDescriptionLabelRightPin?.isActive = true + + let fieldContainer = UIView(frame: .zero) + self.fieldContainer = fieldContainer + fieldContainer.translatesAutoresizingMaskIntoConstraints = false + + addSubview(fieldContainer) + setupFieldContainer(fieldContainer) + + fieldContainer.topAnchor.constraint(equalTo: formDescriptionLabel.bottomAnchor, constant: 4).isActive = true + textContainerLeftPin = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + textContainerLeftPin?.isActive = true + textContainerRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.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: fieldContainer.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() + } + + /// Method to override. + /// Intended to add the interactive content (textField) to the fieldContainer. + open func fieldContainerContent(_ container: UIView) { + + } + + /// Configuration logic for the text container view. + private func setupFieldContainer(_ parentView: UIView) { + + let backgroundView = UIView(frame: .zero) + self.backgroundView = backgroundView + backgroundView.translatesAutoresizingMaskIntoConstraints = false + + parentView.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)]) + + fieldContainerContent(parentView) + + let separatorView = UIView(frame: .zero) + self.separatorView = separatorView + separatorView.translatesAutoresizingMaskIntoConstraints = false + separatorView.backgroundColor = .black + + parentView.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 + + parentView.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) + + formDescriptionLabel?.updateView(size) + placeholderErrorLabel?.font = MFStyler.fontForTextFieldUnderLabel() + dashLine?.updateView(size) + + layoutIfNeeded() + } + + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath?.removeAllPoints() + + if !hideBorder, let frame = fieldContainer?.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 + //-------------------------------------------------- + + open 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() + } + } + + open 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?.setNeedsDisplay() + self?.layoutIfNeeded() + self?.showErrorDropdown(self?.showError ?? false) + } + } + + 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() + } + } + + 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[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) { + formIsDisabled() + } + + 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 + } + } + + open override func setLeftPinConstant(_ constant: CGFloat) { + + textContainerLeftPin?.constant = constant + errorLableLeftPin?.constant = constant + formDescriptionLabelLeftPin?.constant = constant + } + + open override func setRightPinConstant(_ constant: CGFloat) { + + textContainerRightPin?.constant = constant + errorLableRightPin?.constant = constant + formDescriptionLabelRightPin?.constant = constant + } + + public func showDropDown(_ show: Bool) { + + if hasDropDown { + dropDownCaretLabel?.isHidden = !show + dropDownCarrotWidth?.isActive = !show + setNeedsLayout() + layoutIfNeeded() + } + } + + open func formIsEnabled() { + + // Set outside the dispatch so that registerAnimations can know about it + 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) + } + } + + open func formIsDisabled() { + + // Set outside the dispatch so that registerAnimations can know about it + 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() + } + } + + open func showPlaceholderErrorLabel(_ show: Bool) { + + placeholderErrorLabel?.isHidden = !show + } + + open func showDashSeperatorView(_ 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: - Molecular +extension FieldEntryForm { + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 76 + } +} + +// MARK: - Form Validation +extension FieldEntryForm: FormValidationProtocol { + + public func isValidField() -> Bool { + return isValid + } + + public func formFieldName() -> String? { + return fieldKey + } + + public func formFieldValue() -> Any? { + return text + } +} + +// MARK: - Accessibility +extension FieldEntryForm { + + @objc open func pushAccessibilityNotification() { + // To Be Overriden + } + + /** + Adding missing accessibilityLabel value + if we have some value in accessibilityLabel, + then only can append regular and picker item + */ + @objc open func setAccessibilityString(_ accessibilityString: String?) { + // To Be Overriden + } +} diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift new file mode 100644 index 00000000..e4f68a6a --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -0,0 +1,232 @@ +// +// MdnEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import AddressBookUI +import ContactsUI +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() { + + 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 +} + +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) +} + +//-------------------------------------------------- +// MARK: - ContactPicker Delegate +//-------------------------------------------------- + +public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + + if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { + + 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() + + 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 + } + + return customDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true +} + +public func textFieldDidBeginEditing(_ textField: UITextField) { + + textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + customDelegate?.textFieldDidBeginEditing?(textField) +} + +public func textFieldDidEndEditing(_ textField: UITextField) { + + customDelegate?.textFieldDidEndEditing?(textField) + + 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 +} +} diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift new file mode 100644 index 00000000..170aefc8 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -0,0 +1,528 @@ +// +// 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: TextEntryField?) + /// Called when the entered text becomes invalid based on the validation block + @objc optional func isInvalid(textfield: TextEntryField?) + /// Dismisses the keyboard. + @objc optional func dismissField(sender: Any?) +} + + +@objcMembers open class TextEntryField: FieldEntryForm { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public var textField: UITextField? + + private var calendar: Calendar? + + //-------------------------------------------------- + // 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 + //-------------------------------------------------- + + public var observingForChanges = false + + private var borderPath: UIBezierPath? + + // The text of this textField. + public override var text: String? { + get { return textField?.text } + set { + textField?.text = newValue + valueChanged() + } + } + + public override var formText: String? { + get { return formDescriptionLabel?.text } + set { + formDescriptionLabel?.text = newValue + setAccessibilityString(newValue) + } + } + + /// Sets 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 validationBlock: ((_ enteredValue: String?) -> Bool)? { + didSet { + valueChanged() + } + } + + //-------------------------------------------------- + // 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 & TextEntryFieldDelegate)?) { + 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() + dropDownCaretLabel?.isHidden = hasDropDown + self.hasDropDown = !hasDropDown + setWithMap(map, bothDelegates: bothDelegates) + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func fieldContainerContent(_ container: UIView) { + + 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) + + container.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 dropDownCaretLabel = Label() + self.dropDownCaretLabel = dropDownCaretLabel + dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) + dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + dropDownCaretLabel.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) + dropDownCaretLabel.isHidden = true + dropDownCaretLabel.isUserInteractionEnabled = true + let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCaretLabel.addGestureRecognizer(tapOnCarrot) + + container.addSubview(dropDownCaretLabel) + + dropDownCaretLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true + dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6).isActive = true + container.trailingAnchor.constraint(equalTo: dropDownCaretLabel.trailingAnchor, constant: 16).isActive = true + container.bottomAnchor.constraint(equalTo: dropDownCaretLabel.bottomAnchor).isActive = true + dropDownCarrotWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) + dropDownCarrotWidth?.isActive = true + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + if let textField = textField { + MFStyler.styleTextField(textField) + } + + layoutIfNeeded() + } + + deinit { + mfTextFieldDelegate = nil + uiTextFieldDelegate = nil + } + + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath?.removeAllPoints() + + if !hideBorder, let frame = fieldContainer?.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 + //-------------------------------------------------- + + 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() + + textField?.accessibilityValue = nil + } + + public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + + mfTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + + public override func setWithMap(_ map: [AnyHashable: Any]?) { + super.setWithMap(map) + + 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) { + formIsDisabled() + } + + 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 + + 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 formIsEnabled() { + super.formIsEnabled() + + textField?.isUserInteractionEnabled = true + textField?.isEnabled = true + } + + open override func formIsDisabled() { + super.formIsDisabled() + + textField?.isUserInteractionEnabled = false + textField?.isEnabled = false + } + + //-------------------------------------------------- + // 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: [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 +extension TextEntryField { + + open override 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 + */ + open override 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") ?? "")" + } + +} diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index ef22419b..1fcdbdc6 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -395,74 +395,6 @@ import UIKit } } - //-------------------------------------------------- - // MARK: - Date Picker - //-------------------------------------------------- - - 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: - Methods //-------------------------------------------------- @@ -721,6 +653,74 @@ import UIKit } } +// 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 { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index ffafdc82..d368cbfc 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -37,7 +37,7 @@ @"caretView": CaretView.class, @"caretButton": CaretButton.class, @"textField" : TextField.class, - @"digitTextField" : MFDigitTextField.class, + @"digitTextField" : DigitTextField.class, @"checkbox" : Checkbox.class, @"checkboxWithLabelView" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, From 8d11e28f4ea8b15904976736990bb5e943975838 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 21 Oct 2019 12:11:58 -0400 Subject: [PATCH 016/112] promising state. --- MVMCoreUI.xcodeproj/project.pbxproj | 10 ++++++---- ...tryForm.swift => FieldEntryFormView.swift} | 13 +++++++------ .../Atoms/TextFields/TextEntryField.swift | 19 +++++++------------ MVMCoreUI/Atoms/TextFields/TextField.swift | 1 + .../MVMCoreUIMoleculeMappingObject.m | 4 ++-- 5 files changed, 23 insertions(+), 24 deletions(-) rename MVMCoreUI/Atoms/TextFields/{FieldEntryForm.swift => FieldEntryFormView.swift} (98%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 57508395..644e5488 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 /* FieldEntryForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* FieldEntryForm.swift */; }; + 0A21DB7F235DECC500C160A2 /* FieldEntryFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* FieldEntryFormView.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 */; }; @@ -32,6 +32,7 @@ 0A21DB8C235E06EF00C160A2 /* MFDigitTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; 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 */; }; @@ -209,7 +210,7 @@ 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 /* FieldEntryForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldEntryForm.swift; sourceTree = ""; }; + 0A21DB7E235DECC500C160A2 /* FieldEntryFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldEntryFormView.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 = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; @@ -763,7 +764,7 @@ 0A8321A72355062F00CB7F00 /* MdnTextField.swift */, 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */, 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */, - 0A21DB7E235DECC500C160A2 /* FieldEntryForm.swift */, + 0A21DB7E235DECC500C160A2 /* FieldEntryFormView.swift */, 0A21DB80235DF87300C160A2 /* TextField.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, ); @@ -1044,10 +1045,11 @@ D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, - 0A21DB7F235DECC500C160A2 /* FieldEntryForm.swift in Sources */, + 0A21DB7F235DECC500C160A2 /* FieldEntryFormView.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, + 0A21DB91235E0EDB00C160A2 /* DigitTextBox.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift b/MVMCoreUI/Atoms/TextFields/FieldEntryFormView.swift similarity index 98% rename from MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift rename to MVMCoreUI/Atoms/TextFields/FieldEntryFormView.swift index 592051f6..53a3cda1 100644 --- a/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift +++ b/MVMCoreUI/Atoms/TextFields/FieldEntryFormView.swift @@ -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. */ -open class FieldEntryForm: ViewConstrainingView { +@objcMembers open class FieldEntryFormView: ViewConstrainingView { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -202,7 +202,7 @@ open class FieldEntryForm: ViewConstrainingView { /// Method to override. /// Intended to add the interactive content (textField) to the fieldContainer. - open func fieldContainerContent(_ container: UIView) { + open func setupFieldContainerContent(_ container: UIView) { } @@ -221,7 +221,7 @@ open class FieldEntryForm: ViewConstrainingView { parentView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: 1), parentView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: 1)]) - fieldContainerContent(parentView) + setupFieldContainerContent(parentView) let separatorView = UIView(frame: .zero) self.separatorView = separatorView @@ -421,10 +421,11 @@ open class FieldEntryForm: ViewConstrainingView { } // MARK: - Molecular -extension FieldEntryForm { +extension FieldEntryFormView { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + setWithMap(json) } override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { @@ -433,7 +434,7 @@ extension FieldEntryForm { } // MARK: - Form Validation -extension FieldEntryForm: FormValidationProtocol { +extension FieldEntryFormView: FormValidationProtocol { public func isValidField() -> Bool { return isValid @@ -449,7 +450,7 @@ extension FieldEntryForm: FormValidationProtocol { } // MARK: - Accessibility -extension FieldEntryForm { +extension FieldEntryFormView { @objc open func pushAccessibilityNotification() { // To Be Overriden diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 170aefc8..a03367ec 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -19,7 +19,7 @@ import UIKit } -@objcMembers open class TextEntryField: FieldEntryForm { +@objcMembers open class TextEntryField: FieldEntryFormView { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -83,7 +83,7 @@ import UIKit } /// Sets placeholder text in the textField. - public var placeholder: String? { + public override var placeholder: String? { get { guard let attributedPlaceholder = textField?.attributedPlaceholder else { return nil } return attributedPlaceholder.string @@ -130,7 +130,7 @@ import UIKit } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public init(bothDelegates: (UITextFieldDelegate & TextEntryFieldDelegate)?) { + public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) setupView() setBothTextFieldDelegates(bothDelegates) @@ -151,7 +151,7 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - open override func fieldContainerContent(_ container: UIView) { + open override func setupFieldContainerContent(_ container: UIView) { let textField = UITextField(frame: .zero) self.textField = textField @@ -167,9 +167,9 @@ import UIKit container.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)]) + textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 10), + textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), + container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)]) let dropDownCaretLabel = Label() self.dropDownCaretLabel = dropDownCaretLabel @@ -504,11 +504,6 @@ extension TextEntryField { } } - /** - Adding missing accessibilityLabel value - if we have some value in accessibilityLabel, - then only can append regular and picker item - */ open override func setAccessibilityString(_ accessibilityString: String?) { guard let textField = textField else { return } diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index 1fcdbdc6..3418b757 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -214,6 +214,7 @@ import UIKit setWithMap(map, bothDelegates: bothDelegates) } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index d368cbfc..c57dd121 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -36,8 +36,8 @@ @"footer": StandardFooterView.class, @"caretView": CaretView.class, @"caretButton": CaretButton.class, - @"textField" : TextField.class, - @"digitTextField" : DigitTextField.class, + @"textField" : TextEntryField.class, +// @"digitTextField" : DigitTextField.class, @"checkbox" : Checkbox.class, @"checkboxWithLabelView" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, From 2ce41ad74bb5b3b40afe8083da0ae2d977d3b72d Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 22 Oct 2019 09:52:12 -0400 Subject: [PATCH 017/112] moar texht. --- MVMCoreUI.xcodeproj/project.pbxproj | 12 +- .../Atoms/TextFields/DigitEntryField.swift | 528 ++++++++++++++++++ .../Atoms/TextFields/DigitTextField.swift | 4 - ...tryFormView.swift => FormEntryField.swift} | 84 +-- .../Atoms/TextFields/MdnEntryField.swift | 365 ++++++------ .../Atoms/TextFields/TextEntryField.swift | 6 +- 6 files changed, 765 insertions(+), 234 deletions(-) create mode 100644 MVMCoreUI/Atoms/TextFields/DigitEntryField.swift rename MVMCoreUI/Atoms/TextFields/{FieldEntryFormView.swift => FormEntryField.swift} (87%) 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) } } From 74abef0c30bcce98ff96c385e568c8f3f4561b71 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 23 Oct 2019 13:59:10 -0400 Subject: [PATCH 018/112] saving state. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../Atoms/TextFields/DropdownEntryField.swift | 483 ++++++++++++++++++ .../Atoms/TextFields/FormEntryField.swift | 343 +++++-------- .../Atoms/TextFields/TextEntryField.swift | 119 ++--- 4 files changed, 652 insertions(+), 297 deletions(-) create mode 100644 MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 83e2b9a1..5c31b47b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; + 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; @@ -217,6 +218,7 @@ 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; + 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownEntryField.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; 0A8321A72355062F00CB7F00 /* MdnTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnTextField.swift; sourceTree = ""; }; @@ -770,6 +772,7 @@ 0A21DB80235DF87300C160A2 /* TextField.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, + 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */, ); path = TextFields; sourceTree = ""; @@ -1125,6 +1128,7 @@ D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift new file mode 100644 index 00000000..005c5e8e --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -0,0 +1,483 @@ +// +// DropdownEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class DropdownEntryField: TextEntryField { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + private var calendar: Calendar? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var dropDownCaretLabel: UILabel? + + public var enabledTextColor: UIColor? + public var disabledTextColor: UIColor? + + public var observingForChanges = false + + private var borderPath: UIBezierPath? + + public override var isEnabled: Bool { + didSet { + + } + } + + // The text of this textField. + public override var text: String? { + get { return textField?.text } + set { + textField?.text = newValue + valueChanged() + } + } + + public var validationBlock: ((_ enteredValue: String?) -> Bool)? { + didSet { + valueChanged() + } + } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + public var dropDownCaretWidth: 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() + dropDownCaretLabel?.isHidden = hasDropDown + self.hasDropDown = !hasDropDown + setWithMap(map, bothDelegates: bothDelegates) + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setupFieldContainerContent(_ container: UIView) { + + 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) + + container.addSubview(textField) + + NSLayoutConstraint.activate([ + textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 10), + textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), + container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)]) + + let dropDownCaretLabel = Label() + self.dropDownCaretLabel = dropDownCaretLabel + dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) + dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + dropDownCaretLabel.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) + dropDownCaretLabel.isHidden = true + dropDownCaretLabel.isUserInteractionEnabled = true + let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCaretLabel.addGestureRecognizer(tapOnCarrot) + + container.addSubview(dropDownCaretLabel) + + dropDownCaretLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true + dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6).isActive = true + container.trailingAnchor.constraint(equalTo: dropDownCaretLabel.trailingAnchor, constant: 16).isActive = true + container.bottomAnchor.constraint(equalTo: dropDownCaretLabel.bottomAnchor).isActive = true + dropDownCarrotWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) + dropDownCarrotWidth?.isActive = true + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + if let textField = textField { + MFStyler.styleTextField(textField) + } + + layoutIfNeeded() + } + + deinit { + mfTextFieldDelegate = nil + uiTextFieldDelegate = nil + } + + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath?.removeAllPoints() + + if !hideBorder, let frame = fieldContainer?.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 + //-------------------------------------------------- + + 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() + + textField?.accessibilityValue = nil + } + + public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + + mfTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + + public override func setWithMap(_ map: [AnyHashable: Any]?) { + super.setWithMap(map) + + 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) { + formIsDisabled() + } + + 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 + + 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 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 = "" + } + + 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: [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 +extension TextEntryField { + + open override func pushAccessibilityNotification() { + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + UIAccessibility.post(notification: .layoutChanged, argument: self.textField) + } + } + + open override 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") ?? "")" + } + +} diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index f3fce186..1f92bcd7 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -8,7 +8,6 @@ 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. @@ -18,13 +17,13 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- + private(set) var descriptionLabel: Label? + private(set) var feedbackLabel: Label? + private(set) var fieldContainer: UIView? + public var backgroundView: UIView? - public var formDescriptionLabel: Label? - public var fieldContainer: UIView? - public var placeholderErrorLabel: Label? public var separatorView: UIView? public var dashLine: DashLine? - public var dropDownCaretLabel: UILabel? //-------------------------------------------------- // MARK: - Accessories @@ -38,43 +37,67 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - public var showError = false - public var hasDropDown = false + public var isValid = false + public var fieldKey: String? + private var borderPath: UIBezierPath? - public var isEnabled = true + + public var errorMessage: String? + public var showErrorMessage = false + + public var isEnabled = true { + didSet { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.isUserInteractionEnabled = self.isEnabled + self.descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() + self.feedbackLabel?.textColor = self.isEnabled ? .black : UIColor.mfSilver() + self.separatorView?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black + } + } + } /// Determines if a border should be drawn. public var hideBorder = false { - didSet { setNeedsLayout() } + didSet { setNeedsDisplay() } } - public var formText: String? { - get { return formDescriptionLabel?.text } + public var descriptionText: String? { + get { return descriptionLabel?.text } set { - formDescriptionLabel?.text = newValue + descriptionLabel?.text = newValue setAccessibilityString(newValue) } } - // Override this with logic of the textfield(s) that are of focus in this form. + /// Override this to conveniently get/set the textfield(s). public var text: String? { - get { return "" } - set { } + get { return nil } + set { + fatalError("You're supposed to override FormEntryField's 'text' variable.") + } } - public var placeholderTextColor: UIColor = .black - - /// Setgs placeholder text in the textField. - public var placeholder: String? { - get { return placeholderErrorLabel?.text } + /// Sets feedback text in the textField. + public var feedback: String? { + get { return feedbackLabel?.text } set { - guard let newPlaceholderText = newValue else { return } + guard isEnabled, + let newFeedback = newValue + else { return } - if !showError { - placeholderErrorLabel?.text = newPlaceholderText + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.feedbackLabel?.text = newFeedback + self.separatorHeightConstraint?.constant = self.showErrorMessage ? 4 : 1 + self.separatorView?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black + self.setNeedsDisplay() + self.layoutIfNeeded() } - setAccessibilityString(newPlaceholderText) + setAccessibilityString(newFeedback) } } @@ -89,30 +112,18 @@ import UIKit return formatter }() - public var isValid = false - public var fieldKey: String? - - public var enabledTextColor: UIColor? - public var disabledTextColor: UIColor? - - public var errorMessage: String? - //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- - public var heightConstraint: NSLayoutConstraint? + public var textContainerLeading: NSLayoutConstraint? + public var textContainerTrailing: NSLayoutConstraint? - public var dropDownCarrotWidth: NSLayoutConstraint? + public var errorLabelTrailing: NSLayoutConstraint? + public var errorLabelLeading: NSLayoutConstraint? - public var textContainerLeftPin: NSLayoutConstraint? - public var textContainerRightPin: NSLayoutConstraint? - - public var errorLableRightPin: NSLayoutConstraint? - public var errorLableLeftPin: NSLayoutConstraint? - - public var formDescriptionLabelLeftPin: NSLayoutConstraint? - public var formDescriptionLabelRightPin: NSLayoutConstraint? + public var descriptionLabelLeading: NSLayoutConstraint? + public var descriptionLabelTrailing: NSLayoutConstraint? public var separatorHeightConstraint: NSLayoutConstraint? @@ -123,8 +134,8 @@ import UIKit /// This must be overriden by a subclass. public override init(frame: CGRect) { super.init(frame: frame) + setupView() - self.hasDropDown = false } /// This must be overriden by a subclass. @@ -134,7 +145,7 @@ import UIKit required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("TextEntryField does not support xib.") + fatalError("FormEntryField does not support xib.") } //-------------------------------------------------- @@ -151,21 +162,21 @@ import UIKit setContentCompressionResistancePriority(.required, for: .vertical) backgroundColor = .clear - let formDescriptionLabel = Label() - self.formDescriptionLabel = formDescriptionLabel - formDescriptionLabel.font = MFStyler.fontB3() - formDescriptionLabel.textColor = UIColor.mfBattleshipGrey() - formDescriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) - formDescriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) - formDescriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) + let descriptionLabel = Label() + self.descriptionLabel = descriptionLabel + descriptionLabel.font = MFStyler.fontB3() + descriptionLabel.textColor = UIColor.mfBattleshipGrey() + descriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + descriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + descriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) - addSubview(formDescriptionLabel) + addSubview(descriptionLabel) - formDescriptionLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true - formDescriptionLabelLeftPin = formDescriptionLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - formDescriptionLabelLeftPin?.isActive = true - formDescriptionLabelRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: formDescriptionLabel.trailingAnchor) - formDescriptionLabelRightPin?.isActive = true + descriptionLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + descriptionLabelLeading = descriptionLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + descriptionLabelLeading?.isActive = true + descriptionLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor) + descriptionLabelLeading?.isActive = true let fieldContainer = UIView(frame: .zero) self.fieldContainer = fieldContainer @@ -174,39 +185,41 @@ import UIKit addSubview(fieldContainer) setupFieldContainer(fieldContainer) - fieldContainer.topAnchor.constraint(equalTo: formDescriptionLabel.bottomAnchor, constant: 4).isActive = true - textContainerLeftPin = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - textContainerLeftPin?.isActive = true - textContainerRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) - textContainerRightPin?.isActive = true + fieldContainer.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 4).isActive = true + textContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + textContainerLeading?.isActive = true + textContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) + textContainerTrailing?.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) + let feedbackLabel = Label() + self.feedbackLabel = feedbackLabel + feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() + feedbackLabel.textColor = .black + feedbackLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + feedbackLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + feedbackLabel.setContentCompressionResistancePriority(.required, for: .vertical) - addSubview(placeholderErrorLabel) + addSubview(feedbackLabel) - placeholderErrorLabel.topAnchor.constraint(equalTo: fieldContainer.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 + feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor).isActive = true + errorLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + errorLabelLeading?.isActive = true + errorLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) + errorLabelTrailing?.isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true setNeedsLayout() } - /// Method to override. - /// Intended to add the interactive content (textField) to the fieldContainer. + /** + Method to override. + Intended to add the interactive content (textField) to the fieldContainer. + */ open func setupFieldContainerContent(_ container: UIView) { - // To Be Overridden + // To Be Overridden By Subclass. } - /// Configuration logic for the text container view. + /// Configuration for the field container view. private func setupFieldContainer(_ parentView: UIView) { let backgroundView = UIView(frame: .zero) @@ -253,13 +266,17 @@ import UIKit open override func updateView(_ size: CGFloat) { super.updateView(size) - formDescriptionLabel?.updateView(size) - placeholderErrorLabel?.font = MFStyler.fontForTextFieldUnderLabel() + descriptionLabel?.updateView(size) + feedbackLabel?.font = MFStyler.fontForTextFieldUnderLabel() dashLine?.updateView(size) layoutIfNeeded() } + //-------------------------------------------------- + // MARK: - Drawing + //-------------------------------------------------- + open override func draw(_ rect: CGRect) { super.draw(rect) @@ -274,7 +291,7 @@ import UIKit 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() + let strokeColor = showErrorMessage ? UIColor.mfPumpkin() : UIColor.mfSilver() strokeColor.setStroke() borderPath?.stroke() @@ -282,142 +299,30 @@ import UIKit } //-------------------------------------------------- - // MARK: - Methods + // MARK: - Constraint Methods //-------------------------------------------------- - 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() - } - } - - open func showErrorMessage(_ errorMessage: String?) { - - 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) - } - } - - open func hideError() { - - DispatchQueue.main.async { [weak self] in - 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() - } - } - - 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[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) { - formIsDisabled() - } - - 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 - } - } - open override func setLeftPinConstant(_ constant: CGFloat) { - textContainerLeftPin?.constant = constant - errorLableLeftPin?.constant = constant - formDescriptionLabelLeftPin?.constant = constant + textContainerLeading?.constant = constant + errorLabelLeading?.constant = constant + descriptionLabelLeading?.constant = constant } open override func setRightPinConstant(_ constant: CGFloat) { - textContainerRightPin?.constant = constant - errorLableRightPin?.constant = constant - formDescriptionLabelRightPin?.constant = constant + textContainerTrailing?.constant = constant + errorLabelTrailing?.constant = constant + descriptionLabelTrailing?.constant = constant } - public func showDropDown(_ show: Bool) { - - if hasDropDown { - dropDownCaretLabel?.isHidden = !show - dropDownCarrotWidth?.isActive = !show - setNeedsLayout() - layoutIfNeeded() - } - } - - open func formIsEnabled() { - - // Set outside the dispatch so that registerAnimations can know about it - isEnabled = true - - DispatchQueue.main.async { [weak self] in - 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) - } - } - - open func formIsDisabled() { - - // Set outside the dispatch so that registerAnimations can know about it - isEnabled = false - - DispatchQueue.main.async { [weak self] in - 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() - } - } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- open func showPlaceholderErrorLabel(_ show: Bool) { - placeholderErrorLabel?.isHidden = !show + feedbackLabel?.isHidden = !show } open func showDashSeperatorView(_ dash: Bool) { @@ -433,7 +338,31 @@ extension FormEntryField { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - setWithMap(json) + + guard let dictionary = json, + !dictionary.isEmpty + else { return } + + if let formText = dictionary[KeyLabel] as? String { + self.descriptionText = formText + } + + if let text = dictionary[KeyDisable] as? String, text.isEqual(StringY) || dictionary.boolForKey(KeyDisable) { + isEnabled = false + } + + if let errMessage = dictionary[KeyErrorMessage] as? String { + self.errorMessage = errMessage + } + + if let hideBorder = dictionary["hideBorder"] as? Bool { + self.hideBorder = hideBorder + } + + // Key used to send text value to server + if let fieldKey = dictionary[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } } override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index b943beea..82d27df1 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -24,8 +24,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - public var textField: UITextField? - + private(set) var textField: UITextField? private var calendar: Calendar? //-------------------------------------------------- @@ -52,18 +51,27 @@ import UIKit /// 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 - } + set { textField?.delegate = newValue } } //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + public var enabledTextColor: UIColor? + public var disabledTextColor: UIColor? + public var observingForChanges = false - private var borderPath: UIBezierPath? + public override var isEnabled: Bool { + didSet { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.textField?.textColor = self.isEnabled ? self.enabledTextColor : self.enabledTextColor + } + } + } // The text of this textField. public override var text: String? { @@ -74,37 +82,7 @@ import UIKit } } - public override var formText: String? { - get { return formDescriptionLabel?.text } - set { - formDescriptionLabel?.text = newValue - setAccessibilityString(newValue) - } - } - - /// Sets placeholder text in the textField. - public override 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 validationBlock: ((_ enteredValue: String?) -> Bool)? { + public var validationBlock: ((_ value: String?) -> Bool)? { didSet { valueChanged() } @@ -126,7 +104,7 @@ import UIKit required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("init(coder:) has not been implemented") + fatalError("TextEntryField does not support xib.") } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. @@ -136,17 +114,6 @@ import UIKit 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() - dropDownCaretLabel?.isHidden = hasDropDown - self.hasDropDown = !hasDropDown - setWithMap(map, bothDelegates: bothDelegates) - } - //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -170,25 +137,6 @@ 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)]) - - let dropDownCaretLabel = Label() - self.dropDownCaretLabel = dropDownCaretLabel - dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) - dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) - dropDownCaretLabel.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) - dropDownCaretLabel.isHidden = true - dropDownCaretLabel.isUserInteractionEnabled = true - let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) - dropDownCaretLabel.addGestureRecognizer(tapOnCarrot) - - container.addSubview(dropDownCaretLabel) - - dropDownCaretLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true - dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6).isActive = true - container.trailingAnchor.constraint(equalTo: dropDownCaretLabel.trailingAnchor, constant: 16).isActive = true - container.bottomAnchor.constraint(equalTo: dropDownCaretLabel.bottomAnchor).isActive = true - dropDownCarrotWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) - dropDownCarrotWidth?.isActive = true } open override func updateView(_ size: CGFloat) { @@ -206,31 +154,20 @@ import UIKit uiTextFieldDelegate = nil } - open override func draw(_ rect: CGRect) { - super.draw(rect) - - borderPath?.removeAllPoints() - - if !hideBorder, let frame = fieldContainer?.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 //-------------------------------------------------- + public func showDropDown(_ show: Bool) { + + if hasDropDown { + dropDownCaretLabel?.isHidden = !show + dropDownCarrotWidth?.isActive = !show + setNeedsLayout() + layoutIfNeeded() + } + } + open override func showErrorMessage(_ errorMessage: String?) { super.showErrorMessage(errorMessage) @@ -327,6 +264,7 @@ import UIKit textField?.isUserInteractionEnabled = true textField?.isEnabled = true + showDropDown(true) } open override func formIsDisabled() { @@ -334,6 +272,7 @@ import UIKit textField?.isUserInteractionEnabled = false textField?.isEnabled = false + self.showDropDown(false) } //-------------------------------------------------- @@ -344,7 +283,7 @@ import UIKit // Update label for placeholder if !showError { - placeholderErrorLabel?.text = "" + feedbackLabel?.text = "" } let previousValidity = isValid From a5ffae9edd58b64ef92c11e9bee1de1d0bc059fd Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 23 Oct 2019 15:15:23 -0400 Subject: [PATCH 019/112] Latest state. --- MVMCoreUI.xcodeproj/project.pbxproj | 2 - .../Atoms/TextFields/DigitEntryField.swift | 4 +- .../Atoms/TextFields/DropdownEntryField.swift | 6 +- .../Atoms/TextFields/FormEntryField.swift | 23 +- .../Atoms/TextFields/MdnEntryField.swift | 46 +- .../Atoms/TextFields/TextEntryField.swift | 337 +++----- MVMCoreUI/Atoms/TextFields/TextField.swift | 799 ------------------ 7 files changed, 130 insertions(+), 1087 deletions(-) delete mode 100644 MVMCoreUI/Atoms/TextFields/TextField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 5c31b47b..8cf0af96 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -213,7 +213,6 @@ 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 /* 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 = ""; }; @@ -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 */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 346c1c9c..20b9965d 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -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 = "" diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 005c5e8e..2448ccef 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -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() { diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 1f92bcd7..cd1042b3 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -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) } } diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index ff4316e9..7d2ad7a7 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -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 diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 82d27df1..63a5ce1b 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -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") ?? "")" } - } diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift deleted file mode 100644 index 3418b757..00000000 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ /dev/null @@ -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 - } -} From 393b3f355b43c0fc8bb871f44f2262f357493169 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 24 Oct 2019 09:08:53 -0400 Subject: [PATCH 020/112] lztest greatest. --- MVMCoreUI.xcodeproj/project.pbxproj | 12 +- .../{DigitTextBox.swift => DigitBox.swift} | 22 +- .../Atoms/TextFields/DigitEntryField.swift | 4 +- .../Atoms/TextFields/DigitTextField.swift | 530 ------------------ .../Atoms/TextFields/DropdownEntryField.swift | 91 +-- .../Atoms/TextFields/MdnEntryField.swift | 39 +- MVMCoreUI/Atoms/TextFields/MdnTextField.swift | 233 -------- .../Atoms/TextFields/TextEntryField.swift | 5 + 8 files changed, 37 insertions(+), 899 deletions(-) rename MVMCoreUI/Atoms/TextFields/{DigitTextBox.swift => DigitBox.swift} (86%) delete mode 100644 MVMCoreUI/Atoms/TextFields/DigitTextField.swift delete mode 100644 MVMCoreUI/Atoms/TextFields/MdnTextField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 8cf0af96..3eb70209 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -32,7 +32,7 @@ 0A21DB8C235E06EF00C160A2 /* MFDigitTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; + 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitBox.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 */; }; @@ -220,9 +220,7 @@ 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownEntryField.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; - 0A8321A72355062F00CB7F00 /* MdnTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnTextField.swift; sourceTree = ""; }; - 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitTextField.swift; sourceTree = ""; }; - 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitTextBox.swift; sourceTree = ""; }; + 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; @@ -764,9 +762,7 @@ D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, - 0A8321A72355062F00CB7F00 /* MdnTextField.swift */, - 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */, - 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */, + 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */, 0A21DB7E235DECC500C160A2 /* FormEntryField.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, @@ -1053,7 +1049,7 @@ D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, - 0A21DB91235E0EDB00C160A2 /* DigitTextBox.swift in Sources */, + 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift similarity index 86% rename from MVMCoreUI/Atoms/TextFields/DigitTextBox.swift rename to MVMCoreUI/Atoms/TextFields/DigitBox.swift index 30b2b16d..b78ee0b7 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -1,5 +1,5 @@ // -// DigitTextBox.swift +// DigitBox.swift // MVMCoreUI // // Created by Kevin Christiano on 10/15/19. @@ -8,11 +8,11 @@ import UIKit -@objc protocol DigitTextBoxDelegate: NSObjectProtocol { +@objc protocol DigitBoxDelegate: NSObjectProtocol { @objc optional func textFieldDidDelete(_ textField: UITextField?) } -@objcMembers open class DigitTextBox: UITextField, MVMCoreViewProtocol { +@objcMembers open class DigitBox: UITextField, MVMCoreViewProtocol { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -40,7 +40,7 @@ import UIKit required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("init(coder:) has not been implemented") + fatalError("DigitBox has not been implemented") } public override init(frame: CGRect) { @@ -92,11 +92,11 @@ import UIKit public func updateView(_ size: CGFloat) { DispatchQueue.main.async { [weak self] in - guard let wSelf = self else { return } + guard let self = self else { return } - if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(wSelf.previousSize)) { - MFStyler.styleTextField(wSelf) - self?.font = MFFonts.mfFont55Rg(28) + if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(self.previousSize)) { + MFStyler.styleTextField(self) + self.font = MFFonts.mfFont55Rg(28) var digitWidth: CGFloat = 0 var digitHeight: CGFloat = 0 @@ -115,9 +115,9 @@ import UIKit }) sizeObject?.performBlockBase(onSize: size) - self?.widthConstraint?.constant = digitWidth - self?.heightConstraint?.constant = digitHeight - self?.previousSize = size + self.widthConstraint?.constant = digitWidth + self.heightConstraint?.constant = digitHeight + self.previousSize = size } } } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 20b9965d..3d947293 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -9,7 +9,7 @@ import UIKit -class DigitEntryField: TextEntryField, DigitTextBoxDelegate { +class DigitEntryField: TextEntryField, DigitBoxDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -22,7 +22,7 @@ class DigitEntryField: TextEntryField, DigitTextBoxDelegate { private var numberOfDigits = 0 private var switchedAutomatically = false - public var digitFields: [DigitTextBox]? + public var digitFields: [DigitBox]? /// Setgs placeholder text in the textField. public override var feedback: String? { diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift deleted file mode 100644 index 4ff01512..00000000 --- a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift +++ /dev/null @@ -1,530 +0,0 @@ -// -// DigitTextField.swift -// MVMCoreUI -// -// Created by Kevin Christiano on 10/15/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - - -@objcMembers open class DigitTextField: TextField, 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 && !errorShowing { - placeholderErrorLabel.text = newValue - - } else if !errorShowing { - 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 { - (textFields 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 formLabel?.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) - } - - //-------------------------------------------------- - // 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 - } - } - - 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 - }) - } - - 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.formLabel?.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() - - formLabel?.styleB2(true) - self.formText = "" - alignCenterHorizontal() - } - - //-------------------------------------------------- - // 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 - - (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.textFields?.count ?? 0)) - }) - } - } - - override public func showErrorMessage(_ errorMessage: String?) { - - DispatchQueue.main.async { [weak self] in - super.showErrorMessage (errorMessage) - - if self.errorShowing { - self.labelToTextFieldPin?.constant = 10 - } - (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - obj.setAsError() - }) - } - } - - public override func hideError() { - - DispatchQueue.main.async { [weak self] in - - super.hideError() - (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - obj.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 textFields ?? [] { - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) - } - } - } - - func setDefaultValidationBlock() { - - weak var weakSelf = self - - self.validationBlock = { enteredValue in - if (enteredValue?.count ?? 0) > 0 && (enteredValue?.count ?? 0) == weakSelf?.textFields?.count { - return true - } else { - return false - } - } - } - - func enable(_ enable: Bool) { - super.enable(enable) - - if enable { - formLabel?.styleB2(true) - } else { - formLabel?.textColor = UIColor.mfBattleshipGrey() - } - - for textField in textFields ?? [] { - 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 - - (textFields 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 - (textFields 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 (self.textFields) { - return [self.textFields arrayByAddingObject:(DigitTextBox *)self.label]; - } else { - return @[self.label]; - } - } - - //-------------------------------------------------- - // MARK: - TextFieldDelegate - //-------------------------------------------------- - - @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() - - if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) { - return uiTextFieldDelegate!.textFieldShouldReturn!(textField) - } - return 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 - } else { - 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() - } - if (uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldDidBeginEditing(_:))))! { - uiTextFieldDelegate?.textFieldDidBeginEditing!(textField) - } - } - - @objc public func textFieldDidEndEditing(_ textField: UITextField) { - - if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldDidEndEditing(_:))) { - uiTextFieldDelegate!.textFieldDidEndEditing!(textField) - } - } - - @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { - - selectPreviousTextField(textField, clear: false) - - if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) { - return uiTextFieldDelegate?.textFieldShouldClear(textField) - } - return true - } - - // MARK: - Passed Along TextField delegate - @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - - if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) { - return uiTextFieldDelegate?.textFieldShouldBeginEditing(textField) - } - return true - } - - @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - - if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) { - return uiTextFieldDelegate?.textFieldShouldEndEditing(textField) - } - return true - } -} diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 2448ccef..a2a57cc4 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -21,12 +21,7 @@ import UIKit //-------------------------------------------------- public var dropDownCaretLabel: UILabel? - - public var enabledTextColor: UIColor? - public var disabledTextColor: UIColor? - - public var observingForChanges = false - + private var borderPath: UIBezierPath? public override var isEnabled: Bool { @@ -44,12 +39,6 @@ import UIKit } } - public var validationBlock: ((_ enteredValue: String?) -> Bool)? { - didSet { - valueChanged() - } - } - //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -76,47 +65,18 @@ import UIKit } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + public override 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() - dropDownCaretLabel?.isHidden = hasDropDown - self.hasDropDown = !hasDropDown - setWithMap(map, bothDelegates: bothDelegates) - } - //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setupFieldContainerContent(_ container: UIView) { - - 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) - - container.addSubview(textField) - - NSLayoutConstraint.activate([ - textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 10), - textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), - container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)]) - + let dropDownCaretLabel = Label() self.dropDownCaretLabel = dropDownCaretLabel dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) @@ -133,44 +93,12 @@ import UIKit dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6).isActive = true container.trailingAnchor.constraint(equalTo: dropDownCaretLabel.trailingAnchor, constant: 16).isActive = true container.bottomAnchor.constraint(equalTo: dropDownCaretLabel.bottomAnchor).isActive = true - dropDownCarrotWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) - dropDownCarrotWidth?.isActive = true + dropDownCaretWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) + dropDownCaretWidth?.isActive = true } open override func updateView(_ size: CGFloat) { super.updateView(size) - - if let textField = textField { - MFStyler.styleTextField(textField) - } - - layoutIfNeeded() - } - - deinit { - mfTextFieldDelegate = nil - uiTextFieldDelegate = nil - } - - open override func draw(_ rect: CGRect) { - super.draw(rect) - - borderPath?.removeAllPoints() - - if !hideBorder, let frame = fieldContainer?.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() - } } //-------------------------------------------------- @@ -237,15 +165,6 @@ import UIKit dropDownCaretLabel?.isHidden = false self.hasDropDown = true - case "password": - textField?.isSecureTextEntry = true - - case "number": - textField?.keyboardType = .numberPad - - case "email": - textField?.keyboardType = .emailAddress - default: break } diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 7d2ad7a7..aec24696 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -17,9 +17,7 @@ class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, // MARK: - Properties //-------------------------------------------------- - public weak var customDelegate: UITextFieldDelegate? - - public var isNationalMdn = false + public var isNationalMdn = true public var shouldValidateMDN = false public var mdn: String? { @@ -50,29 +48,17 @@ class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, //-------------------------------------------------- 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(_:))) + let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissTextFieldResponder(_:))) 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 - } - } - //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -108,11 +94,6 @@ class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, return true } - @objc func dismissFieldInput(_ sender: Any?) { - - textField?.resignFirstResponder() - } - func getContacts(_ sender: Any?) { let picker = CNContactPickerViewController() @@ -161,7 +142,7 @@ class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, textField.resignFirstResponder() - return customDelegate?.textFieldShouldReturn?(textField) ?? true + return uiTextFieldDelegate?.textFieldShouldReturn?(textField) ?? true } @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { @@ -170,18 +151,18 @@ class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, return false } - return customDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + return uiTextFieldDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } public func textFieldDidBeginEditing(_ textField: UITextField) { textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) - customDelegate?.textFieldDidBeginEditing?(textField) + uiTextFieldDelegate?.textFieldDidBeginEditing?(textField) } public func textFieldDidEndEditing(_ textField: UITextField) { - customDelegate?.textFieldDidEndEditing?(textField) + uiTextFieldDelegate?.textFieldDidEndEditing?(textField) if validateAndColor() && isNationalMdn { textField.text = MVMCoreUIUtility.formatMdn(textField.text) @@ -194,16 +175,16 @@ class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - return customDelegate?.textFieldShouldBeginEditing?(textField) ?? true + return uiTextFieldDelegate?.textFieldShouldBeginEditing?(textField) ?? true } @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - return customDelegate?.textFieldShouldEndEditing?(textField) ?? true + return uiTextFieldDelegate?.textFieldShouldEndEditing?(textField) ?? true } @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { - return customDelegate?.textFieldShouldClear?(textField) ?? true + return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true } } diff --git a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift deleted file mode 100644 index 35ab49d7..00000000 --- a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift +++ /dev/null @@ -1,233 +0,0 @@ -// -// MdnTextField.swift -// MVMCoreUI -// -// Created by Kevin Christiano on 10/14/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import AddressBookUI -import ContactsUI -import UIKit -import MVMCore - - -@objcMembers open class MdnTextField: TextField, 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() { - - 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 - } - - 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) - } - - //-------------------------------------------------- - // MARK: - ContactPicker Delegate - //-------------------------------------------------- - - public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { - - if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { - - 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() - - 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 - } - - return customDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true - } - - public func textFieldDidBeginEditing(_ textField: UITextField) { - - textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) - customDelegate?.textFieldDidBeginEditing?(textField) - } - - public func textFieldDidEndEditing(_ textField: UITextField) { - - customDelegate?.textFieldDidEndEditing?(textField) - - 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 - } -} diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 63a5ce1b..e5da5006 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -200,6 +200,11 @@ import UIKit } } + @objc func dismissTextFieldResponder(_ sender: Any?) { + + textField?.resignFirstResponder() + } + //-------------------------------------------------- // MARK: - Observing for change //-------------------------------------------------- From 70a3ad280470366bd1cdfd485573efc5895837ce Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 24 Oct 2019 13:51:19 -0400 Subject: [PATCH 021/112] latest. no errs. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 2 +- .../Atoms/TextFields/DigitEntryField.swift | 380 ++++++++---------- .../Atoms/TextFields/DropdownEntryField.swift | 224 ++--------- .../Atoms/TextFields/FormEntryField.swift | 4 +- 4 files changed, 206 insertions(+), 404 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index b78ee0b7..1f6860ee 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -23,7 +23,7 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - weak var textBoxDelegate: DigitTextBoxDelegate? + weak var textBoxDelegate: DigitBoxDelegate? private var previousSize: CGFloat = 0.0 diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 3d947293..3d977170 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -9,7 +9,7 @@ import UIKit -class DigitEntryField: TextEntryField, DigitBoxDelegate { +open class DigitEntryField: TextEntryField, DigitBoxDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -24,58 +24,68 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { private var switchedAutomatically = false public var digitFields: [DigitBox]? - /// Setgs placeholder text in the textField. + public override var isEnabled: Bool { + didSet { + if isEnabled { + descriptionLabel?.styleB2(true) + } else { + descriptionLabel?.textColor = UIColor.mfBattleshipGrey() + } + + for textField in digitFields ?? [] { + textField.isUserInteractionEnabled = isEnabled + textField.isEnabled = isEnabled + textField.textColor = isEnabled ? .black : UIColor.mfBattleshipGrey() + } + } + } + + /// Sets placeholder text in the textField. public override var feedback: String? { get { var string = "" for digitField in digitFields ?? [] { - if let placeholderText = digitField.attributedPlaceholder?.string { - string += placeholderText + if let digitext = digitField.attributedPlaceholder?.string { + string += digitext } } return !string.isEmpty ? string : nil } set { - guard let placeholderValue = newValue else { return } + guard let fieldValue = newValue, let fields = digitFields 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: [ + for (index, field) in fields.enumerated() { + if index < fieldValue.count { + let stringForIndex = (newValue as NSString?)?.substring(with: NSRange(location: index, length: 1)) + field.attributedPlaceholder = NSAttributedString(string: stringForIndex ?? "", attributes: [ NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) - } else if stop != nil { - stop = true } - }) + } +// +// if feedback.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")) +// +// super.showErrorMessage(errorMessage) +// +// if self.showErrorMessage { +// self.labelToTextFieldPin?.constant = 10 +// } +// for field in self.digitFields ?? [] { +// field.setAsError() +// } } - - // 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 = "" @@ -89,30 +99,42 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { 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 + guard let fields = self.digitFields else { return } + + for (index, field) in fields.enumerated() { + if index < (text?.count ?? 0) { + let stringForIndex = (text as NSString?)?.substring(with: NSRange(location: index, length: 1)) + field.text = stringForIndex } - }) + } + valueChanged() } } - public override var formText: String? { - get { - return formDescriptionLabel?.text - } + public override var descriptionText: String? { + get { return descriptionLabel?.text } set { - if let formText = newValue, !formText.isEmpty > 0 { + if let formText = newValue, !formText.isEmpty { messageToTextFieldPin?.constant = 10 } else { messageToTextFieldPin?.constant = 0 } - super.formText = newValue + super.descriptionText = newValue + } + } + + public override var errorMessage: String? { + didSet { + if let errorMessage = errorMessage, !errorMessage.isEmpty { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + for field in self.digitFields ?? [] { + field.hideError() + } + } + } } } @@ -135,6 +157,7 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { public init(numberOfDigits: Int) { super.init(frame: .zero) + setup() self.numberOfDigits = numberOfDigits buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } @@ -142,29 +165,66 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { public init(numberOfDigits: Int, bothDelegates delegates: (UITextFieldDelegate & MFTextFieldDelegate)?) { super.init(bothDelegates: delegates as? (TextFieldDelegate & UITextFieldDelegate)) + setup() self.numberOfDigits = numberOfDigits buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } - public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { + public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) + setup() self.numberOfDigits = numberOfDigits buildTextFieldsView(size: size) } open override func setupFieldContainerContent(_ container: UIView) { - setupTextFieldsView(forSize: numberOfDigits) + setupTextFieldsView(forSize: CGFloat(numberOfDigits)) + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open func setup() { + + descriptionLabel?.styleB2(true) + descriptionText = "" + alignCenterHorizontal() + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.descriptionLabel?.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) + } } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - func createDigitField() -> DigitTextBox { + func createDigitField() -> DigitBox { - let textField = DigitTextBox() + let textField = DigitBox() textField.delegate = self textField.textBoxDelegate = self @@ -180,7 +240,7 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { if numberOfDigits > 0 { - let digitFields = [DigitTextBox](repeating: createDigitField(), count: numberOfDigits) + let digitFields = [DigitBox](repeating: createDigitField(), count: numberOfDigits) for digitField in digitFields { digitField.updateView(size) @@ -200,47 +260,31 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { DispatchQueue.main.async { [weak self] in guard let self = self else { return } - if let placeholder = self.placeholder, !placeholder.isEmpty { + if let feedback = self.feedback, !feedback.isEmpty { self.labelToTextFieldPin?.constant = 10 - + } else { self.labelToTextFieldPin?.constant = 0 } } } - open override func updateView(_ size: CGFloat) { - super.updateView(size) + func setAsSecureTextEntry(_ secureEntry: Bool) { DispatchQueue.main.async { [weak self] in - guard let self = self else { return } + guard let self = self, + let fields = self.digitFields + else { return } - self.formDescriptionLabel?.updateView(size) - - if let digitFields = self.digitFields, !digitFields.isEmpty { + for (index, field) in fields.enumerated() { + field.isSecureTextEntry = secureEntry - // Remove all current UI. - StackableViewController.remove(digitFields) - - // Update text boxes. - for digitField in digitFields { - digitField.updateView(size) - } + // Accessibility - 33704 fix voice over will read what pin user is filling + field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(fields.count)) } - - // 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(), @@ -269,6 +313,20 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { }) } + func setDefaultValidationBlock() { + + weak var weakSelf = self + + validationBlock = { enteredValue in + + if (enteredValue?.count ?? 0) > 0 && (enteredValue?.count ?? 0) == weakSelf?.digitFields?.count { + return true + } + + return false + } + } + //-------------------------------------------------- // MARK: - Molecule //-------------------------------------------------- @@ -282,106 +340,21 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { numberOfDigits = digits } + if !dictionary.isEmpty{ + for textField in digitFields ?? [] { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegateObject as? UITextFieldDelegate) + } + } + 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 //-------------------------------------------------- @@ -390,56 +363,58 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { var selectNextField = false - (digitFields as NSArray?)?.enumerateObjects(options: .reverse, using: { obj, idx, stop in - if obj == currentTextField { + guard let fields = digitFields else { return } + + for field in fields { + + if field == currentTextField { selectNextField = true + } else if selectNextField { if !clear { - self.switchedAutomatically = true + switchedAutomatically = true } - obj.becomeFirstResponder() - self.switchedAutomatically = false - stop = true + field.becomeFirstResponder() + switchedAutomatically = false - //accessibility - UIAccessibility.post(notification: .layoutChanged, argument: obj) + UIAccessibility.post(notification: .layoutChanged, argument: field) } - }) + } } func selectNextTextField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false - (digitFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - - if obj == currentTextField { + + guard let fields = digitFields else { return } + + for field in fields{ + if field == currentTextField { selectNextField = true } else if selectNextField { if !clear { self.switchedAutomatically = true } - obj.becomeFirstResponder() + field.becomeFirstResponder() self.switchedAutomatically = false - stop = true - - //accessibility - UIAccessibility.post(notification: .layoutChanged, argument: obj) + + UIAccessibility.post(notification: .layoutChanged, argument: field) } - }) + } } //-------------------------------------------------- // MARK: - Accessinility //-------------------------------------------------- - open override var accessibilityElements: [Any]? { - - if let digitFields = self.digitFields { - return [digitFields] //return [self.digitFields arrayByAddingObject:(DigitTextBox *)self.label]; - } else { - return [placeholder] - } + open override class func accessibilityElements() -> [Any]? { +// self.digit +// if let digitFields = self.digitFields { +// return [digitFields] + [textField] +// } +// + return [textField] } //-------------------------------------------------- @@ -464,9 +439,9 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { 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 @@ -474,21 +449,23 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { 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 + return false } func textFieldDidDelete(_ textField: UITextField?) { - // empty cell, go back to previous cell and clear. + // Empty cell, go back to previous cell and clear. selectPreviousTextField(textField, clear: true) } @@ -504,7 +481,6 @@ class DigitEntryField: TextEntryField, DigitBoxDelegate { @objc public func textFieldDidEndEditing(_ textField: UITextField) { - uiTextFieldDelegate?.textFieldDidEndEditing?(textField) } diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index a2a57cc4..72d8cb41 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -21,21 +21,11 @@ import UIKit //-------------------------------------------------- public var dropDownCaretLabel: UILabel? - - private var borderPath: UIBezierPath? + public var dropDownIsDisplayed = false public override var isEnabled: Bool { didSet { - - } - } - - // The text of this textField. - public override var text: String? { - get { return textField?.text } - set { - textField?.text = newValue - valueChanged() + showDropDown(isEnabled) } } @@ -67,8 +57,11 @@ import UIKit /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) - setupView() - setBothTextFieldDelegates(bothDelegates) + + if let textField = textField { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) + } + setBothTextDelegates(bothDelegates) } //-------------------------------------------------- @@ -76,7 +69,9 @@ import UIKit //-------------------------------------------------- open override func setupFieldContainerContent(_ container: UIView) { - + + guard let textField = textField else { return } + let dropDownCaretLabel = Label() self.dropDownCaretLabel = dropDownCaretLabel dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) @@ -97,169 +92,26 @@ import UIKit dropDownCaretWidth?.isActive = true } - open override func updateView(_ size: CGFloat) { - super.updateView(size) - } - //-------------------------------------------------- // 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() - - textField?.accessibilityValue = nil - } - - public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { - - mfTextFieldDelegate = delegate - uiTextFieldDelegate = delegate - } - - public override func setWithMap(_ map: [AnyHashable: Any]?) { - super.setWithMap(map) - - 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) { - formIsDisabled() - } - - 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 - - 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 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) + dropDownCaretLabel?.isHidden = !show + dropDownCaretWidth?.isActive = !show + setNeedsLayout() + layoutIfNeeded() } //-------------------------------------------------- // MARK: - Observing for change //-------------------------------------------------- - func valueChanged() { + override func startEditing() { + super.startEditing() - // Update label for placeholder - if !showError { - feedbackLabel?.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) + showDropDown(!showErrorMessage) } class func getEnabledTextfields(_ textFieldToDetermine: [TextEntryField]?) -> [AnyHashable]? { @@ -278,7 +130,7 @@ import UIKit // MARK: - Date Picker extension DropdownEntryField { - + private func createDatePicker() { guard let textField = textField else { return } @@ -337,11 +189,6 @@ extension DropdownEntryField { textField?.resignFirstResponder() return pickedDate } - - public func dismissPicker() { - - textField?.resignFirstResponder() - } } // MARK: - Molecular @@ -350,46 +197,26 @@ extension DropdownEntryField { 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) - } + guard let dictionary = json, + !dictionary.isEmpty + else { return } + + if let _ = dictionary[KeyType] as? String { + dropDownCaretLabel?.isHidden = false } } - - override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 76 - } } // MARK: - Accessibility extension DropdownEntryField { - open override func pushAccessibilityNotification() { - - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - UIAccessibility.post(notification: .layoutChanged, argument: self.textField) - } - } - open override func setAccessibilityString(_ accessibilityString: String?) { guard let textField = textField else { return } var accessibilityString = accessibilityString ?? "" - if hasDropDown, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { + if dropDownIsDisplayed, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { accessibilityString += txtPickerItem } else if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") { @@ -398,5 +225,4 @@ extension DropdownEntryField { textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" } - } diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index cd1042b3..e6b4f2f7 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -34,14 +34,14 @@ import UIKit private var borderPath: UIBezierPath? + public var showErrorMessage = false + public var errorMessage: String? { didSet { feedback = errorMessage } } - public var showErrorMessage = false - public var isEnabled = true { didSet { DispatchQueue.main.async { [weak self] in From 5bc78e594e5595101f9e6059729cdf442d4a5ad1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 24 Oct 2019 15:43:06 -0400 Subject: [PATCH 022/112] getting ready for the next phase. --- MVMCoreUI/Atoms/Buttons/CaretButton.swift | 4 +--- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 8 ++++---- MVMCoreUI/Atoms/TextFields/FormEntryField.swift | 4 +--- MVMCoreUI/Atoms/TextFields/MdnEntryField.swift | 2 +- MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m | 6 ++++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index 4ca750c6..759dfdb4 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -17,7 +17,6 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI private let CARET_VIEW_HEIGHT: Float = 10.5 private let CARET_VIEW_WIDTH: Float = 6.5 - //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ @@ -52,8 +51,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI changeCaretColor() } - public func updateView(_ size: CGFloat) { - } + public func updateView(_ size: CGFloat) { } //------------------------------------------------------ // MARK: - Functions diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 3d977170..3c30311d 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -9,7 +9,7 @@ import UIKit -open class DigitEntryField: TextEntryField, DigitBoxDelegate { +@objcMembers open class DigitEntryField: TextEntryField, DigitBoxDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -120,6 +120,7 @@ open class DigitEntryField: TextEntryField, DigitBoxDelegate { } else { messageToTextFieldPin?.constant = 0 } + super.descriptionText = newValue } } @@ -151,7 +152,7 @@ open class DigitEntryField: TextEntryField, DigitBoxDelegate { required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("init(coder:) has not been implemented") + fatalError("DigitEntryField xib has not been implemented") } public init(numberOfDigits: Int) { @@ -239,7 +240,6 @@ open class DigitEntryField: TextEntryField, DigitBoxDelegate { } if numberOfDigits > 0 { - let digitFields = [DigitBox](repeating: createDigitField(), count: numberOfDigits) for digitField in digitFields { @@ -313,7 +313,7 @@ open class DigitEntryField: TextEntryField, DigitBoxDelegate { }) } - func setDefaultValidationBlock() { + public override func defaultValidationBlock() { weak var weakSelf = self diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index e6b4f2f7..bc65bfd4 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -37,9 +37,7 @@ import UIKit public var showErrorMessage = false public var errorMessage: String? { - didSet { - feedback = errorMessage - } + didSet { feedback = errorMessage } } public var isEnabled = true { diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index aec24696..f06c9a0a 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -12,7 +12,7 @@ import UIKit import MVMCore -class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { +@objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index edd39fd8..e676e02e 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -36,8 +36,10 @@ @"footer": StandardFooterView.class, @"caretView": CaretView.class, @"caretButton": CaretButton.class, - @"textField" : TextEntryField.class, -// @"digitTextField" : DigitTextField.class, + @"textField": TextEntryField.class, + @"digitEntryField": DigitEntryField.class, + @"mdnEntryField" : MdnEntryField.class, + @"dropdownEntryField" : DropdownEntryField.class, @"checkbox" : Checkbox.class, @"checkboxWithLabelView" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, From f14614514dab2ca03a87d84348e5f599ef7fe7cb Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 25 Oct 2019 15:17:38 -0400 Subject: [PATCH 023/112] dcdcdd --- .../Atoms/TextFields/FormEntryField.swift | 119 ++++++++++-------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index bc65bfd4..182c895d 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -21,8 +21,10 @@ import UIKit private(set) var feedbackLabel: Label? private(set) var fieldContainer: UIView? - public var backgroundView: UIView? - public var separatorView: UIView? + private var borderStrokeColor: UIColor = UIColor.mfSilver() + private var borderPath: UIBezierPath? + public var bottomBar: UIView? + public var dashLine: DashLine? //-------------------------------------------------- @@ -32,23 +34,28 @@ import UIKit public var isValid = false public var fieldKey: String? - private var borderPath: UIBezierPath? - public var showErrorMessage = false public var errorMessage: String? { - didSet { feedback = errorMessage } + didSet { + if showErrorMessage { + feedback = errorMessage + } + } } + /// Toggles the enables state of this component. public var isEnabled = true { didSet { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.isUserInteractionEnabled = self.isEnabled + self.feedbackLabel?.text = nil self.descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() - self.feedbackLabel?.textColor = self.isEnabled ? .black : UIColor.mfSilver() - self.separatorView?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black + self.bottomBar?.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin() : .black) : UIColor.mfSilver() + self.fieldContainer?.setNeedsDisplay() + self.fieldContainer?.layoutIfNeeded() } } } @@ -57,6 +64,17 @@ import UIKit public var hideBorder = false { didSet { setNeedsDisplay() } } + + public var isLocked = false { + didSet { + isUserInteractionEnabled = isLocked + borderStrokeColor = isLocked ? .clear : UIColor.mfSilver() + bottomBar?.backgroundColor = isLocked ? .clear : .black + bottomBarHeightConstraint?.constant = 1 + + fieldContainer?.setNeedsDisplay() + } + } public var descriptionText: String? { get { return descriptionLabel?.text } @@ -69,23 +87,19 @@ import UIKit /// Override this to conveniently get/set the textfield(s). public var text: String? { get { return nil } - set { - fatalError("You're supposed to override FormEntryField's 'text' variable.") - } + set { fatalError("You need to override FormEntryField's 'text' variable in the subclass.") } } /// Sets feedback text in the textField. public var feedback: String? { get { return feedbackLabel?.text } set { - guard isEnabled else { return } - DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.feedbackLabel?.text = newValue - self.separatorHeightConstraint?.constant = self.showErrorMessage ? 4 : 1 - self.separatorView?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black + self.bottomBarHeightConstraint?.constant = self.showErrorMessage ? 4 : 1 + self.bottomBar?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black self.setNeedsDisplay() self.layoutIfNeeded() } @@ -118,7 +132,7 @@ import UIKit public var descriptionLabelLeading: NSLayoutConstraint? public var descriptionLabelTrailing: NSLayoutConstraint? - public var separatorHeightConstraint: NSLayoutConstraint? + public var bottomBarHeightConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Initializers @@ -215,32 +229,20 @@ import UIKit /// Configuration for the field container view. private func setupFieldContainer(_ parentView: UIView) { - let backgroundView = UIView(frame: .zero) - self.backgroundView = backgroundView - backgroundView.translatesAutoresizingMaskIntoConstraints = false - - parentView.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)]) - setupFieldContainerContent(parentView) - let separatorView = UIView(frame: .zero) - self.separatorView = separatorView - separatorView.translatesAutoresizingMaskIntoConstraints = false - separatorView.backgroundColor = .black + let bottomBar = UIView(frame: .zero) + self.bottomBar = bottomBar + bottomBar.translatesAutoresizingMaskIntoConstraints = false + bottomBar.backgroundColor = .black - parentView.addSubview(separatorView) + parentView.addSubview(bottomBar) - 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 + bottomBarHeightConstraint = bottomBar.heightAnchor.constraint(equalToConstant: 1) + bottomBarHeightConstraint?.isActive = true + bottomBar.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true + parentView.trailingAnchor.constraint(equalTo: bottomBar.trailingAnchor).isActive = true + parentView.bottomAnchor.constraint(equalTo: bottomBar.bottomAnchor).isActive = true let dashLine = DashLine() dashLine.translatesAutoresizingMaskIntoConstraints = false @@ -250,10 +252,10 @@ import UIKit parentView.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)]) + dashLine.centerYAnchor.constraint(equalTo: bottomBar.centerYAnchor), + dashLine.centerXAnchor.constraint(equalTo: bottomBar.centerXAnchor), + dashLine.topAnchor.constraint(equalTo: bottomBar.topAnchor), + dashLine.leadingAnchor.constraint(equalTo: bottomBar.leadingAnchor)]) } open override func updateView(_ size: CGFloat) { @@ -277,15 +279,17 @@ import UIKit if !hideBorder, let frame = fieldContainer?.frame { + let insetLean: CGFloat = 0.5 + 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 = showErrorMessage ? UIColor.mfPumpkin() : UIColor.mfSilver() - strokeColor.setStroke() + borderPath?.move(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + frame.size.height)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + insetLean)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + insetLean)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + frame.size.height)) + + borderStrokeColor.setStroke() borderPath?.stroke() } @@ -313,16 +317,27 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - open func showPlaceholderErrorLabel(_ show: Bool) { + open func showError(_ show: Bool) { - feedbackLabel?.isHidden = !show + showErrorMessage = show + + if show { + feedbackLabel?.text = errorMessage + } + } + + open func resetEntryAppearance() { + + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = .black + bottomBarHeightConstraint?.constant = 1 } open func showDashSeperatorView(_ 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 + bottomBar?.backgroundColor = dash ? .clear : .black } } @@ -337,7 +352,7 @@ extension FormEntryField { else { return } if let formText = dictionary[KeyLabel] as? String { - self.descriptionText = formText + descriptionText = formText } if let text = dictionary[KeyDisable] as? String, text.isEqual(StringY) || dictionary.boolForKey(KeyDisable) { @@ -345,7 +360,7 @@ extension FormEntryField { } if let errMessage = dictionary[KeyErrorMessage] as? String { - self.errorMessage = errMessage + errorMessage = errMessage } if let hideBorder = dictionary["hideBorder"] as? Bool { From 8d1ac1e94a2e5062c4b1819a42db5840ffd25f4b Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 28 Oct 2019 09:12:38 -0400 Subject: [PATCH 024/112] form engtry. --- .../Atoms/TextFields/FormEntryField.swift | 70 ++++++++++++------- .../Atoms/TextFields/TextEntryField.swift | 2 +- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 182c895d..90cf03bd 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -34,6 +34,8 @@ import UIKit public var isValid = false public var fieldKey: String? + /// Determines if a border should be drawn. + public var hideBorder = false public var showErrorMessage = false public var errorMessage: String? { @@ -60,22 +62,6 @@ import UIKit } } - /// Determines if a border should be drawn. - public var hideBorder = false { - didSet { setNeedsDisplay() } - } - - public var isLocked = false { - didSet { - isUserInteractionEnabled = isLocked - borderStrokeColor = isLocked ? .clear : UIColor.mfSilver() - bottomBar?.backgroundColor = isLocked ? .clear : .black - bottomBarHeightConstraint?.constant = 1 - - fieldContainer?.setNeedsDisplay() - } - } - public var descriptionText: String? { get { return descriptionLabel?.text } set { @@ -313,6 +299,47 @@ import UIKit descriptionLabelTrailing?.constant = constant } + //-------------------------------------------------- + // MARK: - Form Appearance + //-------------------------------------------------- + + public enum Appearance { + case original + case error + case locked + case selected + } + + public func fieldAppearance(_ state: Appearance) { + + switch state { + case .original: + isUserInteractionEnabled = true + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = .black + bottomBarHeightConstraint?.constant = 1 + + case .error: + isUserInteractionEnabled = true + bottomBarHeightConstraint?.constant = 4 + + case .locked: + isUserInteractionEnabled = false + hideBorder = true + bottomBar?.backgroundColor = .clear + bottomBarHeightConstraint?.constant = 1 + + case .selected: + isUserInteractionEnabled = true + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = .black + bottomBarHeightConstraint?.constant = 1 + } + + fieldContainer?.setNeedsDisplay() + layoutIfNeeded() + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -322,15 +349,10 @@ import UIKit showErrorMessage = show if show { - feedbackLabel?.text = errorMessage - } - } - - open func resetEntryAppearance() { - borderStrokeColor = .mfSilver() - bottomBar?.backgroundColor = .black - bottomBarHeightConstraint?.constant = 1 + feedbackLabel?.text = errorMessage + fieldAppearance(.error) + } } open func showDashSeperatorView(_ dash: Bool) { diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index e5da5006..0eb61015 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -237,7 +237,7 @@ import UIKit if isValid { clearError() - separatorView?.backgroundColor = .black + bottomBar?.backgroundColor = .black } else if let errMessage = errorMessage { feedback = errMessage } From bf073d15e291e3e267f31903d4de8408c713ab0d Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 28 Oct 2019 09:50:19 -0400 Subject: [PATCH 025/112] com. --- MVMCoreUI/Atoms/TextFields/FormEntryField.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 90cf03bd..79b1328e 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -310,6 +310,7 @@ import UIKit case selected } + /// Updates the visual appearance of the entry field. public func fieldAppearance(_ state: Appearance) { switch state { @@ -349,7 +350,6 @@ import UIKit showErrorMessage = show if show { - feedbackLabel?.text = errorMessage fieldAppearance(.error) } From 9a578fc6b1e9020aa98b77df878a52ac06f4b532 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 28 Oct 2019 15:51:03 -0400 Subject: [PATCH 026/112] the latest. --- .../Atoms/TextFields/DropdownEntryField.swift | 8 + .../Atoms/TextFields/FormEntryField.swift | 238 +++++++++--------- .../Atoms/TextFields/TextEntryField.swift | 14 +- MVMCoreUI/Atoms/Views/DashLine.swift | 5 +- 4 files changed, 127 insertions(+), 138 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 72d8cb41..401208b8 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -16,6 +16,14 @@ import UIKit private var calendar: Calendar? + //-------------------------------------------------- + // MARK: - Accessories + //-------------------------------------------------- + + public weak var datePicker: UIDatePicker? + public var pickerView: UIPickerView? + + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 79b1328e..7fe63f97 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -22,10 +22,10 @@ import UIKit private(set) var fieldContainer: UIView? private var borderStrokeColor: UIColor = UIColor.mfSilver() - private var borderPath: UIBezierPath? + private var borderPath: UIBezierPath = UIBezierPath() public var bottomBar: UIView? - public var dashLine: DashLine? + var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- // MARK: - Properties @@ -38,13 +38,7 @@ import UIKit public var hideBorder = false public var showErrorMessage = false - public var errorMessage: String? { - didSet { - if showErrorMessage { - feedback = errorMessage - } - } - } + public var errorMessage: String? /// Toggles the enables state of this component. public var isEnabled = true { @@ -52,12 +46,7 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isUserInteractionEnabled = self.isEnabled - self.feedbackLabel?.text = nil - self.descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() - self.bottomBar?.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin() : .black) : UIColor.mfSilver() - self.fieldContainer?.setNeedsDisplay() - self.fieldContainer?.layoutIfNeeded() + self.isEnabled ? self.originalAppearance() : self.errorAppearance() } } } @@ -80,20 +69,13 @@ import UIKit public var feedback: String? { get { return feedbackLabel?.text } set { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.feedbackLabel?.text = newValue - self.bottomBarHeightConstraint?.constant = self.showErrorMessage ? 4 : 1 - self.bottomBar?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black - self.setNeedsDisplay() - self.layoutIfNeeded() - } - + self.feedbackLabel?.text = newValue + self.refreshUI() setAccessibilityString(newValue) } } + // TODO: Pull this out into Styler or some class akin to it. public var formatter: DateFormatter = { let formatter = DateFormatter() @@ -109,11 +91,11 @@ import UIKit // MARK: - Constraints //-------------------------------------------------- - public var textContainerLeading: NSLayoutConstraint? - public var textContainerTrailing: NSLayoutConstraint? + public var fieldContainerLeading: NSLayoutConstraint? + public var fieldContainerTrailing: NSLayoutConstraint? - public var errorLabelTrailing: NSLayoutConstraint? - public var errorLabelLeading: NSLayoutConstraint? + public var feedbackLabelTrailing: NSLayoutConstraint? + public var feedbackLabelLeading: NSLayoutConstraint? public var descriptionLabelLeading: NSLayoutConstraint? public var descriptionLabelTrailing: NSLayoutConstraint? @@ -149,9 +131,8 @@ import UIKit final public override func setupView() { guard subviews.isEmpty else { return } - + // heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true translatesAutoresizingMaskIntoConstraints = false - setContentHuggingPriority(.required, for: .vertical) setContentCompressionResistancePriority(.required, for: .vertical) backgroundColor = .clear @@ -159,9 +140,8 @@ import UIKit self.descriptionLabel = descriptionLabel descriptionLabel.font = MFStyler.fontB3() descriptionLabel.textColor = UIColor.mfBattleshipGrey() - descriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) - descriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) descriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) + descriptionLabel.setContentCompressionResistancePriority(.required, for: .horizontal) addSubview(descriptionLabel) @@ -179,35 +159,35 @@ import UIKit setupFieldContainer(fieldContainer) fieldContainer.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 4).isActive = true - textContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - textContainerLeading?.isActive = true - textContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) - textContainerTrailing?.isActive = true + fieldContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + fieldContainerLeading?.isActive = true + fieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) + fieldContainerTrailing?.isActive = true let feedbackLabel = Label() self.feedbackLabel = feedbackLabel feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() feedbackLabel.textColor = .black - feedbackLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) - feedbackLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) feedbackLabel.setContentCompressionResistancePriority(.required, for: .vertical) + feedbackLabel.setContentCompressionResistancePriority(.required, for: .horizontal) addSubview(feedbackLabel) - feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor).isActive = true - errorLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - errorLabelLeading?.isActive = true - errorLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) - errorLabelTrailing?.isActive = true + feedbackLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true + feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne).isActive = true + feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + feedbackLabelLeading?.isActive = true + feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) + feedbackLabelTrailing?.isActive = true layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true setNeedsLayout() } /** - Method to override. - Intended to add the interactive content (textField) to the fieldContainer. - */ + Method to override. + Intended to add the interactive content (textField) to the fieldContainer. + */ open func setupFieldContainerContent(_ container: UIView) { // To Be Overridden By Subclass. } @@ -229,19 +209,6 @@ import UIKit bottomBar.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true parentView.trailingAnchor.constraint(equalTo: bottomBar.trailingAnchor).isActive = true parentView.bottomAnchor.constraint(equalTo: bottomBar.bottomAnchor).isActive = true - - let dashLine = DashLine() - dashLine.translatesAutoresizingMaskIntoConstraints = false - dashLine.backgroundColor = .white - dashLine.isHidden = true - - parentView.addSubview(dashLine) - - NSLayoutConstraint.activate([ - dashLine.centerYAnchor.constraint(equalTo: bottomBar.centerYAnchor), - dashLine.centerXAnchor.constraint(equalTo: bottomBar.centerXAnchor), - dashLine.topAnchor.constraint(equalTo: bottomBar.topAnchor), - dashLine.leadingAnchor.constraint(equalTo: bottomBar.leadingAnchor)]) } open override func updateView(_ size: CGFloat) { @@ -249,9 +216,8 @@ import UIKit descriptionLabel?.updateView(size) feedbackLabel?.font = MFStyler.fontForTextFieldUnderLabel() - dashLine?.updateView(size) - layoutIfNeeded() + refreshUI() } //-------------------------------------------------- @@ -261,23 +227,22 @@ import UIKit open override func draw(_ rect: CGRect) { super.draw(rect) - borderPath?.removeAllPoints() + borderPath.removeAllPoints() if !hideBorder, let frame = fieldContainer?.frame { + // Brings the other half of the line inside the view to prevent thinness from cropping. let insetLean: CGFloat = 0.5 - borderPath = UIBezierPath() - borderPath?.lineWidth = 1 + borderPath.lineWidth = 1 - borderPath?.move(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + frame.size.height)) - borderPath?.addLine(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + insetLean)) - borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + insetLean)) - borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + frame.size.height)) + borderPath.move(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + frame.size.height)) + borderPath.addLine(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + frame.size.height)) borderStrokeColor.setStroke() - - borderPath?.stroke() + borderPath.stroke() } } @@ -287,15 +252,15 @@ import UIKit open override func setLeftPinConstant(_ constant: CGFloat) { - textContainerLeading?.constant = constant - errorLabelLeading?.constant = constant + fieldContainerLeading?.constant = constant + feedbackLabelLeading?.constant = constant descriptionLabelLeading?.constant = constant } open override func setRightPinConstant(_ constant: CGFloat) { - textContainerTrailing?.constant = constant - errorLabelTrailing?.constant = constant + fieldContainerTrailing?.constant = constant + feedbackLabelTrailing?.constant = constant descriptionLabelTrailing?.constant = constant } @@ -303,64 +268,82 @@ import UIKit // MARK: - Form Appearance //-------------------------------------------------- - public enum Appearance { + public enum Appearance: String { case original case error - case locked - case selected + case lock + case select + case disabled } - /// Updates the visual appearance of the entry field. - public func fieldAppearance(_ state: Appearance) { + open func originalAppearance() { - switch state { - case .original: - isUserInteractionEnabled = true - borderStrokeColor = .mfSilver() - bottomBar?.backgroundColor = .black - bottomBarHeightConstraint?.constant = 1 - - case .error: - isUserInteractionEnabled = true - bottomBarHeightConstraint?.constant = 4 - - case .locked: - isUserInteractionEnabled = false - hideBorder = true - bottomBar?.backgroundColor = .clear - bottomBarHeightConstraint?.constant = 1 - - case .selected: - isUserInteractionEnabled = true - borderStrokeColor = .mfSilver() - bottomBar?.backgroundColor = .black - bottomBarHeightConstraint?.constant = 1 + isUserInteractionEnabled = true + bottomBarHeightConstraint?.constant = 1 + hideBorder = false + showErrorMessage = false + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = .black + descriptionLabel?.textColor = .mfBattleshipGrey() + + refreshUI() + } + + open func errorAppearance(showError: Bool = false) { + + isUserInteractionEnabled = true + hideBorder = false + bottomBarHeightConstraint?.constant = 4 + borderStrokeColor = .mfPumpkin() + bottomBar?.backgroundColor = .mfPumpkin() + + if showError { + showErrorMessage = true + feedback = errorMessage } - fieldContainer?.setNeedsDisplay() + refreshUI() + } + + open func lockAppearance() { + + isUserInteractionEnabled = false + bottomBarHeightConstraint?.constant = 1 + hideBorder = true + bottomBar?.backgroundColor = .clear + + refreshUI() + } + + open func selectedAppearance() { + + isUserInteractionEnabled = true + bottomBarHeightConstraint?.constant = 1 + hideBorder = false + borderStrokeColor = .black + bottomBar?.backgroundColor = .black + + refreshUI() + } + + open func disabledAppearance() { + + isUserInteractionEnabled = false + bottomBarHeightConstraint?.constant = 1 + hideBorder = false + feedback = nil + descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() + bottomBar?.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin() : .black) : UIColor.mfSilver() + + refreshUI() + } + + open func refreshUI() { + + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + setNeedsDisplay() layoutIfNeeded() } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - open func showError(_ show: Bool) { - - showErrorMessage = show - - if show { - feedbackLabel?.text = errorMessage - fieldAppearance(.error) - } - } - - open func showDashSeperatorView(_ dash: Bool) { - - // Never hide seperator view because it could be possiblely used by other classes for positioning - dashLine?.isHidden = !dash - bottomBar?.backgroundColor = dash ? .clear : .black - } } // MARK: - Molecular @@ -368,6 +351,7 @@ extension FormEntryField { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject guard let dictionary = json, !dictionary.isEmpty @@ -389,6 +373,10 @@ extension FormEntryField { self.hideBorder = hideBorder } + if let appearance = dictionary["appearance"] as? String { + // let enu = Appearance(rawValue: appearance) + } + // Key used to send text value to server if let fieldKey = dictionary[KeyFieldKey] as? String { self.fieldKey = fieldKey @@ -396,7 +384,7 @@ extension FormEntryField { } override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 76 + return 115 } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 0eb61015..c95da2b9 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -25,14 +25,6 @@ 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 @@ -173,8 +165,7 @@ import UIKit } deinit { - mfTextFieldDelegate = nil - uiTextFieldDelegate = nil + setBothTextDelegates(nil) } //-------------------------------------------------- @@ -183,7 +174,7 @@ import UIKit open func clearError() { - feedback = nil + // feedback = nil textField?.accessibilityValue = nil } @@ -245,6 +236,7 @@ import UIKit func startEditing() { + errorAppearance(showError: true) textField?.becomeFirstResponder() } } diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 6197afe5..9166ddfd 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -25,12 +25,13 @@ open class DashLine: MFView { super.init(frame: .zero) } - public init() { - super.init(frame: .zero) + public convenience init() { + self.init(frame: .zero) } required public init?(coder: NSCoder) { super.init(coder: coder) + fatalError("DashLine xib not supported") } //------------------------------------------------------ From 4a5fd4f539766872c56b27cd9cf12dfb0c537f87 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 29 Oct 2019 12:21:19 -0400 Subject: [PATCH 027/112] ok. --- .../Atoms/TextFields/FormEntryField.swift | 60 ++++++++++++------- .../Atoms/TextFields/TextEntryField.swift | 4 +- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 7fe63f97..0a071508 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -23,7 +23,15 @@ import UIKit private var borderStrokeColor: UIColor = UIColor.mfSilver() private var borderPath: UIBezierPath = UIBezierPath() - public var bottomBar: UIView? + + public var bottomBar: CAShapeLayer = { + let layer = CAShapeLayer() + layer.backgroundColor = UIColor.black.cgColor + layer.drawsAsynchronously = true + layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); + + return layer + }() var delegateObject: MVMCoreUIDelegateObject? @@ -181,6 +189,8 @@ import UIKit feedbackLabelTrailing?.isActive = true layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true + fieldContainer.layer.addSublayer(bottomBar) + setNeedsLayout() } @@ -194,21 +204,7 @@ import UIKit /// Configuration for the field container view. private func setupFieldContainer(_ parentView: UIView) { - setupFieldContainerContent(parentView) - - let bottomBar = UIView(frame: .zero) - self.bottomBar = bottomBar - bottomBar.translatesAutoresizingMaskIntoConstraints = false - bottomBar.backgroundColor = .black - - parentView.addSubview(bottomBar) - - bottomBarHeightConstraint = bottomBar.heightAnchor.constraint(equalToConstant: 1) - bottomBarHeightConstraint?.isActive = true - bottomBar.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true - parentView.trailingAnchor.constraint(equalTo: bottomBar.trailingAnchor).isActive = true - parentView.bottomAnchor.constraint(equalTo: bottomBar.bottomAnchor).isActive = true } open override func updateView(_ size: CGFloat) { @@ -220,6 +216,14 @@ import UIKit refreshUI() } + open override func layoutSubviews() { + super.layoutSubviews() + + if let fieldHeight = fieldContainer?.bounds.height { + bottomBar.frame = CGRect(x: 0, y: fieldHeight, width: fieldHeight, height: 1) + } + } + //-------------------------------------------------- // MARK: - Drawing //-------------------------------------------------- @@ -233,7 +237,6 @@ import UIKit // Brings the other half of the line inside the view to prevent thinness from cropping. let insetLean: CGFloat = 0.5 - borderPath.lineWidth = 1 borderPath.move(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + frame.size.height)) @@ -246,6 +249,19 @@ import UIKit } } + func resizeBottomBar(from: CGFloat, to: CGFloat, duration: CFTimeInterval) { + + let bottomBarHeight = CABasicAnimation(keyPath: "bounds.size.height") + bottomBarHeight.fromValue = from + bottomBarHeight.toValue = to + bottomBarHeight.duration = 0.1 + bottomBarHeight.isRemovedOnCompletion = false + bottomBar.add(bottomBarHeight, forKey: "bottomBarHeight") + if let fieldBounds = fieldContainer?.bounds { + bottomBar.frame = CGRect(x: 0, y: fieldBounds.height, width: fieldBounds.width, height: to) + } + } + //-------------------------------------------------- // MARK: - Constraint Methods //-------------------------------------------------- @@ -283,7 +299,7 @@ import UIKit hideBorder = false showErrorMessage = false borderStrokeColor = .mfSilver() - bottomBar?.backgroundColor = .black + bottomBar.backgroundColor = UIColor.black.cgColor descriptionLabel?.textColor = .mfBattleshipGrey() refreshUI() @@ -295,13 +311,15 @@ import UIKit hideBorder = false bottomBarHeightConstraint?.constant = 4 borderStrokeColor = .mfPumpkin() - bottomBar?.backgroundColor = .mfPumpkin() + bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor if showError { showErrorMessage = true feedback = errorMessage } + resizeBottomBar(from: 1, to: 4, duration: 5) + refreshUI() } @@ -310,7 +328,7 @@ import UIKit isUserInteractionEnabled = false bottomBarHeightConstraint?.constant = 1 hideBorder = true - bottomBar?.backgroundColor = .clear + bottomBar.backgroundColor = UIColor.clear.cgColor refreshUI() } @@ -321,7 +339,7 @@ import UIKit bottomBarHeightConstraint?.constant = 1 hideBorder = false borderStrokeColor = .black - bottomBar?.backgroundColor = .black + bottomBar.backgroundColor = UIColor.black.cgColor refreshUI() } @@ -333,7 +351,7 @@ import UIKit hideBorder = false feedback = nil descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() - bottomBar?.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin() : .black) : UIColor.mfSilver() + bottomBar.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor) : UIColor.mfSilver().cgColor refreshUI() } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index c95da2b9..88645848 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -192,7 +192,7 @@ import UIKit } @objc func dismissTextFieldResponder(_ sender: Any?) { - + originalAppearance() textField?.resignFirstResponder() } @@ -228,7 +228,7 @@ import UIKit if isValid { clearError() - bottomBar?.backgroundColor = .black + bottomBar.backgroundColor = UIColor.black.cgColor } else if let errMessage = errorMessage { feedback = errMessage } From 3d98c91e73d8780a73f1b08de048043a44b6a5e6 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 29 Oct 2019 13:26:41 -0400 Subject: [PATCH 028/112] better, muchbetter. --- .../Atoms/TextFields/FormEntryField.swift | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 0a071508..67cf3d62 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -191,7 +191,8 @@ import UIKit fieldContainer.layer.addSublayer(bottomBar) - setNeedsLayout() + setNeedsDisplay() + layoutIfNeeded() } /** @@ -219,9 +220,7 @@ import UIKit open override func layoutSubviews() { super.layoutSubviews() - if let fieldHeight = fieldContainer?.bounds.height { - bottomBar.frame = CGRect(x: 0, y: fieldHeight, width: fieldHeight, height: 1) - } + resizeBottomBar(size: 1) } //-------------------------------------------------- @@ -247,18 +246,25 @@ import UIKit borderStrokeColor.setStroke() borderPath.stroke() } + + layoutIfNeeded() } - func resizeBottomBar(from: CGFloat, to: CGFloat, duration: CFTimeInterval) { + func resizeBottomBar(size: CGFloat) { + +// if bottomBar.frame.height == 1 { +// +// } + +// let bottomBarHeight = CABasicAnimation(keyPath: "bounds.size.height") +// bottomBarHeight.fromValue = 1 +// bottomBarHeight.toValue = 4 +// bottomBarHeight.duration = 0.1 +// bottomBarHeight.isRemovedOnCompletion = false +// bottomBar.add(bottomBarHeight, forKey: "bottomBarHeight") - let bottomBarHeight = CABasicAnimation(keyPath: "bounds.size.height") - bottomBarHeight.fromValue = from - bottomBarHeight.toValue = to - bottomBarHeight.duration = 0.1 - bottomBarHeight.isRemovedOnCompletion = false - bottomBar.add(bottomBarHeight, forKey: "bottomBarHeight") if let fieldBounds = fieldContainer?.bounds { - bottomBar.frame = CGRect(x: 0, y: fieldBounds.height, width: fieldBounds.width, height: to) + bottomBar.frame = CGRect(x: 0, y: fieldBounds.height - size, width: fieldBounds.width, height: size) } } @@ -302,6 +308,8 @@ import UIKit bottomBar.backgroundColor = UIColor.black.cgColor descriptionLabel?.textColor = .mfBattleshipGrey() + resizeBottomBar(size: 1) + refreshUI() } @@ -318,7 +326,7 @@ import UIKit feedback = errorMessage } - resizeBottomBar(from: 1, to: 4, duration: 5) + resizeBottomBar(size: 4) refreshUI() } @@ -330,6 +338,8 @@ import UIKit hideBorder = true bottomBar.backgroundColor = UIColor.clear.cgColor + resizeBottomBar(size: 1) + refreshUI() } @@ -341,6 +351,8 @@ import UIKit borderStrokeColor = .black bottomBar.backgroundColor = UIColor.black.cgColor + resizeBottomBar(size: 1) + refreshUI() } @@ -353,6 +365,8 @@ import UIKit descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() bottomBar.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor) : UIColor.mfSilver().cgColor + resizeBottomBar(size: 1) + refreshUI() } From d2d4e3d42c930e922d7cd2ce37490f4f21b2af90 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 29 Oct 2019 13:40:25 -0400 Subject: [PATCH 029/112] hold for now. --- .../Atoms/TextFields/FormEntryField.swift | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 67cf3d62..a16cafc5 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -308,9 +308,7 @@ import UIKit bottomBar.backgroundColor = UIColor.black.cgColor descriptionLabel?.textColor = .mfBattleshipGrey() - resizeBottomBar(size: 1) - - refreshUI() + refreshUI(bottomBarSize: 1) } open func errorAppearance(showError: Bool = false) { @@ -326,9 +324,7 @@ import UIKit feedback = errorMessage } - resizeBottomBar(size: 4) - - refreshUI() + refreshUI(bottomBarSize: 4) } open func lockAppearance() { @@ -338,9 +334,7 @@ import UIKit hideBorder = true bottomBar.backgroundColor = UIColor.clear.cgColor - resizeBottomBar(size: 1) - - refreshUI() + refreshUI(bottomBarSize: 1) } open func selectedAppearance() { @@ -351,9 +345,7 @@ import UIKit borderStrokeColor = .black bottomBar.backgroundColor = UIColor.black.cgColor - resizeBottomBar(size: 1) - - refreshUI() + refreshUI(bottomBarSize: 1) } open func disabledAppearance() { @@ -365,12 +357,14 @@ import UIKit descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() bottomBar.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor) : UIColor.mfSilver().cgColor - resizeBottomBar(size: 1) - - refreshUI() + refreshUI(bottomBarSize: 1) } - open func refreshUI() { + open func refreshUI(bottomBarSize: CGFloat? = nil) { + + if let size = bottomBarSize, let fieldBounds = fieldContainer?.bounds { + bottomBar.frame = CGRect(x: 0, y: fieldBounds.height - size, width: fieldBounds.width, height: size) + } self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) setNeedsDisplay() From 494df94d3aa053182247076e68d8703ee3ff3fdd Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 30 Oct 2019 09:35:54 -0400 Subject: [PATCH 030/112] correction. --- .../Atoms/TextFields/DigitEntryField.swift | 27 +++---- .../Atoms/TextFields/FormEntryField.swift | 15 +--- .../Atoms/TextFields/TextEntryField.swift | 73 +++++++++---------- 3 files changed, 52 insertions(+), 63 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 3c30311d..9618c989 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -155,28 +155,29 @@ import UIKit fatalError("DigitEntryField xib has not been implemented") } - public init(numberOfDigits: Int) { - super.init(frame: .zero) - + public override init(frame: CGRect) { + super.init(frame: frame) setup() + } + + public convenience init() { + self.init(frame: .zero) + } + + public convenience init(numberOfDigits: Int) { + self.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)) - - setup() - self.numberOfDigits = numberOfDigits - buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) - } - - public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { + public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat? = nil) { super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) setup() self.numberOfDigits = numberOfDigits - buildTextFieldsView(size: size) + buildTextFieldsView(size: size ?? MVMCoreUISplitViewController.getDetailViewWidth()) } open override func setupFieldContainerContent(_ container: UIView) { diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index a16cafc5..39f8893f 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -181,7 +181,7 @@ import UIKit addSubview(feedbackLabel) - feedbackLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true + feedbackLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 15).isActive = true feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne).isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true @@ -220,7 +220,7 @@ import UIKit open override func layoutSubviews() { super.layoutSubviews() - resizeBottomBar(size: 1) + refreshUI(bottomBarSize: 1) } //-------------------------------------------------- @@ -252,17 +252,6 @@ import UIKit func resizeBottomBar(size: CGFloat) { -// if bottomBar.frame.height == 1 { -// -// } - -// let bottomBarHeight = CABasicAnimation(keyPath: "bounds.size.height") -// bottomBarHeight.fromValue = 1 -// bottomBarHeight.toValue = 4 -// bottomBarHeight.duration = 0.1 -// bottomBarHeight.isRemovedOnCompletion = false -// bottomBar.add(bottomBarHeight, forKey: "bottomBarHeight") - if let fieldBounds = fieldContainer?.bounds { bottomBar.frame = CGRect(x: 0, y: fieldBounds.height - size, width: fieldBounds.width, height: size) } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 88645848..697ec59f 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -26,33 +26,6 @@ import UIKit private(set) var textField: UITextField? - //-------------------------------------------------- - // 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 //-------------------------------------------------- @@ -94,6 +67,33 @@ import UIKit } } + //-------------------------------------------------- + // 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: - Constraints //-------------------------------------------------- @@ -109,7 +109,6 @@ import UIKit setupView() } - /// Basic initializer. public convenience init() { self.init(frame: .zero) } @@ -120,9 +119,9 @@ import UIKit } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - super.init(frame: .zero) - setupView() + public convenience init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + self.init(frame: .zero) + setBothTextDelegates(bothDelegates) } @@ -174,7 +173,7 @@ import UIKit open func clearError() { - // feedback = nil + feedback = nil textField?.accessibilityValue = nil } @@ -192,7 +191,7 @@ import UIKit } @objc func dismissTextFieldResponder(_ sender: Any?) { - originalAppearance() + textField?.resignFirstResponder() } @@ -200,7 +199,7 @@ import UIKit // MARK: - Observing for change //-------------------------------------------------- - func valueChanged() { + @objc func valueChanged() { if !showErrorMessage { feedback = "" @@ -224,19 +223,19 @@ import UIKit } } - func endInputing() { + @objc func endInputing() { if isValid { clearError() bottomBar.backgroundColor = UIColor.black.cgColor + } else if let errMessage = errorMessage { feedback = errMessage } } - func startEditing() { + @objc func startEditing() { - errorAppearance(showError: true) textField?.becomeFirstResponder() } } From a455511809dc4eb91d00054b0d49562e34aa3319 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 30 Oct 2019 09:58:20 -0400 Subject: [PATCH 031/112] reconfig. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 1 - MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift | 3 +++ MVMCoreUI/Atoms/TextFields/FormEntryField.swift | 2 +- MVMCoreUI/Atoms/TextFields/TextEntryField.swift | 5 +++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 9618c989..47d3899c 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -167,7 +167,6 @@ import UIKit public convenience init(numberOfDigits: Int) { self.init(frame: .zero) - self.numberOfDigits = numberOfDigits buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 401208b8..e0d5a20c 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -66,9 +66,12 @@ import UIKit public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) + setupView() + if let textField = textField { MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) } + setBothTextDelegates(bothDelegates) } diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 39f8893f..322462b5 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -181,7 +181,7 @@ import UIKit addSubview(feedbackLabel) - feedbackLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 15).isActive = true + feedbackLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne).isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 697ec59f..93c2cb3c 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -119,9 +119,10 @@ import UIKit } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public convenience init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - self.init(frame: .zero) + public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + super.init(frame: .zero) + setupView() setBothTextDelegates(bothDelegates) } From c9b41c9bc1aa1a3d0c17da26273a822cae700274 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 30 Oct 2019 11:23:03 -0400 Subject: [PATCH 032/112] Latest revision. --- .../Atoms/TextFields/DigitEntryField.swift | 70 +++--- .../Atoms/TextFields/DropdownEntryField.swift | 11 +- .../Atoms/TextFields/FormEntryField.swift | 201 ++++++++++-------- .../Atoms/TextFields/MdnEntryField.swift | 12 +- .../Atoms/TextFields/TextEntryField.swift | 61 +++--- 5 files changed, 186 insertions(+), 169 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 47d3899c..d4064106 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -27,9 +27,9 @@ import UIKit public override var isEnabled: Bool { didSet { if isEnabled { - descriptionLabel?.styleB2(true) + titleLabel.styleB2(true) } else { - descriptionLabel?.textColor = UIColor.mfBattleshipGrey() + titleLabel.textColor = UIColor.mfBattleshipGrey() } for textField in digitFields ?? [] { @@ -63,29 +63,29 @@ import UIKit NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) } } -// -// if feedback.text.length > 0 { -// labelToTextFieldPin?.constant = 10 -// } else { -// labelToTextFieldPin?.constant = 0 -// } + // + // if feedback.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")) -// -// super.showErrorMessage(errorMessage) -// -// if self.showErrorMessage { -// self.labelToTextFieldPin?.constant = 10 -// } -// for field in self.digitFields ?? [] { -// field.setAsError() -// } + // textField.accessibilityLabel() = newValue ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular")) + // + // super.showErrorMessage(errorMessage) + // + // if self.showErrorMessage { + // self.labelToTextFieldPin?.constant = 10 + // } + // for field in self.digitFields ?? [] { + // field.setAsError() + // } } } - + public override var text: String? { get { var string = "" @@ -107,13 +107,13 @@ import UIKit field.text = stringForIndex } } - + valueChanged() } } - public override var descriptionText: String? { - get { return descriptionLabel?.text } + public override var title: String? { + get { return titleLabel.text } set { if let formText = newValue, !formText.isEmpty { messageToTextFieldPin?.constant = 10 @@ -121,7 +121,7 @@ import UIKit messageToTextFieldPin?.constant = 0 } - super.descriptionText = newValue + super.title = newValue } } @@ -190,8 +190,8 @@ import UIKit open func setup() { - descriptionLabel?.styleB2(true) - descriptionText = "" + titleLabel.styleB2(true) + title = "" alignCenterHorizontal() } @@ -201,7 +201,7 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.descriptionLabel?.updateView(size) + self.titleLabel.updateView(size) if let digitFields = self.digitFields, !digitFields.isEmpty { @@ -262,7 +262,7 @@ import UIKit if let feedback = self.feedback, !feedback.isEmpty { self.labelToTextFieldPin?.constant = 10 - + } else { self.labelToTextFieldPin?.constant = 0 } @@ -398,7 +398,7 @@ import UIKit } field.becomeFirstResponder() self.switchedAutomatically = false - + UIAccessibility.post(notification: .layoutChanged, argument: field) } } @@ -409,11 +409,11 @@ import UIKit //-------------------------------------------------- open override class func accessibilityElements() -> [Any]? { -// self.digit -// if let digitFields = self.digitFields { -// return [digitFields] + [textField] -// } -// + // self.digit + // if let digitFields = self.digitFields { + // return [digitFields] + [textField] + // } + // return [textField] } @@ -475,7 +475,7 @@ import UIKit textField.text = "" valueChanged() } - + uiTextFieldDelegate?.textFieldDidBeginEditing?(textField) } @@ -487,7 +487,7 @@ import UIKit @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { selectPreviousTextField(textField, clear: false) - + return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true } diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index e0d5a20c..7b0b9ee2 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -68,9 +68,7 @@ import UIKit setupView() - if let textField = textField { - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) - } + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) setBothTextDelegates(bothDelegates) } @@ -81,7 +79,6 @@ import UIKit open override func setupFieldContainerContent(_ container: UIView) { - guard let textField = textField else { return } let dropDownCaretLabel = Label() self.dropDownCaretLabel = dropDownCaretLabel @@ -144,8 +141,6 @@ extension DropdownEntryField { private func createDatePicker() { - guard let textField = textField else { return } - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) @@ -197,7 +192,7 @@ extension DropdownEntryField { } } - textField?.resignFirstResponder() + textField.resignFirstResponder() return pickedDate } } @@ -223,8 +218,6 @@ extension DropdownEntryField { open override func setAccessibilityString(_ accessibilityString: String?) { - guard let textField = textField else { return } - var accessibilityString = accessibilityString ?? "" if dropDownIsDisplayed, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 322462b5..8fcdaec2 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -17,11 +17,31 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - private(set) var descriptionLabel: Label? - private(set) var feedbackLabel: Label? - private(set) var fieldContainer: UIView? + private(set) var titleLabel: Label = { + let label = Label() + label.font = MFStyler.fontB3() + label.textColor = UIColor.mfBattleshipGrey() + label.setContentCompressionResistancePriority(.required, for: .vertical) + label.setContentCompressionResistancePriority(.required, for: .horizontal) + return label + }() - private var borderStrokeColor: UIColor = UIColor.mfSilver() + private(set) var feedbackLabel: Label = { + let label = Label() + label.font = MFStyler.fontForTextFieldUnderLabel() + label.textColor = .black + label.setContentCompressionResistancePriority(.required, for: .vertical) + label.setContentCompressionResistancePriority(.required, for: .horizontal) + return label + }() + + private(set) var fieldContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + private var borderStrokeColor: UIColor = .mfSilver() private var borderPath: UIBezierPath = UIBezierPath() public var bottomBar: CAShapeLayer = { @@ -44,7 +64,12 @@ import UIKit /// Determines if a border should be drawn. public var hideBorder = false - public var showErrorMessage = false + + public var showErrorMessage = false { + didSet { + feedbackHeightConstraint?.isActive = !showErrorMessage + } + } public var errorMessage: String? @@ -54,15 +79,15 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isEnabled ? self.originalAppearance() : self.errorAppearance() + self.isEnabled ? self.originalAppearance() : self.disabledAppearance() } } } - public var descriptionText: String? { - get { return descriptionLabel?.text } + public var title: String? { + get { return titleLabel.text } set { - descriptionLabel?.text = newValue + titleLabel.text = newValue setAccessibilityString(newValue) } } @@ -70,16 +95,16 @@ import UIKit /// Override this to conveniently get/set the textfield(s). public var text: String? { get { return nil } - set { fatalError("You need to override FormEntryField's 'text' variable in the subclass.") } + set { fatalError("You need to override FormEntryField's 'text' variable in your subclass.") } } /// Sets feedback text in the textField. public var feedback: String? { - get { return feedbackLabel?.text } + get { return feedbackLabel.text } set { - self.feedbackLabel?.text = newValue - self.refreshUI() + feedbackLabel.text = newValue setAccessibilityString(newValue) + refreshUI() } } @@ -104,9 +129,10 @@ import UIKit public var feedbackLabelTrailing: NSLayoutConstraint? public var feedbackLabelLeading: NSLayoutConstraint? + var feedbackHeightConstraint: NSLayoutConstraint? - public var descriptionLabelLeading: NSLayoutConstraint? - public var descriptionLabelTrailing: NSLayoutConstraint? + public var titleLabelLeading: NSLayoutConstraint? + public var titleLabelTrailing: NSLayoutConstraint? public var bottomBarHeightConstraint: NSLayoutConstraint? @@ -117,15 +143,19 @@ import UIKit /// This must be overriden by a subclass. public override init(frame: CGRect) { super.init(frame: frame) - setupView() } - /// This must be overriden by a subclass. public convenience init() { self.init(frame: .zero) } + public init(title: String) { + super.init(frame: .zero) + setupView() + self.titleLabel.text = title + } + required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("FormEntryField does not support xib.") @@ -139,49 +169,31 @@ import UIKit final public override func setupView() { guard subviews.isEmpty else { return } - // heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true + translatesAutoresizingMaskIntoConstraints = false setContentCompressionResistancePriority(.required, for: .vertical) backgroundColor = .clear - let descriptionLabel = Label() - self.descriptionLabel = descriptionLabel - descriptionLabel.font = MFStyler.fontB3() - descriptionLabel.textColor = UIColor.mfBattleshipGrey() - descriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) - descriptionLabel.setContentCompressionResistancePriority(.required, for: .horizontal) + addSubview(titleLabel) - addSubview(descriptionLabel) - - descriptionLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true - descriptionLabelLeading = descriptionLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - descriptionLabelLeading?.isActive = true - descriptionLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor) - descriptionLabelLeading?.isActive = true - - let fieldContainer = UIView(frame: .zero) - self.fieldContainer = fieldContainer - fieldContainer.translatesAutoresizingMaskIntoConstraints = false + titleLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + titleLabelLeading = titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + titleLabelLeading?.isActive = true + titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor) + titleLabelLeading?.isActive = true addSubview(fieldContainer) - setupFieldContainer(fieldContainer) + setupFieldContainerContent(fieldContainer) - fieldContainer.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 4).isActive = true + fieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4).isActive = true fieldContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) fieldContainerLeading?.isActive = true fieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) fieldContainerTrailing?.isActive = true - let feedbackLabel = Label() - self.feedbackLabel = feedbackLabel - feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() - feedbackLabel.textColor = .black - feedbackLabel.setContentCompressionResistancePriority(.required, for: .vertical) - feedbackLabel.setContentCompressionResistancePriority(.required, for: .horizontal) - addSubview(feedbackLabel) - feedbackLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true + feedbackHeightConstraint = feedbackLabel.heightAnchor.constraint(equalToConstant: 0) feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne).isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true @@ -197,32 +209,47 @@ import UIKit /** Method to override. - Intended to add the interactive content (textField) to the fieldContainer. + Intended to add the interactive content (i.e. textField) to the fieldContainer. */ open func setupFieldContainerContent(_ container: UIView) { - // To Be Overridden By Subclass. - } - - /// Configuration for the field container view. - private func setupFieldContainer(_ parentView: UIView) { - setupFieldContainerContent(parentView) + // To be overridden by subclass. } open override func updateView(_ size: CGFloat) { super.updateView(size) - descriptionLabel?.updateView(size) - feedbackLabel?.font = MFStyler.fontForTextFieldUnderLabel() - + titleLabel.updateView(size) + feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() refreshUI() } + open override func reset() { + super.reset() + + backgroundColor = .clear + titleLabel.reset() + feedbackLabel.reset() + fieldContainer.subviews.forEach { $0.removeFromSuperview() } + originalAppearance() + } + open override func layoutSubviews() { super.layoutSubviews() refreshUI(bottomBarSize: 1) } + open func refreshUI(bottomBarSize: CGFloat? = nil) { + + if let size = bottomBarSize { + bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) + } + + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + setNeedsDisplay() + layoutIfNeeded() + } + //-------------------------------------------------- // MARK: - Drawing //-------------------------------------------------- @@ -232,16 +259,18 @@ import UIKit borderPath.removeAllPoints() - if !hideBorder, let frame = fieldContainer?.frame { + if !hideBorder { // Brings the other half of the line inside the view to prevent thinness from cropping. + let origin = fieldContainer.frame.origin + let size = fieldContainer.frame.size let insetLean: CGFloat = 0.5 borderPath.lineWidth = 1 - borderPath.move(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + frame.size.height)) - borderPath.addLine(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + insetLean)) - borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + insetLean)) - borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + frame.size.height)) + 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() @@ -252,9 +281,7 @@ import UIKit func resizeBottomBar(size: CGFloat) { - if let fieldBounds = fieldContainer?.bounds { - bottomBar.frame = CGRect(x: 0, y: fieldBounds.height - size, width: fieldBounds.width, height: size) - } + bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) } //-------------------------------------------------- @@ -265,18 +292,18 @@ import UIKit fieldContainerLeading?.constant = constant feedbackLabelLeading?.constant = constant - descriptionLabelLeading?.constant = constant + titleLabelLeading?.constant = constant } open override func setRightPinConstant(_ constant: CGFloat) { fieldContainerTrailing?.constant = constant feedbackLabelTrailing?.constant = constant - descriptionLabelTrailing?.constant = constant + titleLabelTrailing?.constant = constant } //-------------------------------------------------- - // MARK: - Form Appearance + // MARK: - Form UI Base Appearance //-------------------------------------------------- public enum Appearance: String { @@ -295,11 +322,11 @@ import UIKit showErrorMessage = false borderStrokeColor = .mfSilver() bottomBar.backgroundColor = UIColor.black.cgColor - descriptionLabel?.textColor = .mfBattleshipGrey() - + titleLabel.textColor = .mfBattleshipGrey() refreshUI(bottomBarSize: 1) } + /// - parameter showError: Default = false. Determines if error message should be displayed when setting the visual error appearance. open func errorAppearance(showError: Bool = false) { isUserInteractionEnabled = true @@ -322,7 +349,6 @@ import UIKit bottomBarHeightConstraint?.constant = 1 hideBorder = true bottomBar.backgroundColor = UIColor.clear.cgColor - refreshUI(bottomBarSize: 1) } @@ -333,7 +359,6 @@ import UIKit hideBorder = false borderStrokeColor = .black bottomBar.backgroundColor = UIColor.black.cgColor - refreshUI(bottomBarSize: 1) } @@ -343,22 +368,10 @@ import UIKit bottomBarHeightConstraint?.constant = 1 hideBorder = false feedback = nil - descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() - bottomBar.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor) : UIColor.mfSilver().cgColor - + titleLabel.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() + bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor refreshUI(bottomBarSize: 1) } - - open func refreshUI(bottomBarSize: CGFloat? = nil) { - - if let size = bottomBarSize, let fieldBounds = fieldContainer?.bounds { - bottomBar.frame = CGRect(x: 0, y: fieldBounds.height - size, width: fieldBounds.width, height: size) - } - - self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) - setNeedsDisplay() - layoutIfNeeded() - } } // MARK: - Molecular @@ -373,7 +386,7 @@ extension FormEntryField { else { return } if let formText = dictionary[KeyLabel] as? String { - descriptionText = formText + title = formText } if let text = dictionary[KeyDisable] as? String, text.isEqual(StringY) || dictionary.boolForKey(KeyDisable) { @@ -389,7 +402,23 @@ extension FormEntryField { } if let appearance = dictionary["appearance"] as? String { - // let enu = Appearance(rawValue: appearance) + switch Appearance(rawValue: appearance) { + case .error: + errorAppearance() + + case .lock: + lockAppearance() + + case .select: + selectedAppearance() + + case .disabled: + // This is set by the isEnabled property observer. + isEnabled = false + + default: + originalAppearance() + } } // Key used to send text value to server diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index f06c9a0a..ae4253a1 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -48,15 +48,15 @@ import MVMCore //-------------------------------------------------- private func setup() { - - textField?.keyboardType = .numberPad + + 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(dismissTextFieldResponder(_:))) toolbar.items = [contacts, space, dismissButton] - textField?.inputAccessoryView = toolbar + textField.inputAccessoryView = toolbar } //-------------------------------------------------- @@ -126,10 +126,8 @@ import MVMCore text = unformattedMDN - if let textField = textField { - textFieldShouldReturn(textField) - textFieldDidEndEditing(textField) - } + textFieldShouldReturn(textField) + textFieldDidEndEditing(textField) } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 93c2cb3c..4e9542fe 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -24,7 +24,16 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - private(set) var textField: UITextField? + private(set) var textField: UITextField = { + let textField = UITextField(frame: .zero) + textField.translatesAutoresizingMaskIntoConstraints = false + textField.setContentCompressionResistancePriority(.required, for: .vertical) + textField.font = MFStyler.fontForTextField() + textField.smartQuotesType = .no + textField.smartDashesType = .no + textField.smartInsertDeleteType = .no + return textField + }() //-------------------------------------------------- // MARK: - Properties @@ -40,17 +49,17 @@ 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 + self.textField.isEnabled = self.isEnabled + self.textField.textColor = self.isEnabled ? self.enabledTextColor : self.enabledTextColor } } } // The text of this textField. public override var text: String? { - get { return textField?.text } + get { return textField.text } set { - textField?.text = newValue + textField.text = newValue valueChanged() } } @@ -63,7 +72,7 @@ import UIKit public override var errorMessage: String? { didSet { - textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField?.text ?? "", errorMessage ?? "") + textField.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField.text ?? "", errorMessage ?? "") } } @@ -90,8 +99,8 @@ import UIKit /// 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 } + get { return textField.delegate } + set { textField.delegate = newValue } } //-------------------------------------------------- @@ -121,7 +130,7 @@ import UIKit /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) - + setupView() setBothTextDelegates(bothDelegates) } @@ -132,20 +141,12 @@ import UIKit open override func setupFieldContainerContent(_ container: UIView) { - 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) container.addSubview(textField) NSLayoutConstraint.activate([ + textField.heightAnchor.constraint(equalToConstant: 24), textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 10), textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)]) @@ -157,9 +158,9 @@ import UIKit open override func updateView(_ size: CGFloat) { super.updateView(size) - if let textField = textField { - MFStyler.styleTextField(textField) - } + + MFStyler.styleTextField(textField) + layoutIfNeeded() } @@ -175,7 +176,7 @@ import UIKit open func clearError() { feedback = nil - textField?.accessibilityValue = nil + textField.accessibilityValue = nil } public func setBothTextDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { @@ -193,7 +194,7 @@ import UIKit @objc func dismissTextFieldResponder(_ sender: Any?) { - textField?.resignFirstResponder() + textField.resignFirstResponder() } //-------------------------------------------------- @@ -237,7 +238,7 @@ import UIKit @objc func startEditing() { - textField?.becomeFirstResponder() + textField.becomeFirstResponder() } } @@ -264,13 +265,13 @@ extension TextEntryField { switch dictionary.stringForkey(KeyType) { case "password": - textField?.isSecureTextEntry = true + textField.isSecureTextEntry = true case "number": - textField?.keyboardType = .numberPad + textField.keyboardType = .numberPad case "email": - textField?.keyboardType = .emailAddress + textField.keyboardType = .emailAddress default: break @@ -293,9 +294,7 @@ extension TextEntryField { uiTextFieldDelegate = delegateObject.uiTextFieldDelegate - if let textField = textField { - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) - } + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) } } @@ -313,8 +312,6 @@ extension TextEntryField { open override func setAccessibilityString(_ accessibilityString: String?) { - guard let textField = textField else { return } - var accessibilityString = accessibilityString ?? "" if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") { From 8266b3b650d80c87fc5f0c5fd2b3602d1496aa3a Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 30 Oct 2019 11:36:59 -0400 Subject: [PATCH 033/112] fixed display issues. --- .../Atoms/TextFields/DigitEntryField.swift | 10 +++--- .../Atoms/TextFields/DropdownEntryField.swift | 35 +++++++++---------- .../Atoms/TextFields/TextEntryField.swift | 10 +++--- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index d4064106..7d9e0edb 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -150,11 +150,6 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("DigitEntryField xib has not been implemented") - } - public override init(frame: CGRect) { super.init(frame: frame) setup() @@ -179,6 +174,11 @@ import UIKit buildTextFieldsView(size: size ?? MVMCoreUISplitViewController.getDetailViewWidth()) } + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("DigitEntryField xib has not been implemented") + } + open override func setupFieldContainerContent(_ container: UIView) { setupTextFieldsView(forSize: CGFloat(numberOfDigits)) diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 7b0b9ee2..887ff1b3 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -14,6 +14,16 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- + let dropDownCaretLabel: Label = { + let label = Label() + label.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) + label.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + label.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) + label.isHidden = true + label.isUserInteractionEnabled = true + return label + }() + private var calendar: Calendar? //-------------------------------------------------- @@ -28,7 +38,6 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - public var dropDownCaretLabel: UILabel? public var dropDownIsDisplayed = false public override var isEnabled: Bool { @@ -57,36 +66,26 @@ import UIKit 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 override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) setupView() - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) - setBothTextDelegates(bothDelegates) } + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("DropdownEntryField does not support xib.") + } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setupFieldContainerContent(_ container: UIView) { - - let dropDownCaretLabel = Label() - self.dropDownCaretLabel = dropDownCaretLabel - dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) - dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) - dropDownCaretLabel.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) - dropDownCaretLabel.isHidden = true - dropDownCaretLabel.isUserInteractionEnabled = true let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) dropDownCaretLabel.addGestureRecognizer(tapOnCarrot) @@ -106,7 +105,7 @@ import UIKit public func showDropDown(_ show: Bool) { - dropDownCaretLabel?.isHidden = !show + dropDownCaretLabel.isHidden = !show dropDownCaretWidth?.isActive = !show setNeedsLayout() layoutIfNeeded() @@ -208,7 +207,7 @@ extension DropdownEntryField { else { return } if let _ = dictionary[KeyType] as? String { - dropDownCaretLabel?.isHidden = false + dropDownCaretLabel.isHidden = false } } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 4e9542fe..d2795c27 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -122,11 +122,6 @@ import UIKit self.init(frame: .zero) } - required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("TextEntryField does not support xib.") - } - /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) @@ -135,6 +130,11 @@ import UIKit setBothTextDelegates(bothDelegates) } + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("TextEntryField does not support xib.") + } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- From 9d043edb29f841577664668c07ceaf0b1c62a124 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 30 Oct 2019 12:33:40 -0400 Subject: [PATCH 034/112] comm. --- MVMCoreUI/Atoms/TextFields/FormEntryField.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 8fcdaec2..a7530ac6 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -11,6 +11,8 @@ 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. + * + * When subclassing, be sure to override setupFieldContainerContent(). In this method you will setup all the content bound to the field container. */ @objcMembers open class FormEntryField: ViewConstrainingView { //-------------------------------------------------- From 5c1d7b4a387343646cf93ae67c3aea9683a42721 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 30 Oct 2019 16:02:18 -0400 Subject: [PATCH 035/112] Keeping up appearance. --- .../Atoms/TextFields/FormEntryField.swift | 145 +++++++----------- .../Atoms/TextFields/TextEntryField.swift | 10 +- 2 files changed, 57 insertions(+), 98 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index a7530ac6..118740bb 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -19,7 +19,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - private(set) var titleLabel: Label = { + public private(set) var titleLabel: Label = { let label = Label() label.font = MFStyler.fontB3() label.textColor = UIColor.mfBattleshipGrey() @@ -28,7 +28,7 @@ import UIKit return label }() - private(set) var feedbackLabel: Label = { + public private(set) var feedbackLabel: Label = { let label = Label() label.font = MFStyler.fontForTextFieldUnderLabel() label.textColor = .black @@ -37,7 +37,7 @@ import UIKit return label }() - private(set) var fieldContainer: UIView = { + public private(set) var fieldContainer: UIView = { let view = UIView(frame: .zero) view.translatesAutoresizingMaskIntoConstraints = false return view @@ -65,13 +65,11 @@ import UIKit public var fieldKey: String? /// Determines if a border should be drawn. - public var hideBorder = false + private var hideBorder = false - public var showErrorMessage = false { - didSet { - feedbackHeightConstraint?.isActive = !showErrorMessage - } - } + public private(set) var appearance: Appearance = .original + + public var showErrorMessage = false public var errorMessage: String? @@ -81,7 +79,7 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isEnabled ? self.originalAppearance() : self.disabledAppearance() + self.updateUI(appearance: self.isEnabled ? .original : .disable) } } } @@ -106,7 +104,7 @@ import UIKit set { feedbackLabel.text = newValue setAccessibilityString(newValue) - refreshUI() + refreshBorderUI() } } @@ -131,13 +129,10 @@ import UIKit public var feedbackLabelTrailing: NSLayoutConstraint? public var feedbackLabelLeading: NSLayoutConstraint? - var feedbackHeightConstraint: NSLayoutConstraint? public var titleLabelLeading: NSLayoutConstraint? public var titleLabelTrailing: NSLayoutConstraint? - public var bottomBarHeightConstraint: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -195,7 +190,6 @@ import UIKit addSubview(feedbackLabel) - feedbackHeightConstraint = feedbackLabel.heightAnchor.constraint(equalToConstant: 0) feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne).isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true @@ -222,7 +216,7 @@ import UIKit titleLabel.updateView(size) feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() - refreshUI() + refreshBorderUI() } open override func reset() { @@ -232,20 +226,19 @@ import UIKit titleLabel.reset() feedbackLabel.reset() fieldContainer.subviews.forEach { $0.removeFromSuperview() } - originalAppearance() + updateUI(appearance: .original) } open override func layoutSubviews() { super.layoutSubviews() - refreshUI(bottomBarSize: 1) + refreshBorderUI(bottomBarSize: 1) } - open func refreshUI(bottomBarSize: CGFloat? = nil) { + open func refreshBorderUI(bottomBarSize: CGFloat? = nil) { - if let size = bottomBarSize { - bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) - } + let size = CGFloat(appearance == .error ? 4 : 1) + bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) setNeedsDisplay() @@ -313,66 +306,50 @@ import UIKit case error case lock case select - case disabled + case disable } - open func originalAppearance() { - - isUserInteractionEnabled = true - bottomBarHeightConstraint?.constant = 1 - hideBorder = false - showErrorMessage = false - borderStrokeColor = .mfSilver() - bottomBar.backgroundColor = UIColor.black.cgColor - titleLabel.textColor = .mfBattleshipGrey() - refreshUI(bottomBarSize: 1) - } - - /// - parameter showError: Default = false. Determines if error message should be displayed when setting the visual error appearance. - open func errorAppearance(showError: Bool = false) { + public func updateUI(appearance: Appearance) { + self.appearance = appearance isUserInteractionEnabled = true hideBorder = false - bottomBarHeightConstraint?.constant = 4 - borderStrokeColor = .mfPumpkin() - bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor - if showError { - showErrorMessage = true - feedback = errorMessage + switch appearance { + case .original: + borderStrokeColor = .mfSilver() + feedback = nil + bottomBar.backgroundColor = UIColor.black.cgColor + titleLabel.textColor = .mfBattleshipGrey() + + case .error: + borderStrokeColor = .mfPumpkin() + titleLabel.textColor = UIColor.mfBattleshipGrey() + bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor + feedback = showErrorMessage ? errorMessage : nil + + case .lock: + isUserInteractionEnabled = false + hideBorder = true + feedback = nil + titleLabel.textColor = UIColor.mfBattleshipGrey() + bottomBar.backgroundColor = UIColor.clear.cgColor + + case .select: + borderStrokeColor = .black + feedback = nil + titleLabel.textColor = UIColor.mfBattleshipGrey() + bottomBar.backgroundColor = UIColor.black.cgColor + + case .disable: + isUserInteractionEnabled = false + feedback = nil + borderStrokeColor = .mfSilver() + titleLabel.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() + bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor } - refreshUI(bottomBarSize: 4) - } - - open func lockAppearance() { - - isUserInteractionEnabled = false - bottomBarHeightConstraint?.constant = 1 - hideBorder = true - bottomBar.backgroundColor = UIColor.clear.cgColor - refreshUI(bottomBarSize: 1) - } - - open func selectedAppearance() { - - isUserInteractionEnabled = true - bottomBarHeightConstraint?.constant = 1 - hideBorder = false - borderStrokeColor = .black - bottomBar.backgroundColor = UIColor.black.cgColor - refreshUI(bottomBarSize: 1) - } - - open func disabledAppearance() { - - isUserInteractionEnabled = false - bottomBarHeightConstraint?.constant = 1 - hideBorder = false - feedback = nil - titleLabel.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() - bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor - refreshUI(bottomBarSize: 1) + refreshBorderUI(bottomBarSize: appearance == .error ? 4 : 1) } } @@ -403,24 +380,8 @@ extension FormEntryField { self.hideBorder = hideBorder } - if let appearance = dictionary["appearance"] as? String { - switch Appearance(rawValue: appearance) { - case .error: - errorAppearance() - - case .lock: - lockAppearance() - - case .select: - selectedAppearance() - - case .disabled: - // This is set by the isEnabled property observer. - isEnabled = false - - default: - originalAppearance() - } + if let appearance = dictionary["appearance"] as? String, let kind = Appearance(rawValue: appearance) { + updateUI(appearance: kind) } // Key used to send text value to server diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index d2795c27..07f4e5be 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -50,7 +50,7 @@ import UIKit guard let self = self else { return } self.textField.isEnabled = self.isEnabled - self.textField.textColor = self.isEnabled ? self.enabledTextColor : self.enabledTextColor + self.textField.textColor = self.isEnabled ? self.enabledTextColor : self.disabledTextColor } } } @@ -115,7 +115,7 @@ import UIKit public override init(frame: CGRect) { super.init(frame: frame) - setupView() +// setupView() } public convenience init() { @@ -126,7 +126,7 @@ import UIKit public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) - setupView() +// setupView() setBothTextDelegates(bothDelegates) } @@ -158,10 +158,8 @@ import UIKit open override func updateView(_ size: CGFloat) { super.updateView(size) - MFStyler.styleTextField(textField) - - + layoutIfNeeded() } From 6e8a8a78cc1806094132776d198d86fbe1a93f37 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 1 Nov 2019 10:08:13 -0400 Subject: [PATCH 036/112] getting ready to try another refactor. --- MVMCoreUI.xcodeproj/project.pbxproj | 15 +++- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 78 ++++++++++++++----- .../Atoms/TextFields/DigitEntryField.swift | 22 +++--- .../Atoms/TextFields/DropdownEntryField.swift | 4 +- .../Atoms/TextFields/FormEntryField.swift | 2 +- .../Atoms/TextFields/MdnEntryField.swift | 7 +- .../Atoms/TextFields/TextEntryField.swift | 58 ++++++++------ .../ViewContainer/BoundaryView.swift | 13 ++++ 8 files changed, 135 insertions(+), 64 deletions(-) create mode 100644 MVMCoreUI/Containers/ViewContainer/BoundaryView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 96c5476a..92c110b2 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */; }; + 0A74EB4B236C72C400941B4B /* BoundaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A74EB4A236C72C400941B4B /* BoundaryView.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; @@ -231,6 +232,7 @@ 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownEntryField.swift; sourceTree = ""; }; + 0A74EB4A236C72C400941B4B /* BoundaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoundaryView.swift; sourceTree = ""; }; 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; @@ -437,6 +439,14 @@ path = FormUIHelpers; sourceTree = ""; }; + 0A74EB49236C728C00941B4B /* ViewContainer */ = { + isa = PBXGroup; + children = ( + 0A74EB4A236C72C400941B4B /* BoundaryView.swift */, + ); + path = ViewContainer; + sourceTree = ""; + }; D224798823142BF2003FCCF9 /* SwitchMolecules */ = { isa = PBXGroup; children = ( @@ -641,6 +651,7 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( + 0A74EB49236C728C00941B4B /* ViewContainer */, D29DF2B721E7BE79003B2FB9 /* TabBarController */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */, @@ -1071,7 +1082,6 @@ 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, - D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, @@ -1092,8 +1102,6 @@ D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */, 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */, - B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */, - D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, @@ -1123,6 +1131,7 @@ D29DF2BF21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.m in Sources */, D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */, + 0A74EB4B236C72C400941B4B /* BoundaryView.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, 01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 1f6860ee..a9bb85d0 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -13,20 +13,29 @@ import UIKit } @objcMembers open class DigitBox: UITextField, MVMCoreViewProtocol { - //-------------------------------------------------- - // MARK: - Outlets - //-------------------------------------------------- - - private weak var bottomBar: SeparatorView? - //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + public var bottomBar: CAShapeLayer = { + let layer = CAShapeLayer() + layer.backgroundColor = UIColor.black.cgColor + layer.drawsAsynchronously = true + layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); + return layer + }() + weak var textBoxDelegate: DigitBoxDelegate? private var previousSize: CGFloat = 0.0 + /// Determines if a border should be drawn. + private var hideBorder = false + private var showError = false + + private var borderStrokeColor: UIColor = .mfSilver() + private var borderPath: UIBezierPath = UIBezierPath() + //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -65,20 +74,50 @@ import UIKit textAlignment = .center keyboardType = .numberPad layer.borderWidth = 1 - hideError() + showError(false) - widthConstraint = widthAnchor.constraint(equalToConstant: 40) + widthConstraint = widthAnchor.constraint(equalToConstant: 39) widthConstraint?.isActive = true - heightConstraint = heightAnchor.constraint(equalToConstant: 60) + heightConstraint = heightAnchor.constraint(equalToConstant: 44) heightConstraint?.isActive = true - bottomBar = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot) - bottomBar?.setAsMedium() + layer.addSublayer(bottomBar) updateView(MVMCoreUISplitViewController.getDetailViewWidth()) } + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath.removeAllPoints() + + if !hideBorder { + // Brings the other half of the line inside the view to prevent cropping. + let origin = frame.origin + let size = frame.size + let insetLean: CGFloat = 0.5 + borderPath.lineWidth = 1 + + 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() + } + + layoutIfNeeded() + } + + open override func layoutSubviews() { + super.layoutSubviews() + + let barHeight: CGFloat = showError ? 4 : 1 + bottomBar.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -122,16 +161,15 @@ import UIKit } } - func setAsError() { + func showError(_ show: Bool) { - layer.borderColor = UIColor.mfPumpkin().cgColor - bottomBar?.backgroundColor = UIColor.mfPumpkin() - bottomBar?.height?.constant = 4 - } - - func hideError() { + showError = show + borderStrokeColor = show ? .mfPumpkin() : .mfSilver() + bottomBar.backgroundColor = show ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor + let barHeight: CGFloat = show ? 4 : 1 + bottomBar.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) - layer.borderColor = UIColor.mfSilver().cgColor - bottomBar?.setAsMedium() + setNeedsDisplay() + layoutIfNeeded() } } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 7d9e0edb..34c3afc5 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -14,7 +14,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - private weak var digitFieldsView: UIView? + public private(set) var digitFieldsView: UIView? //-------------------------------------------------- // MARK: - Properties @@ -132,7 +132,7 @@ import UIKit guard let self = self else { return } for field in self.digitFields ?? [] { - field.hideError() + field.showError(false) } } } @@ -179,6 +179,16 @@ import UIKit fatalError("DigitEntryField xib has not been implemented") } + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + + open func setup() { + + titleLabel.styleB2(true) + alignCenterHorizontal() + } + open override func setupFieldContainerContent(_ container: UIView) { setupTextFieldsView(forSize: CGFloat(numberOfDigits)) @@ -188,13 +198,6 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - open func setup() { - - titleLabel.styleB2(true) - title = "" - alignCenterHorizontal() - } - open override func updateView(_ size: CGFloat) { super.updateView(size) @@ -203,6 +206,7 @@ import UIKit self.titleLabel.updateView(size) + if let digitFields = self.digitFields, !digitFields.isEmpty { // Remove all current UI. diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 887ff1b3..2f1bcbaa 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -31,8 +31,6 @@ import UIKit //-------------------------------------------------- public weak var datePicker: UIDatePicker? - public var pickerView: UIPickerView? - //-------------------------------------------------- // MARK: - Properties @@ -72,7 +70,7 @@ import UIKit setupView() MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) - setBothTextDelegates(bothDelegates) + setBothTextDelegates(to: bothDelegates) } required public init?(coder: NSCoder) { diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 118740bb..ce1ffeba 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -55,7 +55,7 @@ import UIKit return layer }() - var delegateObject: MVMCoreUIDelegateObject? + weak var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index ae4253a1..06252a76 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -20,6 +20,8 @@ import MVMCore public var isNationalMdn = true public var shouldValidateMDN = false +// public var pickerView: UIPickerView? + public var mdn: String? { get { return MVMCoreUIUtility.removeMdnFormat(text) } set { text = MVMCoreUIUtility.formatMdn(newValue) } @@ -82,7 +84,7 @@ import MVMCore let isValid = hasValidMDN() if isValid { - clearError() + clearErrorState() } else { errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField) @@ -94,7 +96,7 @@ import MVMCore return true } - func getContacts(_ sender: Any?) { + @objc func getContacts(_ sender: Any?) { let picker = CNContactPickerViewController() picker.delegate = self @@ -125,7 +127,6 @@ import MVMCore } text = unformattedMDN - textFieldShouldReturn(textField) textFieldDidEndEditing(textField) } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 07f4e5be..46664042 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -1,5 +1,5 @@ // -// TextField.swift +// TextEntryField.swift // MVMCoreUI // // Created by Kevin Christiano on 10/2/19. @@ -24,7 +24,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - private(set) var textField: UITextField = { + public private(set) var textField: UITextField = { let textField = UITextField(frame: .zero) textField.translatesAutoresizingMaskIntoConstraints = false textField.setContentCompressionResistancePriority(.required, for: .vertical) @@ -39,10 +39,9 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - public var enabledTextColor: UIColor? - public var disabledTextColor: UIColor? + public var textColor: (enabled: UIColor?, disabled: UIColor?) - public var observingForChanges = false + public var observingForChange = false public override var isEnabled: Bool { didSet { @@ -50,7 +49,7 @@ import UIKit guard let self = self else { return } self.textField.isEnabled = self.isEnabled - self.textField.textColor = self.isEnabled ? self.enabledTextColor : self.disabledTextColor + self.textField.textColor = self.isEnabled ? self.textColor.enabled : self.textColor.disabled } } } @@ -64,6 +63,13 @@ import UIKit } } + public var placeholder: String? { + get { return textField.placeholder } + set { + textField.placeholder = newValue + } + } + public var validationBlock: ((_ value: String?) -> Bool)? { didSet { valueChanged() @@ -83,13 +89,13 @@ import UIKit /// 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 + if mfTextFieldDelegate != nil && !observingForChange { + observingForChange = 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 + } else if mfTextFieldDelegate == nil && observingForChange { + observingForChange = 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) @@ -115,7 +121,6 @@ import UIKit public override init(frame: CGRect) { super.init(frame: frame) -// setupView() } public convenience init() { @@ -125,9 +130,7 @@ import UIKit /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) - -// setupView() - setBothTextDelegates(bothDelegates) + setBothTextDelegates(to: bothDelegates) } required public init?(coder: NSCoder) { @@ -142,7 +145,6 @@ import UIKit open override func setupFieldContainerContent(_ container: UIView) { MFStyler.styleTextField(textField) - container.addSubview(textField) NSLayoutConstraint.activate([ @@ -159,25 +161,24 @@ import UIKit super.updateView(size) MFStyler.styleTextField(textField) - layoutIfNeeded() } deinit { - setBothTextDelegates(nil) + setBothTextDelegates(to: nil) } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - open func clearError() { + open func clearErrorState() { - feedback = nil textField.accessibilityValue = nil + updateUI(appearance: .original) } - public func setBothTextDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + public func setBothTextDelegates(to delegate: (UITextFieldDelegate & TextFieldDelegate)?) { mfTextFieldDelegate = delegate uiTextFieldDelegate = delegate @@ -215,7 +216,7 @@ import UIKit mfTextFieldDelegate?.isInvalid?(textfield: self) } else if !previousValidity && isValid { - clearError() + clearErrorState() if let mfTextFieldDelegate = mfTextFieldDelegate { mfTextFieldDelegate.isValid?(textfield: self) @@ -226,8 +227,7 @@ import UIKit @objc func endInputing() { if isValid { - clearError() - bottomBar.backgroundColor = UIColor.black.cgColor + clearErrorState() } else if let errMessage = errorMessage { feedback = errMessage @@ -254,11 +254,19 @@ extension TextEntryField { FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol) if let enabledTextColorHex = dictionary["enabledTextColor"] as? String { - enabledTextColor = UIColor.mfGet(forHex: enabledTextColorHex) + textColor.enabled = UIColor.mfGet(forHex: enabledTextColorHex) } if let disabledTextColorHex = dictionary["disabledTextColor"] as? String { - disabledTextColor = UIColor.mfGet(forHex: disabledTextColorHex) + textColor.disabled = UIColor.mfGet(forHex: disabledTextColorHex) + } + + if let text = dictionary[KeyText] as? String { + self.text = text + } + + if let placeholder = dictionary[placeholder] as? String { + self.placeholder = placeholder } switch dictionary.stringForkey(KeyType) { diff --git a/MVMCoreUI/Containers/ViewContainer/BoundaryView.swift b/MVMCoreUI/Containers/ViewContainer/BoundaryView.swift new file mode 100644 index 00000000..0256ddd3 --- /dev/null +++ b/MVMCoreUI/Containers/ViewContainer/BoundaryView.swift @@ -0,0 +1,13 @@ +// +// BoundaryView.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/1/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class BoundaryView: UIView { + +} From 5e96653d4baf3ebf978cb2a96a64413ee9335ecc Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 1 Nov 2019 10:17:20 -0400 Subject: [PATCH 037/112] undo. --- MVMCoreUI.xcodeproj/project.pbxproj | 12 ------------ .../Containers/ViewContainer/BoundaryView.swift | 13 ------------- 2 files changed, 25 deletions(-) delete mode 100644 MVMCoreUI/Containers/ViewContainer/BoundaryView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 92c110b2..90a8421f 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -43,7 +43,6 @@ 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */; }; - 0A74EB4B236C72C400941B4B /* BoundaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A74EB4A236C72C400941B4B /* BoundaryView.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; @@ -232,7 +231,6 @@ 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownEntryField.swift; sourceTree = ""; }; - 0A74EB4A236C72C400941B4B /* BoundaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoundaryView.swift; sourceTree = ""; }; 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; @@ -439,14 +437,6 @@ path = FormUIHelpers; sourceTree = ""; }; - 0A74EB49236C728C00941B4B /* ViewContainer */ = { - isa = PBXGroup; - children = ( - 0A74EB4A236C72C400941B4B /* BoundaryView.swift */, - ); - path = ViewContainer; - sourceTree = ""; - }; D224798823142BF2003FCCF9 /* SwitchMolecules */ = { isa = PBXGroup; children = ( @@ -651,7 +641,6 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( - 0A74EB49236C728C00941B4B /* ViewContainer */, D29DF2B721E7BE79003B2FB9 /* TabBarController */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */, @@ -1131,7 +1120,6 @@ D29DF2BF21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.m in Sources */, D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */, - 0A74EB4B236C72C400941B4B /* BoundaryView.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, 01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, diff --git a/MVMCoreUI/Containers/ViewContainer/BoundaryView.swift b/MVMCoreUI/Containers/ViewContainer/BoundaryView.swift deleted file mode 100644 index 0256ddd3..00000000 --- a/MVMCoreUI/Containers/ViewContainer/BoundaryView.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// BoundaryView.swift -// MVMCoreUI -// -// Created by Kevin Christiano on 11/1/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -class BoundaryView: UIView { - -} From 475d08296b3ec1d5e55a89273cd28d450ef80d48 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 1 Nov 2019 16:15:19 -0400 Subject: [PATCH 038/112] Reworked bezier logic for CaretView. It was not optimal. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 25 +- .../Atoms/TextFields/DigitEntryField.swift | 266 ++++++++---------- .../Atoms/TextFields/DropdownEntryField.swift | 34 +-- .../Atoms/TextFields/FormEntryField.swift | 65 ++--- .../Atoms/TextFields/MdnEntryField.swift | 28 +- .../Atoms/TextFields/TextEntryField.swift | 35 ++- MVMCoreUI/Atoms/Views/CaretView.swift | 130 +++++---- MVMCoreUI/Atoms/Views/DashLine.swift | 2 +- 8 files changed, 300 insertions(+), 285 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index a9bb85d0..f4edd1a1 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -25,8 +25,6 @@ import UIKit return layer }() - weak var textBoxDelegate: DigitBoxDelegate? - private var previousSize: CGFloat = 0.0 /// Determines if a border should be drawn. @@ -36,6 +34,12 @@ import UIKit private var borderStrokeColor: UIColor = .mfSilver() private var borderPath: UIBezierPath = UIBezierPath() + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + weak var textBoxDelegate: DigitBoxDelegate? + //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -47,11 +51,6 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("DigitBox has not been implemented") - } - public override init(frame: CGRect) { super.init(frame: frame) setup() @@ -61,11 +60,16 @@ import UIKit self.init(frame: .zero) } + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("DigitBox has not been implemented") + } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- - func setup() { + private func setup() { guard constraints.isEmpty else { return } @@ -74,7 +78,7 @@ import UIKit textAlignment = .center keyboardType = .numberPad layer.borderWidth = 1 - showError(false) + showErrorState(false) widthConstraint = widthAnchor.constraint(equalToConstant: 39) widthConstraint?.isActive = true @@ -83,7 +87,6 @@ import UIKit heightConstraint?.isActive = true layer.addSublayer(bottomBar) - updateView(MVMCoreUISplitViewController.getDetailViewWidth()) } @@ -161,7 +164,7 @@ import UIKit } } - func showError(_ show: Bool) { + func showErrorState(_ show: Bool) { showError = show borderStrokeColor = show ? .mfPumpkin() : .mfSilver() diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 34c3afc5..86fbae9a 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -8,7 +8,10 @@ import UIKit - +/** + * Subclass of TextEntryField due to the conveniences provided. + * TODO: Compare overrides to determine if TextEntryField is necessary. + */ @objcMembers open class DigitEntryField: TextEntryField, DigitBoxDelegate { //-------------------------------------------------- // MARK: - Outlets @@ -20,69 +23,65 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - private var numberOfDigits = 0 + private var numberOfDigits = 4 private var switchedAutomatically = false - public var digitFields: [DigitBox]? + public var digitFields: [DigitBox] = [] public override var isEnabled: Bool { didSet { if isEnabled { titleLabel.styleB2(true) } else { - titleLabel.textColor = UIColor.mfBattleshipGrey() + titleLabel.textColor = .mfBattleshipGrey() } - for textField in digitFields ?? [] { + for textField in digitFields { textField.isUserInteractionEnabled = isEnabled textField.isEnabled = isEnabled - textField.textColor = isEnabled ? .black : UIColor.mfBattleshipGrey() + textField.textColor = isEnabled ? .black : .mfBattleshipGrey() } } } /// Sets placeholder text in the textField. - public override var feedback: String? { + public override var placeholder: String? { get { var string = "" - for digitField in digitFields ?? [] { - if let digitext = digitField.attributedPlaceholder?.string { - string += digitext - } - } - + digitFields.forEach { string += $0.attributedPlaceholder?.string ?? "" } + return !string.isEmpty ? string : nil } set { - guard let fieldValue = newValue, let fields = digitFields else { return } + guard let newValue = newValue else { return } - for (index, field) in fields.enumerated() { - if index < fieldValue.count { - let stringForIndex = (newValue as NSString?)?.substring(with: NSRange(location: index, length: 1)) - field.attributedPlaceholder = NSAttributedString(string: stringForIndex ?? "", attributes: [ + for (index, field) in digitFields.enumerated() { + if index < newValue.count { + let indexChar = newValue.index(newValue.startIndex, offsetBy: index) + field.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [ NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) } } - // - // if feedback.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")) - // - // super.showErrorMessage(errorMessage) - // - // if self.showErrorMessage { - // self.labelToTextFieldPin?.constant = 10 - // } - // for field in self.digitFields ?? [] { - // field.setAsError() - // } + // If there is already text in the textfield, set the place holder label below. + if let text = text, !text.isEmpty && !showError { + feedback = placeholder + } else if !showError { + feedback = "" + } + + if let feedback = feedback, !feedback.isEmpty { + feedbackContainerDistance?.constant = 10 + } else { + feedbackContainerDistance?.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") ?? "") } } @@ -90,21 +89,17 @@ import UIKit get { var string = "" - for digitField in digitFields ?? [] { - if let digitText = digitField.text { - string += digitText - } - } - + digitFields.forEach { string += $0.text ?? "" } + return string } set { - guard let fields = self.digitFields else { return } + guard let newValue = newValue else { return } - for (index, field) in fields.enumerated() { - if index < (text?.count ?? 0) { - let stringForIndex = (text as NSString?)?.substring(with: NSRange(location: index, length: 1)) - field.text = stringForIndex + for (index, field) in digitFields.enumerated() { + if index < newValue.count { + let indexChar = newValue.index(newValue.startIndex, offsetBy: index) + field.text = String(newValue[indexChar]) } } @@ -116,9 +111,9 @@ import UIKit get { return titleLabel.text } set { if let formText = newValue, !formText.isEmpty { - messageToTextFieldPin?.constant = 10 + titleContainerDistance?.constant = 10 } else { - messageToTextFieldPin?.constant = 0 + titleContainerDistance?.constant = 0 } super.title = newValue @@ -131,21 +126,17 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - for field in self.digitFields ?? [] { - field.showError(false) + if self.showError { + self.feedbackContainerDistance?.constant = 10 + self.setNeedsLayout() } + + self.digitFields.forEach { $0.showErrorState(true) } } } } } - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - private weak var messageToTextFieldPin: NSLayoutConstraint? - private weak var labelToTextFieldPin: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -185,6 +176,7 @@ import UIKit open func setup() { + hideBorder = true titleLabel.styleB2(true) alignCenterHorizontal() } @@ -206,16 +198,11 @@ import UIKit self.titleLabel.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) - } + if !self.digitFields.isEmpty { + + StackableViewController.remove(self.digitFields) + + self.digitFields.forEach { $0.updateView(size) } } // Layout text boxes. @@ -227,6 +214,14 @@ import UIKit // MARK: - Methods //-------------------------------------------------- + func removeError() { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.digitFields.forEach { $0.showErrorState(false) } + } + } + func createDigitField() -> DigitBox { let textField = DigitBox() @@ -239,22 +234,20 @@ import UIKit func buildTextFieldsView(size: CGFloat) { // Remove all current UI. - if let digitFields = digitFields, !digitFields.isEmpty { + if !digitFields.isEmpty { StackableViewController.remove(digitFields) } if numberOfDigits > 0 { let digitFields = [DigitBox](repeating: createDigitField(), count: numberOfDigits) - for digitField in digitFields { - digitField.updateView(size) - } + digitFields.forEach { $0.updateView(size) } self.digitFields = digitFields setupTextFieldsView(forSize: size) } else { - digitFields = nil + digitFields = [] } } @@ -265,10 +258,10 @@ import UIKit guard let self = self else { return } if let feedback = self.feedback, !feedback.isEmpty { - self.labelToTextFieldPin?.constant = 10 + self.feedbackContainerDistance?.constant = 10 } else { - self.labelToTextFieldPin?.constant = 0 + self.feedbackContainerDistance?.constant = 0 } } } @@ -276,15 +269,13 @@ import UIKit func setAsSecureTextEntry(_ secureEntry: Bool) { DispatchQueue.main.async { [weak self] in - guard let self = self, - let fields = self.digitFields - else { return } + guard let self = self else { return } - for (index, field) in fields.enumerated() { + for (index, field) in self.digitFields.enumerated() { field.isSecureTextEntry = secureEntry // Accessibility - 33704 fix voice over will read what pin user is filling - field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(fields.count)) + field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(self.digitFields.count)) } } } @@ -292,24 +283,21 @@ import UIKit func setupTextFieldsView(forSize size: CGFloat) { guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize(), - let digitFieldsView = digitFieldsView, - let digitFields = digitFields + let digitFieldsView = digitFieldsView 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 { + if self.digitFields.count == 1 { inset.left = 0 inset.right = 0 - } else if let field = object as? UITextField, field == digitFields.first { + } else if let field = object as? UITextField, field == self.digitFields.first { inset.left = 0 - } else if let field = object as? UITextField, field == digitFields.last { + } else if let field = object as? UITextField, field == self.digitFields.last { inset.right = 0 } @@ -319,57 +307,18 @@ import UIKit public override func defaultValidationBlock() { - weak var weakSelf = self - validationBlock = { enteredValue in - - if (enteredValue?.count ?? 0) > 0 && (enteredValue?.count ?? 0) == weakSelf?.digitFields?.count { - return true - } - - return false + guard let enteredValue = enteredValue else { return false } + + return enteredValue.count > 0 && enteredValue.count == self.digitFields.count } } - //-------------------------------------------------- - // 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 - } - - if !dictionary.isEmpty{ - for textField in digitFields ?? [] { - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegateObject as? UITextFieldDelegate) - } - } - - 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: - Helpers - //-------------------------------------------------- - func selectPreviousTextField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false - guard let fields = digitFields else { return } - - for field in fields { + for field in digitFields { if field == currentTextField { selectNextField = true @@ -390,18 +339,16 @@ import UIKit var selectNextField = false - guard let fields = digitFields else { return } - - for field in fields{ + for field in digitFields { if field == currentTextField { selectNextField = true } else if selectNextField { if !clear { - self.switchedAutomatically = true + switchedAutomatically = true } field.becomeFirstResponder() - self.switchedAutomatically = false + switchedAutomatically = false UIAccessibility.post(notification: .layoutChanged, argument: field) } @@ -409,20 +356,46 @@ import UIKit } //-------------------------------------------------- - // MARK: - Accessinility + // MARK: - Molecule //-------------------------------------------------- - open override class func accessibilityElements() -> [Any]? { - // self.digit - // if let digitFields = self.digitFields { - // return [digitFields] + [textField] - // } - // - return [textField] + 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 + } + + if !dictionary.isEmpty{ + for textField in digitFields { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegateObject as? UITextFieldDelegate) + } + } + + 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: - TextFieldDelegate + // MARK: - Accessibility + //-------------------------------------------------- + + open override class func accessibilityElements() -> [Any]? { + // let fields = [] + +// return self.digitFields + return nil + } + + //-------------------------------------------------- + // MARK: - Text Field Delegate //-------------------------------------------------- @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -495,7 +468,10 @@ import UIKit return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true } + //-------------------------------------------------- // MARK: - Passed Along TextField delegate + //-------------------------------------------------- + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { return uiTextFieldDelegate?.textFieldShouldBeginEditing?(textField) ?? true diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 2f1bcbaa..17627b69 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -14,23 +14,19 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - let dropDownCaretLabel: Label = { - let label = Label() - label.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) - label.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) - label.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) - label.isHidden = true - label.isUserInteractionEnabled = true - return label + let dropDownCaretLabel: CaretView = { + let caret = CaretView() + caret.isHidden = true + caret.isUserInteractionEnabled = true + return caret }() - private var calendar: Calendar? - //-------------------------------------------------- // MARK: - Accessories //-------------------------------------------------- public weak var datePicker: UIDatePicker? + private var calendar: Calendar? //-------------------------------------------------- // MARK: - Properties @@ -39,9 +35,7 @@ import UIKit public var dropDownIsDisplayed = false public override var isEnabled: Bool { - didSet { - showDropDown(isEnabled) - } + didSet { showDropDown(isEnabled) } } //-------------------------------------------------- @@ -56,10 +50,9 @@ import UIKit public override init(frame: CGRect) { super.init(frame: frame) - setupView() + setup() } - /// Basic initializer. public convenience init() { self.init(frame: .zero) } @@ -68,7 +61,7 @@ import UIKit public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) - setupView() + setup() MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) setBothTextDelegates(to: bothDelegates) } @@ -78,6 +71,13 @@ import UIKit fatalError("DropdownEntryField does not support xib.") } + private func setup() { + + dropDownCaretLabel.heightAnchor.constraint(equalToConstant: 40).isActive = true + dropDownCaretWidth = widthAnchor.constraint(equalToConstant: 40) + dropDownCaretWidth?.isActive = true + } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -116,7 +116,7 @@ import UIKit override func startEditing() { super.startEditing() - showDropDown(!showErrorMessage) + showDropDown(!showError) } class func getEnabledTextfields(_ textFieldToDetermine: [TextEntryField]?) -> [AnyHashable]? { diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index ce1ffeba..40157483 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -43,6 +43,22 @@ import UIKit return view }() + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + weak var delegateObject: MVMCoreUIDelegateObject? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var isValid = false + public var fieldKey: String? + + /// Determines if a border should be drawn. + var hideBorder = false + private var borderStrokeColor: UIColor = .mfSilver() private var borderPath: UIBezierPath = UIBezierPath() @@ -55,21 +71,9 @@ import UIKit return layer }() - weak var delegateObject: MVMCoreUIDelegateObject? - - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - public var isValid = false - public var fieldKey: String? - - /// Determines if a border should be drawn. - private var hideBorder = false - public private(set) var appearance: Appearance = .original - public var showErrorMessage = false + public var showError = false public var errorMessage: String? @@ -133,6 +137,9 @@ import UIKit public var titleLabelLeading: NSLayoutConstraint? public var titleLabelTrailing: NSLayoutConstraint? + public var titleContainerDistance: NSLayoutConstraint? + public var feedbackContainerDistance: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -150,7 +157,7 @@ import UIKit public init(title: String) { super.init(frame: .zero) setupView() - self.titleLabel.text = title + titleLabel.text = title } required public init?(coder: NSCoder) { @@ -182,7 +189,8 @@ import UIKit addSubview(fieldContainer) setupFieldContainerContent(fieldContainer) - fieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4).isActive = true + titleContainerDistance = fieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) + titleContainerDistance?.isActive = true fieldContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) fieldContainerLeading?.isActive = true fieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) @@ -190,7 +198,8 @@ import UIKit addSubview(feedbackLabel) - feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne).isActive = true + feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne) + feedbackContainerDistance?.isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) @@ -203,10 +212,8 @@ import UIKit layoutIfNeeded() } - /** - Method to override. - Intended to add the interactive content (i.e. textField) to the fieldContainer. - */ + /// Method to override. + /// Intended to add the interactive content (i.e. textField) to the fieldContainer. open func setupFieldContainerContent(_ container: UIView) { // To be overridden by subclass. } @@ -237,7 +244,7 @@ import UIKit open func refreshBorderUI(bottomBarSize: CGFloat? = nil) { - let size = CGFloat(appearance == .error ? 4 : 1) + let size: CGFloat = appearance == .error ? 4 : 1 bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) @@ -274,11 +281,6 @@ import UIKit layoutIfNeeded() } - func resizeBottomBar(size: CGFloat) { - - bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) - } - //-------------------------------------------------- // MARK: - Constraint Methods //-------------------------------------------------- @@ -313,37 +315,30 @@ import UIKit self.appearance = appearance isUserInteractionEnabled = true + titleLabel.textColor = .mfBattleshipGrey() hideBorder = false + feedback = showError ? errorMessage : nil switch appearance { case .original: borderStrokeColor = .mfSilver() - feedback = nil bottomBar.backgroundColor = UIColor.black.cgColor - titleLabel.textColor = .mfBattleshipGrey() case .error: borderStrokeColor = .mfPumpkin() - titleLabel.textColor = UIColor.mfBattleshipGrey() bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor - feedback = showErrorMessage ? errorMessage : nil case .lock: isUserInteractionEnabled = false hideBorder = true - feedback = nil - titleLabel.textColor = UIColor.mfBattleshipGrey() bottomBar.backgroundColor = UIColor.clear.cgColor case .select: borderStrokeColor = .black - feedback = nil - titleLabel.textColor = UIColor.mfBattleshipGrey() bottomBar.backgroundColor = UIColor.black.cgColor case .disable: isUserInteractionEnabled = false - feedback = nil borderStrokeColor = .mfSilver() titleLabel.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 06252a76..f1b769d5 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -20,8 +20,6 @@ import MVMCore public var isNationalMdn = true public var shouldValidateMDN = false -// public var pickerView: UIPickerView? - public var mdn: String? { get { return MVMCoreUIUtility.removeMdnFormat(text) } set { text = MVMCoreUIUtility.formatMdn(newValue) } @@ -40,6 +38,13 @@ import MVMCore self.init(frame: .zero) } + /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. + public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + super.init(frame: .zero) + setup() + setBothTextDelegates(to: bothDelegates) + } + required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("MdnEntryField xib not supported.") @@ -65,7 +70,7 @@ import MVMCore // MARK: - Methods //-------------------------------------------------- - func hasValidMDN() -> Bool { + public func hasValidMDN() -> Bool { guard let MDN = mdn, !MDN.isEmpty @@ -78,7 +83,7 @@ import MVMCore return MVMCoreUIUtility.validateInternationalMDNString(MDN) } - func validateAndColor() -> Bool { + public func validateAndColor() -> Bool { if !shouldValidateMDN { let isValid = hasValidMDN() @@ -107,7 +112,7 @@ import MVMCore } //-------------------------------------------------- - // MARK: - ContactPicker Delegate + // MARK: - Contact Picker Delegate //-------------------------------------------------- public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { @@ -127,7 +132,7 @@ import MVMCore } text = unformattedMDN - textFieldShouldReturn(textField) + _ = textFieldShouldReturn(textField) textFieldDidEndEditing(textField) } } @@ -136,15 +141,14 @@ import MVMCore // MARK: - Implemented TextField Delegate //-------------------------------------------------- - @discardableResult - @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return uiTextFieldDelegate?.textFieldShouldReturn?(textField) ?? true } - @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false @@ -172,17 +176,17 @@ import MVMCore // MARK: - Passed Along TextField delegate //-------------------------------------------------- - @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { return uiTextFieldDelegate?.textFieldShouldBeginEditing?(textField) ?? true } - @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { return uiTextFieldDelegate?.textFieldShouldEndEditing?(textField) ?? true } - @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + public func textFieldShouldClear(_ textField: UITextField) -> Bool { return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 46664042..53807ec5 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -65,15 +65,11 @@ import UIKit public var placeholder: String? { get { return textField.placeholder } - set { - textField.placeholder = newValue - } + set { textField.placeholder = newValue } } public var validationBlock: ((_ value: String?) -> Bool)? { - didSet { - valueChanged() - } + didSet { valueChanged() } } public override var errorMessage: String? { @@ -174,6 +170,21 @@ import UIKit open func clearErrorState() { + /* + + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + self.separatorHeightConstraint.constant = 1; + self.separatorView.backgroundColor = [UIColor blackColor]; + [self layoutIfNeeded]; + self.errorShowing = NO; + self.label.textColor = [UIColor blackColor]; + self.label.text = @""; + self.textField.accessibilityValue = nil; + [self setNeedsDisplay]; + [self layoutIfNeeded]; + }]; + */ + textField.accessibilityValue = nil updateUI(appearance: .original) } @@ -202,7 +213,7 @@ import UIKit @objc func valueChanged() { - if !showErrorMessage { + if !showError { feedback = "" } @@ -217,10 +228,7 @@ import UIKit } else if !previousValidity && isValid { clearErrorState() - - if let mfTextFieldDelegate = mfTextFieldDelegate { - mfTextFieldDelegate.isValid?(textfield: self) - } + mfTextFieldDelegate?.isValid?(textfield: self) } } @@ -228,6 +236,7 @@ import UIKit if isValid { clearErrorState() + bottomBar.backgroundColor = UIColor.black.cgColor } else if let errMessage = errorMessage { feedback = errMessage @@ -236,6 +245,10 @@ import UIKit @objc func startEditing() { + if appearance != .original { + updateUI(appearance: .original) + } + textField.becomeFirstResponder() } } diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index ec4457c2..4f1f80eb 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -2,52 +2,53 @@ // CaretView.swift // MobileFirstFramework // -// Created by Kolli, Praneeth on 1/5/18. -// Converted by Christiano, Kevin on 1/5/18. +// Created by Christiano, Kevin on 1/5/18. // Copyright © 2018 Verizon Wireless. All rights reserved. // -open class CaretView: MFView { +@objcMembers open class CaretView: MFView { //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ // Objc can't use float enum. - @objc public static let thin: CGFloat = 6.0 - @objc public static let standard: CGFloat = 2.6 - @objc public static let thick: CGFloat = 1.5 + public static let thin: CGFloat = 6.0 + public static let standard: CGFloat = 2.5 + public static let thick: CGFloat = 1.5 + + public var strokeColor: UIColor = .black + public var lineWidth: CGFloat = 1 + public var direction: Direction = .right + + private var caretPath: UIBezierPath = UIBezierPath() - private(set) var strokeColor: UIColor? - private var lineWidth: CGFloat? - private var lineThickness: CGFloat? - //------------------------------------------------------ // MARK: - Initialization //------------------------------------------------------ - @objc public init() { - super.init(frame: .zero) - } - @objc public override init(frame: CGRect) { super.init(frame: frame) } - @objc required public init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) + @objc public init() { + super.init(frame: .zero) } - /// Can init with a specific line width. @objc public init(lineWidth: CGFloat) { - super.init(frame: CGRect()) + super.init(frame: .zero) self.lineWidth = lineWidth } /// Can init with a specific line thickness, scales based on width and height. @objc public init(lineThickness: CGFloat) { - super.init(frame: CGRect()) - self.lineThickness = lineThickness + super.init(frame: .zero) + // self.lineThickness = lineThickness + } + + @objc required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("CaretView xib not supported.") } @objc override open func setupView() { @@ -55,9 +56,60 @@ open class CaretView: MFView { } //------------------------------------------------------ - // MARK: - Private Function + // MARK: - Drawing //------------------------------------------------------ + /// The direction the caret will be pointing to. + public enum Direction: String { + case left + case right + case down + case up + } + + @objc override open func draw(_ rect: CGRect) { + super.draw(rect) + + caretPath.removeAllPoints() + caretPath.lineJoinStyle = .miter + caretPath.lineWidth = lineWidth + let inset = lineWidth / 2 + + switch direction { + case .up: + caretPath.move(to: CGPoint(x: inset, y: frame.size.height - inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width / 2, y: inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height)) + + case .right: + caretPath.move(to: CGPoint(x: inset, y: inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: frame.size.height / 2)) + caretPath.addLine(to: CGPoint(x: inset, y: frame.size.height - inset)) + + case .down: + caretPath.move(to: CGPoint(x: inset, y: inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width / 2, y: frame.size.height - inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: inset)) + + case .left: + caretPath.move(to: CGPoint(x: frame.size.width - inset, y: inset)) + caretPath.addLine(to: CGPoint(x: inset, y: frame.size.height / 2)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: frame.size.height - inset)) + } + + strokeColor.setStroke() + caretPath.stroke() + } + + //------------------------------------------------------ + // MARK: - Methods + //------------------------------------------------------ + + @objc public func setLineColor(_ color: UIColor) { + strokeColor = color + setNeedsDisplay() + } + private func defaultState() { isOpaque = false isHidden = false @@ -65,34 +117,6 @@ open class CaretView: MFView { strokeColor = .black } - //------------------------------------------------------ - // MARK: - Drawing - //------------------------------------------------------ - - @objc override open func draw(_ rect: CGRect) { - // Drawing Caret - let context = UIGraphicsGetCurrentContext() - context?.clear(rect) - - let lineWidthToDraw: CGFloat = lineWidth ?? frame.size.width / (lineThickness ?? 2.6) - - let path = UIBezierPath() - path.move(to: CGPoint(x: lineWidthToDraw / 2.0, y: 0.0)) - path.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height / 2.0)) - path.addLine(to: CGPoint(x: lineWidthToDraw / 2.0, y: frame.size.height)) - path.addLine(to: CGPoint(x: 0.0, y: frame.size.height - lineWidthToDraw / 2.0)) - path.addLine(to: CGPoint(x: frame.size.width - lineWidthToDraw, y: frame.size.height / 2.0)) - path.addLine(to: CGPoint(x: 0.0, y: lineWidthToDraw / 2.0)) - path.addLine(to: CGPoint(x: lineWidthToDraw / 2.0, y: 0.0)) - strokeColor?.setFill() - path.fill() - path.close() - } - - @objc public func setLineColor(_ color: UIColor?) { - strokeColor = color - setNeedsDisplay() - } //------------------------------------------------------ // MARK: - Atomization @@ -102,10 +126,10 @@ open class CaretView: MFView { @objc open override func setAsMolecule() { defaultState() } - + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - // Configure class properties with JSON values + guard let dictionary = json else { return } if let strokeColorHex = dictionary["strokeColor"] as? String { @@ -115,7 +139,7 @@ open class CaretView: MFView { if let isHiddenValue = dictionary[KeyIsHidden] as? Bool { isHidden = isHiddenValue } - + if let isOpaqueValue = dictionary[KeyIsOpaque] as? Bool { isOpaque = isOpaqueValue } @@ -130,6 +154,6 @@ open class CaretView: MFView { } open override func alignment() -> UIStackView.Alignment { - return UIStackView.Alignment.leading; + return .leading } } diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 9166ddfd..2bc11b98 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -31,7 +31,7 @@ open class DashLine: MFView { required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("DashLine xib not supported") +// fatalError("DashLine xib not supported") } //------------------------------------------------------ From bb3277e1ba30f8b10742d82c96c58a0603e2137f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 4 Nov 2019 11:39:32 -0500 Subject: [PATCH 039/112] latest and greatest. --- MVMCoreUI/Atoms/Buttons/CaretButton.swift | 15 +-- .../Atoms/TextFields/DropdownEntryField.swift | 12 ++- .../Atoms/TextFields/FormEntryField.swift | 1 - .../Atoms/TextFields/TextEntryField.swift | 4 +- MVMCoreUI/Atoms/Views/CaretView.swift | 100 +++++++++++++----- MVMCoreUI/Atoms/Views/DashLine.swift | 7 +- MVMCoreUI/Molecules/Items/TableViewCell.swift | 16 +-- 7 files changed, 106 insertions(+), 49 deletions(-) diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index 8f40568c..1bee754f 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -9,7 +9,6 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { - //------------------------------------------------------ // MARK: - Constants //------------------------------------------------------ @@ -54,15 +53,16 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI public func updateView(_ size: CGFloat) { } //------------------------------------------------------ - // MARK: - Functions + // MARK: - Methods //------------------------------------------------------ private func changeCaretColor() { + setTitleColor(enabledColor, for: .normal) setTitleColor(disabledColor, for: .disabled) if let rightCaretView = rightView as? CaretView { - rightCaretView.setLineColor(isEnabled ? enabledColor : disabledColor) + rightCaretView.isEnabled = isEnabled } } @@ -81,8 +81,8 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI rightView?.translatesAutoresizingMaskIntoConstraints = false addSubview(caretView) - NSLayoutConstraint(item: caretView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: width).isActive = true - NSLayoutConstraint(item: caretView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: height).isActive = true + caretView.widthAnchor.constraint(equalToConstant: width).isActive = true + caretView.heightAnchor.constraint(equalToConstant: height).isActive = true let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 4.0) caretLabelSpacing.isActive = true @@ -91,6 +91,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI NSLayoutConstraint(item: caretView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true NSLayoutConstraint(item: self, attribute: .right, relatedBy: .greaterThanOrEqual, toItem: caretView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true contentHorizontalAlignment = .left + //set correct color after layout changeCaretColor() } @@ -135,10 +136,10 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI } open func alignment() -> UIStackView.Alignment { - return UIStackView.Alignment.leading; + return .leading } - public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 10 } } diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 17627b69..a553913c 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -71,6 +71,10 @@ import UIKit fatalError("DropdownEntryField does not support xib.") } + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + private func setup() { dropDownCaretLabel.heightAnchor.constraint(equalToConstant: 40).isActive = true @@ -84,13 +88,15 @@ import UIKit open override func setupFieldContainerContent(_ container: UIView) { - let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) - dropDownCaretLabel.addGestureRecognizer(tapOnCarrot) + let tapOnCaret = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCaretLabel.addGestureRecognizer(tapOnCaret) container.addSubview(dropDownCaretLabel) + textFieldTrailingConstraint?.isActive = false dropDownCaretLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true - dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6).isActive = true + textFieldTrailingConstraint = dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) + textFieldTrailingConstraint?.isActive = true container.trailingAnchor.constraint(equalTo: dropDownCaretLabel.trailingAnchor, constant: 16).isActive = true container.bottomAnchor.constraint(equalTo: dropDownCaretLabel.bottomAnchor).isActive = true dropDownCaretWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 40157483..b0638253 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -67,7 +67,6 @@ import UIKit layer.backgroundColor = UIColor.black.cgColor layer.drawsAsynchronously = true layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); - return layer }() diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 53807ec5..9b0bfbb2 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -145,9 +145,9 @@ import UIKit NSLayoutConstraint.activate([ textField.heightAnchor.constraint(equalToConstant: 24), - textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 10), + textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 12), textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), - container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)]) + container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 12)]) textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16) textFieldTrailingConstraint?.isActive = true diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 4f1f80eb..519965a1 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -12,17 +12,51 @@ // MARK: - Properties //------------------------------------------------------ - // Objc can't use float enum. - public static let thin: CGFloat = 6.0 - public static let standard: CGFloat = 2.5 - public static let thick: CGFloat = 1.5 - public var strokeColor: UIColor = .black public var lineWidth: CGFloat = 1 public var direction: Direction = .right + public var size: CaretSize? private var caretPath: UIBezierPath = UIBezierPath() + public var enabledColor: UIColor = .black + public var disabledColor: UIColor = .mfSilver() + + public var isEnabled: Bool = true { + didSet { + strokeColor = isEnabled ? enabledColor : disabledColor + setNeedsDisplay() + } + } + + //------------------------------------------------------ + // MARK: - Constraints + //------------------------------------------------------ + + public enum CaretSize: String { + case small + case medium + case large + + // Dimensions of container; provided by InVision. + func dimensions() -> CGSize { + + switch self { + case .small: + return CGSize(width: 6, height: 10) + + case .medium: + return CGSize(width: 9, height: 16) + + case .large: + return CGSize(width: 14, height: 24) + } + } + } + + public var heightConstraint: NSLayoutConstraint? + public var widthConstraint: NSLayoutConstraint? + //------------------------------------------------------ // MARK: - Initialization //------------------------------------------------------ @@ -31,28 +65,31 @@ super.init(frame: frame) } - @objc public init() { - super.init(frame: .zero) + @objc public convenience init() { + self.init(frame: .zero) } - @objc public init(lineWidth: CGFloat) { - super.init(frame: .zero) + @objc public convenience init(lineWidth: CGFloat) { + self.init(frame: .zero) self.lineWidth = lineWidth } - /// Can init with a specific line thickness, scales based on width and height. - @objc public init(lineThickness: CGFloat) { - super.init(frame: .zero) - // self.lineThickness = lineThickness - } - @objc required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) fatalError("CaretView xib not supported.") } + //------------------------------------------------------ + // MARK: - Setup + //------------------------------------------------------ + @objc override open func setupView() { + defaultState() + + // Establishing references. + heightConstraint = heightAnchor.constraint(equalToConstant: 0) + widthConstraint = widthAnchor.constraint(equalToConstant: 0) } //------------------------------------------------------ @@ -60,7 +97,7 @@ //------------------------------------------------------ /// The direction the caret will be pointing to. - public enum Direction: String { + @objc public enum Direction: Int { case left case right case down @@ -73,27 +110,30 @@ caretPath.removeAllPoints() caretPath.lineJoinStyle = .miter caretPath.lineWidth = lineWidth + let inset = lineWidth / 2 + let halfWidth = frame.size.width / 2 + let halfHeight = frame.size.height / 2 switch direction { case .up: caretPath.move(to: CGPoint(x: inset, y: frame.size.height - inset)) - caretPath.addLine(to: CGPoint(x: frame.size.width / 2, y: inset)) + caretPath.addLine(to: CGPoint(x: halfWidth, y: lineWidth)) caretPath.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height)) case .right: caretPath.move(to: CGPoint(x: inset, y: inset)) - caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: frame.size.height / 2)) + caretPath.addLine(to: CGPoint(x: frame.size.width - lineWidth, y: halfHeight)) caretPath.addLine(to: CGPoint(x: inset, y: frame.size.height - inset)) case .down: caretPath.move(to: CGPoint(x: inset, y: inset)) - caretPath.addLine(to: CGPoint(x: frame.size.width / 2, y: frame.size.height - inset)) + caretPath.addLine(to: CGPoint(x: halfWidth, y: frame.size.height - lineWidth)) caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: inset)) case .left: caretPath.move(to: CGPoint(x: frame.size.width - inset, y: inset)) - caretPath.addLine(to: CGPoint(x: inset, y: frame.size.height / 2)) + caretPath.addLine(to: CGPoint(x: lineWidth, y: halfHeight)) caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: frame.size.height - inset)) } @@ -106,17 +146,29 @@ //------------------------------------------------------ @objc public func setLineColor(_ color: UIColor) { + strokeColor = color setNeedsDisplay() } - private func defaultState() { + @objc public func defaultState() { + + translatesAutoresizingMaskIntoConstraints = false isOpaque = false isHidden = false backgroundColor = .clear strokeColor = .black } + @objc public func setConstraints() { + + guard let dimensions = size?.dimensions() else { return } + + heightConstraint?.constant = dimensions.height + heightConstraint?.isActive = true + widthConstraint?.constant = dimensions.width + widthConstraint?.isActive = true + } //------------------------------------------------------ // MARK: - Atomization @@ -127,7 +179,7 @@ defaultState() } - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json else { return } @@ -149,11 +201,11 @@ } } - open override func needsToBeConstrained() -> Bool { + @objc open override func needsToBeConstrained() -> Bool { return true } - open override func alignment() -> UIStackView.Alignment { + @objc open override func alignment() -> UIStackView.Alignment { return .leading } } diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 2bc11b98..bcac274b 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -51,12 +51,7 @@ open class DashLine: MFView { dashLayer.backgroundColor = UIColor.clear.cgColor dashLayer.frame = bounds - if let sublayers = layer.sublayers { - for sublayer in sublayers { - sublayer.removeFromSuperlayer() - } - } - + layer.sublayers?.forEach { $0.removeFromSuperlayer() } layer.addSublayer(dashLayer) let path = UIBezierPath() diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index cc531d75..3dd85f71 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -204,13 +204,17 @@ import UIKit // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view @objc public func addCaretViewAccessory() { + guard accessoryView == nil else { return } - let width: CGFloat = 6 - let height: CGFloat = 10 - caretView = CaretView(lineThickness: CaretView.thin) - caretView?.frame = CGRect(x: 0, y: 0, width: width, height: height) - caretViewWidthSizeObject = MFSizeObject(standardSize: width, standardiPadPortraitSize: 9) - caretViewHeightSizeObject = MFSizeObject(standardSize: height, standardiPadPortraitSize: 16) + + caretView = CaretView(lineWidth: 1) + caretView?.size = .small + caretView?.setConstraints() + + if let size = caretView?.size?.dimensions() { + caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9) + caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16) + } accessoryView = caretView } From ae5a1c0c025d165d91accc924ebb9f0c8224ceba Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 4 Nov 2019 14:44:22 -0500 Subject: [PATCH 040/112] la. --- .../Atoms/TextFields/DigitEntryField.swift | 31 ++++++---- .../Atoms/TextFields/DropdownEntryField.swift | 30 ++++----- .../Atoms/TextFields/FormEntryField.swift | 7 ++- .../Atoms/TextFields/MdnEntryField.swift | 30 ++++++--- .../Atoms/TextFields/TextEntryField.swift | 3 + MVMCoreUI/Atoms/Views/CaretView.swift | 62 +++++++++---------- MVMCoreUI/Molecules/Items/TableViewCell.swift | 14 ++--- 7 files changed, 101 insertions(+), 76 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 86fbae9a..cb96a2cb 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -30,7 +30,7 @@ import UIKit public override var isEnabled: Bool { didSet { if isEnabled { - titleLabel.styleB2(true) + titleLabel.textColor = .black } else { titleLabel.textColor = .mfBattleshipGrey() } @@ -49,7 +49,7 @@ import UIKit var string = "" digitFields.forEach { string += $0.attributedPlaceholder?.string ?? "" } - + return !string.isEmpty ? string : nil } set { @@ -90,7 +90,7 @@ import UIKit var string = "" digitFields.forEach { string += $0.text ?? "" } - + return string } set { @@ -157,7 +157,7 @@ import UIKit buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } - public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat? = nil) { + public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?, size: CGFloat? = nil) { super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) setup() @@ -177,7 +177,7 @@ import UIKit open func setup() { hideBorder = true - titleLabel.styleB2(true) + // titleLabel.styleB2(true) alignCenterHorizontal() } @@ -199,9 +199,9 @@ import UIKit self.titleLabel.updateView(size) if !self.digitFields.isEmpty { - + StackableViewController.remove(self.digitFields) - + self.digitFields.forEach { $0.updateView(size) } } @@ -225,14 +225,16 @@ import UIKit func createDigitField() -> DigitBox { let textField = DigitBox() + textField.isAccessibilityElement = true textField.delegate = self textField.textBoxDelegate = self - return textField } func buildTextFieldsView(size: CGFloat) { + var accessibleElements: [Any] = [titleLabel] + // Remove all current UI. if !digitFields.isEmpty { StackableViewController.remove(digitFields) @@ -241,7 +243,10 @@ import UIKit if numberOfDigits > 0 { let digitFields = [DigitBox](repeating: createDigitField(), count: numberOfDigits) - digitFields.forEach { $0.updateView(size) } + digitFields.forEach { + $0.updateView(size) + accessibleElements.append($0) + } self.digitFields = digitFields setupTextFieldsView(forSize: size) @@ -249,6 +254,8 @@ import UIKit } else { digitFields = [] } + + accessibilityElements = accessibleElements + [feedbackLabel] } override func valueChanged() { @@ -309,7 +316,7 @@ import UIKit validationBlock = { enteredValue in guard let enteredValue = enteredValue else { return false } - + return enteredValue.count > 0 && enteredValue.count == self.digitFields.count } } @@ -379,7 +386,7 @@ import UIKit super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } - open override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 44 } @@ -390,7 +397,7 @@ import UIKit open override class func accessibilityElements() -> [Any]? { // let fields = [] -// return self.digitFields + // return self.digitFields return nil } diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index a553913c..fe5f44b7 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -14,8 +14,10 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - let dropDownCaretLabel: CaretView = { + let dropDownCaretView: CaretView = { let caret = CaretView() +// caret.size = .medium + caret.direction = .down caret.isHidden = true caret.isUserInteractionEnabled = true return caret @@ -77,9 +79,8 @@ import UIKit private func setup() { - dropDownCaretLabel.heightAnchor.constraint(equalToConstant: 40).isActive = true - dropDownCaretWidth = widthAnchor.constraint(equalToConstant: 40) - dropDownCaretWidth?.isActive = true + let tapOnCaret = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCaretView.addGestureRecognizer(tapOnCaret) } //-------------------------------------------------- @@ -87,20 +88,19 @@ import UIKit //-------------------------------------------------- open override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) - let tapOnCaret = UITapGestureRecognizer(target: self, action: #selector(startEditing)) - dropDownCaretLabel.addGestureRecognizer(tapOnCaret) + container.addSubview(dropDownCaretView) - container.addSubview(dropDownCaretLabel) + dropDownCaretView.heightAnchor.constraint(equalToConstant: 9).isActive = true + dropDownCaretView.widthAnchor.constraint(equalToConstant: 16).isActive = true textFieldTrailingConstraint?.isActive = false - dropDownCaretLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true - textFieldTrailingConstraint = dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) + dropDownCaretView.topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor, constant: 13).isActive = true + textFieldTrailingConstraint = dropDownCaretView.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) textFieldTrailingConstraint?.isActive = true - container.trailingAnchor.constraint(equalTo: dropDownCaretLabel.trailingAnchor, constant: 16).isActive = true - container.bottomAnchor.constraint(equalTo: dropDownCaretLabel.bottomAnchor).isActive = true - dropDownCaretWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) - dropDownCaretWidth?.isActive = true + container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true + container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true } //-------------------------------------------------- @@ -109,7 +109,7 @@ import UIKit public func showDropDown(_ show: Bool) { - dropDownCaretLabel.isHidden = !show + dropDownCaretView.isHidden = !show dropDownCaretWidth?.isActive = !show setNeedsLayout() layoutIfNeeded() @@ -211,7 +211,7 @@ extension DropdownEntryField { else { return } if let _ = dictionary[KeyType] as? String { - dropDownCaretLabel.isHidden = false + dropDownCaretView.isHidden = false } } } diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index b0638253..f3e72664 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -23,6 +23,7 @@ import UIKit let label = Label() label.font = MFStyler.fontB3() label.textColor = UIColor.mfBattleshipGrey() + label.isAccessibilityElement = true label.setContentCompressionResistancePriority(.required, for: .vertical) label.setContentCompressionResistancePriority(.required, for: .horizontal) return label @@ -32,6 +33,7 @@ import UIKit let label = Label() label.font = MFStyler.fontForTextFieldUnderLabel() label.textColor = .black + label.isAccessibilityElement = true label.setContentCompressionResistancePriority(.required, for: .vertical) label.setContentCompressionResistancePriority(.required, for: .horizontal) return label @@ -39,6 +41,7 @@ import UIKit public private(set) var fieldContainer: UIView = { let view = UIView(frame: .zero) + view.isAccessibilityElement = false view.translatesAutoresizingMaskIntoConstraints = false return view }() @@ -175,6 +178,7 @@ import UIKit translatesAutoresizingMaskIntoConstraints = false setContentCompressionResistancePriority(.required, for: .vertical) + accessibilityElements = [titleLabel, feedbackLabel] backgroundColor = .clear addSubview(titleLabel) @@ -310,6 +314,7 @@ import UIKit case disable } + /// Updates the visual appearance of the container, with some logical laterations as well. public func updateUI(appearance: Appearance) { self.appearance = appearance @@ -339,7 +344,7 @@ import UIKit case .disable: isUserInteractionEnabled = false borderStrokeColor = .mfSilver() - titleLabel.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() + titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor } diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index f1b769d5..6ca54314 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -20,11 +20,25 @@ import MVMCore public var isNationalMdn = true public var shouldValidateMDN = false + private weak var outsiderTextDelegate: UITextFieldDelegate? + public var mdn: String? { get { return MVMCoreUIUtility.removeMdnFormat(text) } set { text = MVMCoreUIUtility.formatMdn(newValue) } } + /// If you're using a MFViewController, you must set this to it + public override weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { return textField.delegate } + set { + textField.delegate = newValue + outsiderTextDelegate = newValue + if newValue != nil { + textField.delegate = self + } + } + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -92,7 +106,7 @@ import MVMCore clearErrorState() } else { errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") - UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField) + UIAccessibility.post(notification: .layoutChanged, argument: textField) } return isValid @@ -145,7 +159,7 @@ import MVMCore textField.resignFirstResponder() - return uiTextFieldDelegate?.textFieldShouldReturn?(textField) ?? true + return outsiderTextDelegate?.textFieldShouldReturn?(textField) ?? true } public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { @@ -154,18 +168,18 @@ import MVMCore return false } - return uiTextFieldDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + return outsiderTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } public func textFieldDidBeginEditing(_ textField: UITextField) { textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) - uiTextFieldDelegate?.textFieldDidBeginEditing?(textField) + outsiderTextDelegate?.textFieldDidBeginEditing?(textField) } public func textFieldDidEndEditing(_ textField: UITextField) { - uiTextFieldDelegate?.textFieldDidEndEditing?(textField) + outsiderTextDelegate?.textFieldDidEndEditing?(textField) if validateAndColor() && isNationalMdn { textField.text = MVMCoreUIUtility.formatMdn(textField.text) @@ -178,16 +192,16 @@ import MVMCore public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - return uiTextFieldDelegate?.textFieldShouldBeginEditing?(textField) ?? true + return outsiderTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - return uiTextFieldDelegate?.textFieldShouldEndEditing?(textField) ?? true + return outsiderTextDelegate?.textFieldShouldEndEditing?(textField) ?? true } public func textFieldShouldClear(_ textField: UITextField) -> Bool { - return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true + return outsiderTextDelegate?.textFieldShouldClear?(textField) ?? true } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 9b0bfbb2..c40042b3 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -26,6 +26,7 @@ import UIKit public private(set) var textField: UITextField = { let textField = UITextField(frame: .zero) + textField.isAccessibilityElement = true textField.translatesAutoresizingMaskIntoConstraints = false textField.setContentCompressionResistancePriority(.required, for: .vertical) textField.font = MFStyler.fontForTextField() @@ -151,6 +152,8 @@ import UIKit textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16) textFieldTrailingConstraint?.isActive = true + + accessibilityElements = [titleLabel, textField, feedbackLabel] } open override func updateView(_ size: CGFloat) { diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 519965a1..4b13d5fa 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -15,7 +15,7 @@ public var strokeColor: UIColor = .black public var lineWidth: CGFloat = 1 public var direction: Direction = .right - public var size: CaretSize? +// public var size: CaretSize? private var caretPath: UIBezierPath = UIBezierPath() @@ -33,29 +33,29 @@ // MARK: - Constraints //------------------------------------------------------ - public enum CaretSize: String { - case small - case medium - case large - - // Dimensions of container; provided by InVision. - func dimensions() -> CGSize { - - switch self { - case .small: - return CGSize(width: 6, height: 10) - - case .medium: - return CGSize(width: 9, height: 16) - - case .large: - return CGSize(width: 14, height: 24) - } - } - } +// public enum CaretSize: String { +// case small +// case medium +// case large +// +// // Dimensions of container; provided by InVision. +// func dimensions() -> CGSize { +// +// switch self { +// case .small: +// return CGSize(width: 6, height: 10) +// +// case .medium: +// return CGSize(width: 9, height: 16) +// +// case .large: +// return CGSize(width: 14, height: 24) +// } +// } +// } - public var heightConstraint: NSLayoutConstraint? - public var widthConstraint: NSLayoutConstraint? +// public var heightConstraint: NSLayoutConstraint? +// public var widthConstraint: NSLayoutConstraint? //------------------------------------------------------ // MARK: - Initialization @@ -88,8 +88,8 @@ defaultState() // Establishing references. - heightConstraint = heightAnchor.constraint(equalToConstant: 0) - widthConstraint = widthAnchor.constraint(equalToConstant: 0) +// heightConstraint = heightAnchor.constraint(equalToConstant: 0) +// widthConstraint = widthAnchor.constraint(equalToConstant: 0) } //------------------------------------------------------ @@ -162,12 +162,12 @@ @objc public func setConstraints() { - guard let dimensions = size?.dimensions() else { return } - - heightConstraint?.constant = dimensions.height - heightConstraint?.isActive = true - widthConstraint?.constant = dimensions.width - widthConstraint?.isActive = true +// guard let dimensions = size?.dimensions() else { return } +// +// heightConstraint?.constant = dimensions.height +// heightConstraint?.isActive = true +// widthConstraint?.constant = dimensions.width +// widthConstraint?.isActive = true } //------------------------------------------------------ diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index 3dd85f71..588b0b78 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -204,17 +204,13 @@ import UIKit // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view @objc public func addCaretViewAccessory() { - guard accessoryView == nil else { return } - + let width: CGFloat = 6 + let height: CGFloat = 10 caretView = CaretView(lineWidth: 1) - caretView?.size = .small - caretView?.setConstraints() - - if let size = caretView?.size?.dimensions() { - caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9) - caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16) - } + caretView?.frame = CGRect(x: 0, y: 0, width: width, height: height) + caretViewWidthSizeObject = MFSizeObject(standardSize: width, standardiPadPortraitSize: 9) + caretViewHeightSizeObject = MFSizeObject(standardSize: height, standardiPadPortraitSize: 16) accessoryView = caretView } From 88eec2bf9ded5e980e119c8d29191125895470e9 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 5 Nov 2019 09:25:27 -0500 Subject: [PATCH 041/112] carrots and windows. --- .../Atoms/TextFields/DropdownEntryField.swift | 10 +-- MVMCoreUI/Atoms/Views/CaretView.swift | 72 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index fe5f44b7..4d418ede 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -18,8 +18,11 @@ import UIKit let caret = CaretView() // caret.size = .medium caret.direction = .down - caret.isHidden = true + caret.lineWidth = 1.5 +// caret.isHidden = true caret.isUserInteractionEnabled = true + caret.heightAnchor.constraint(equalToConstant: 9).isActive = true + caret.widthAnchor.constraint(equalToConstant: 16).isActive = true return caret }() @@ -92,15 +95,14 @@ import UIKit container.addSubview(dropDownCaretView) - dropDownCaretView.heightAnchor.constraint(equalToConstant: 9).isActive = true - dropDownCaretView.widthAnchor.constraint(equalToConstant: 16).isActive = true - textFieldTrailingConstraint?.isActive = false dropDownCaretView.topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor, constant: 13).isActive = true textFieldTrailingConstraint = dropDownCaretView.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) textFieldTrailingConstraint?.isActive = true container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true + + dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true } //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 4b13d5fa..8d44e343 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -12,13 +12,13 @@ // MARK: - Properties //------------------------------------------------------ + private var caretPath: UIBezierPath = UIBezierPath() public var strokeColor: UIColor = .black public var lineWidth: CGFloat = 1 + public var direction: Direction = .right -// public var size: CaretSize? - - private var caretPath: UIBezierPath = UIBezierPath() - + public var size: CaretSize? + public var enabledColor: UIColor = .black public var disabledColor: UIColor = .mfSilver() @@ -33,29 +33,33 @@ // MARK: - Constraints //------------------------------------------------------ -// public enum CaretSize: String { -// case small -// case medium -// case large -// -// // Dimensions of container; provided by InVision. -// func dimensions() -> CGSize { -// -// switch self { -// case .small: -// return CGSize(width: 6, height: 10) -// -// case .medium: -// return CGSize(width: 9, height: 16) -// -// case .large: -// return CGSize(width: 14, height: 24) -// } -// } -// } - -// public var heightConstraint: NSLayoutConstraint? -// public var widthConstraint: NSLayoutConstraint? + /// Sizes of CaretView are derived from InVision design specs. They are provided for convenience. + public enum CaretSize { + case small(Orientation) + case medium(Orientation) + case large(Orientation) + + /// Orientation based on the longest line of the view + public enum Orientation { + case vertical + case horizontal + } + + // Dimensions of container; provided by InVision. + func dimensions() -> CGSize { + + switch self { + case .small(let o): + return o == .vertical ? CGSize(width: 6, height: 10) : CGSize(width: 10, height: 6) + + case .medium(let o): + return o == .vertical ? CGSize(width: 9, height: 16) : CGSize(width: 16, height: 9) + + case .large(let o): + return o == .vertical ? CGSize(width: 14, height: 24) : CGSize(width: 24, height: 14) + } + } + } //------------------------------------------------------ // MARK: - Initialization @@ -86,10 +90,6 @@ @objc override open func setupView() { defaultState() - - // Establishing references. -// heightConstraint = heightAnchor.constraint(equalToConstant: 0) -// widthConstraint = widthAnchor.constraint(equalToConstant: 0) } //------------------------------------------------------ @@ -162,12 +162,10 @@ @objc public func setConstraints() { -// guard let dimensions = size?.dimensions() else { return } -// -// heightConstraint?.constant = dimensions.height -// heightConstraint?.isActive = true -// widthConstraint?.constant = dimensions.width -// widthConstraint?.isActive = true + guard let dimensions = size?.dimensions() else { return } + + heightAnchor.constraint(equalToConstant: dimensions.height).isActive = true + widthAnchor.constraint(equalToConstant: dimensions.width).isActive = true } //------------------------------------------------------ From 6d7c1df7ccb5d97b224334e7afa6299756948476 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 6 Nov 2019 09:12:18 -0500 Subject: [PATCH 042/112] more. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 6 +- .../Atoms/TextFields/DigitEntryField.swift | 206 ++++++++---------- .../Atoms/TextFields/FormEntryField.swift | 2 +- MVMCoreUI/Atoms/Views/CaretView.swift | 1 + MVMCoreUI/Molecules/Items/TableViewCell.swift | 30 ++- 5 files changed, 121 insertions(+), 124 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index f4edd1a1..05e60146 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -28,10 +28,10 @@ import UIKit private var previousSize: CGFloat = 0.0 /// Determines if a border should be drawn. - private var hideBorder = false - private var showError = false + var hideBorder = false + var showError = false - private var borderStrokeColor: UIColor = .mfSilver() + var borderStrokeColor: UIColor = .mfSilver() private var borderPath: UIBezierPath = UIBezierPath() //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index cb96a2cb..d7533162 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -10,40 +10,35 @@ import UIKit /** * Subclass of TextEntryField due to the conveniences provided. - * TODO: Compare overrides to determine if TextEntryField is necessary. */ @objcMembers open class DigitEntryField: TextEntryField, DigitBoxDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- - public private(set) var digitFieldsView: UIView? + public private(set) var digitFieldsStack: UIStackView? //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - private var numberOfDigits = 4 + private(set) var numberOfDigits = 4 private var switchedAutomatically = false + public var digitFields: [DigitBox] = [] public override var isEnabled: Bool { didSet { - if isEnabled { - titleLabel.textColor = .black - } else { - titleLabel.textColor = .mfBattleshipGrey() - } + titleLabel.textColor = isEnabled ? .black : .black - for textField in digitFields { - textField.isUserInteractionEnabled = isEnabled - textField.isEnabled = isEnabled - textField.textColor = isEnabled ? .black : .mfBattleshipGrey() + for digitBox in digitFields { + digitBox.isUserInteractionEnabled = isEnabled + digitBox.isEnabled = isEnabled + digitBox.textColor = isEnabled ? .black : .mfBattleshipGrey() } } } - /// Sets placeholder text in the textField. public override var placeholder: String? { get { var string = "" @@ -85,6 +80,7 @@ import UIKit } } + /// Traverses each digitbox to retrieve the held value. public override var text: String? { get { var string = "" @@ -107,34 +103,48 @@ import UIKit } } - public override var title: String? { - get { return titleLabel.text } - set { - if let formText = newValue, !formText.isEmpty { - titleContainerDistance?.constant = 10 - } else { - titleContainerDistance?.constant = 0 + /// Updates the visual appearance of the container, with some logical laterations as well. + public override func updateUI(appearance: Appearance) { + + self.appearance = appearance + isUserInteractionEnabled = true + titleLabel.textColor = .mfBattleshipGrey() + hideBorder = false + feedback = showError ? errorMessage : nil + + switch appearance { + case .original: + digitFields.forEach { + $0.borderStrokeColor = .mfSilver() + $0.bottomBar.backgroundColor = UIColor.black.cgColor } - - super.title = newValue - } - } - - public override var errorMessage: String? { - didSet { - if let errorMessage = errorMessage, !errorMessage.isEmpty { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - if self.showError { - self.feedbackContainerDistance?.constant = 10 - self.setNeedsLayout() - } - - self.digitFields.forEach { $0.showErrorState(true) } - } + case .error: + digitFields.forEach { + $0.borderStrokeColor = .mfPumpkin() + $0.bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor } + self.digitFields.forEach { $0.showErrorState(true) } + case .lock: + digitFields.forEach { + $0.isUserInteractionEnabled = false + $0.hideBorder = true + $0.bottomBar.backgroundColor = UIColor.clear.cgColor + } + case .select: + digitFields.forEach { + $0.borderStrokeColor = .black + $0.bottomBar.backgroundColor = UIColor.black.cgColor + } + case .disable: + digitFields.forEach { + $0.isUserInteractionEnabled = false + $0.borderStrokeColor = .mfSilver() + $0.bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor + } + titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() } + + refreshBorderUI(bottomBarSize: appearance == .error ? 4 : 1) } //-------------------------------------------------- @@ -158,7 +168,7 @@ import UIKit } public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?, size: CGFloat? = nil) { - super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) + super.init(bothDelegates: delegate) setup() self.numberOfDigits = numberOfDigits @@ -176,52 +186,18 @@ import UIKit open func setup() { + // Border for Field Container will not be shown. hideBorder = true - // titleLabel.styleB2(true) alignCenterHorizontal() + isAccessibilityElement = false } open override func setupFieldContainerContent(_ container: UIView) { + textField.removeFromSuperview() setupTextFieldsView(forSize: CGFloat(numberOfDigits)) } - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - - open override func updateView(_ size: CGFloat) { - super.updateView(size) - - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.titleLabel.updateView(size) - - if !self.digitFields.isEmpty { - - StackableViewController.remove(self.digitFields) - - self.digitFields.forEach { $0.updateView(size) } - } - - // Layout text boxes. - self.setupTextFieldsView(forSize: size) - } - } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - func removeError() { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.digitFields.forEach { $0.showErrorState(false) } - } - } - func createDigitField() -> DigitBox { let textField = DigitBox() @@ -237,7 +213,7 @@ import UIKit // Remove all current UI. if !digitFields.isEmpty { - StackableViewController.remove(digitFields) + digitFieldsStack?.subviews.forEach { $0.removeFromSuperview() } } if numberOfDigits > 0 { @@ -258,6 +234,42 @@ import UIKit accessibilityElements = accessibleElements + [feedbackLabel] } + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.titleLabel.updateView(size) + + if !self.digitFields.isEmpty { + + self.digitFieldsStack?.subviews.forEach { $0.removeFromSuperview() } + self.digitFields.forEach { $0.updateView(size) } + } + + // Layout text boxes. + self.setupTextFieldsView(forSize: size) + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func removeError() { + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.digitFields.forEach { $0.showErrorState(false) } + } + } + override func valueChanged() { super.valueChanged() @@ -289,27 +301,13 @@ import UIKit func setupTextFieldsView(forSize size: CGFloat) { - guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize(), - let digitFieldsView = digitFieldsView - else { return } + guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize() else { return } - StackableViewController.populateViewHorizontally(digitFieldsView, withUIArray: digitFields, withSpacingBlock: { object in - - var inset = UIEdgeInsets(top: 0, left: space, bottom: 0, right: space) - - if self.digitFields.count == 1 { - inset.left = 0 - inset.right = 0 - - } else if let field = object as? UITextField, field == self.digitFields.first { - inset.left = 0 - - } else if let field = object as? UITextField, field == self.digitFields.last { - inset.right = 0 - } - - return inset - }) + let stack = UIStackView(arrangedSubviews: digitFields) + self.digitFieldsStack = stack + textField.addSubview(stack) + stack.distribution = .equalSpacing + stack.spacing = space } public override func defaultValidationBlock() { @@ -367,6 +365,7 @@ import UIKit //-------------------------------------------------- open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json else { return } @@ -382,25 +381,12 @@ import UIKit } 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: - Accessibility - //-------------------------------------------------- - - open override class func accessibilityElements() -> [Any]? { - // let fields = [] - - // return self.digitFields - return nil - } - //-------------------------------------------------- // MARK: - Text Field Delegate //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index f3e72664..22d971d8 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -73,7 +73,7 @@ import UIKit return layer }() - public private(set) var appearance: Appearance = .original + public var appearance: Appearance = .original public var showError = false diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 8d44e343..70056219 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -160,6 +160,7 @@ strokeColor = .black } + /// Ensure you have defined a CaretSize with Orientation before calling. @objc public func setConstraints() { guard let dimensions = size?.dimensions() else { return } diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index 588b0b78..673e5e80 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -23,6 +23,7 @@ import UIKit // For separation between cells. public var topSeparatorView: SeparatorView? public var bottomSeparatorView: SeparatorView? + public enum SeparatorFrequency: String { case all case allExceptTop @@ -40,17 +41,21 @@ import UIKit // MARK: - Styling open func style(with styleString: String?) { - guard let styleString = styleString else { - return - } + + guard let styleString = styleString else { return } + switch styleString { case "standard": styleStandard() + case "header": styleHeader() + case "none": styleNone() - default: break + + default: + break } } @@ -204,13 +209,18 @@ import UIKit // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view @objc public func addCaretViewAccessory() { + guard accessoryView == nil else { return } - let width: CGFloat = 6 - let height: CGFloat = 10 + caretView = CaretView(lineWidth: 1) - caretView?.frame = CGRect(x: 0, y: 0, width: width, height: height) - caretViewWidthSizeObject = MFSizeObject(standardSize: width, standardiPadPortraitSize: 9) - caretViewHeightSizeObject = MFSizeObject(standardSize: height, standardiPadPortraitSize: 16) + caretView?.size = .small(.vertical) + caretView?.setConstraints() + + if let size = caretView?.size?.dimensions() { + caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9) + caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16) + } + accessoryView = caretView } @@ -258,7 +268,7 @@ import UIKit } else { topSeparatorView?.hide() bottomSeparatorView?.setAsLight() - setSeparatorFrequency(TableViewCell.SeparatorFrequency.allExceptTop, indexPath: indexPath) + setSeparatorFrequency(.allExceptTop, indexPath: indexPath) } } From ce0ffeda2993fc225e4320eb9553da2e06cabe13 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 6 Nov 2019 16:06:16 -0500 Subject: [PATCH 043/112] more changes. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 8 +- .../Atoms/TextFields/DigitEntryField.swift | 136 +++++++++--------- .../Atoms/TextFields/DropdownEntryField.swift | 4 +- .../Atoms/TextFields/TextEntryField.swift | 1 - 4 files changed, 76 insertions(+), 73 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 05e60146..b8f04db6 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -77,7 +77,7 @@ import UIKit backgroundColor = .white textAlignment = .center keyboardType = .numberPad - layer.borderWidth = 1 +// layer.borderWidth = 1 showErrorState(false) widthConstraint = widthAnchor.constraint(equalToConstant: 39) @@ -157,10 +157,12 @@ import UIKit }) sizeObject?.performBlockBase(onSize: size) - self.widthConstraint?.constant = digitWidth - self.heightConstraint?.constant = digitHeight +// self.widthConstraint?.constant = digitWidth +// self.heightConstraint?.constant = digitHeight self.previousSize = size } + + self.setNeedsLayout() } } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index d7533162..a705587d 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -16,6 +16,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- + /// UI Container of digitFields public private(set) var digitFieldsStack: UIStackView? //-------------------------------------------------- @@ -29,7 +30,7 @@ import UIKit public override var isEnabled: Bool { didSet { - titleLabel.textColor = isEnabled ? .black : .black + titleLabel.textColor = isEnabled ? .black : .mfBattleshipGrey() for digitBox in digitFields { digitBox.isUserInteractionEnabled = isEnabled @@ -65,11 +66,11 @@ import UIKit feedback = "" } - if let feedback = feedback, !feedback.isEmpty { - feedbackContainerDistance?.constant = 10 - } else { - feedbackContainerDistance?.constant = 0 - } +// if let feedback = feedback, !feedback.isEmpty { +// feedbackContainerDistance?.constant = 10 +// } else { +// feedbackContainerDistance?.constant = 0 +// } /* * adding missing accessibilityLabel value @@ -109,7 +110,6 @@ import UIKit self.appearance = appearance isUserInteractionEnabled = true titleLabel.textColor = .mfBattleshipGrey() - hideBorder = false feedback = showError ? errorMessage : nil switch appearance { @@ -127,7 +127,6 @@ import UIKit case .lock: digitFields.forEach { $0.isUserInteractionEnabled = false - $0.hideBorder = true $0.bottomBar.backgroundColor = UIColor.clear.cgColor } case .select: @@ -143,8 +142,6 @@ import UIKit } titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() } - - refreshBorderUI(bottomBarSize: appearance == .error ? 4 : 1) } //-------------------------------------------------- @@ -160,19 +157,18 @@ import UIKit self.init(frame: .zero) } - public convenience init(numberOfDigits: Int) { - self.init(frame: .zero) + public init(numberOfDigits: Int) { + super.init(frame: .zero) self.numberOfDigits = numberOfDigits - buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) + setup() } public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?, size: CGFloat? = nil) { super.init(bothDelegates: delegate) - setup() self.numberOfDigits = numberOfDigits - buildTextFieldsView(size: size ?? MVMCoreUISplitViewController.getDetailViewWidth()) + setup() } required public init?(coder: NSCoder) { @@ -186,19 +182,23 @@ import UIKit open func setup() { - // Border for Field Container will not be shown. - hideBorder = true alignCenterHorizontal() isAccessibilityElement = false + hideBorder = true + bottomBar.backgroundColor = UIColor.clear.cgColor + bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height, width: 0, height: 0) + setupDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } open override func setupFieldContainerContent(_ container: UIView) { + // Will not be using textField. + // TODO: May subclass from FormEntry if minimal overriding. textField.removeFromSuperview() - setupTextFieldsView(forSize: CGFloat(numberOfDigits)) + setupDigitFieldsView(size: MVMCoreUIUtility.getWidth()) } - func createDigitField() -> DigitBox { + private func createDigitField() -> DigitBox { let textField = DigitBox() textField.isAccessibilityElement = true @@ -207,26 +207,49 @@ import UIKit return textField } - func buildTextFieldsView(size: CGFloat) { + func setupDigitFieldsView(size: CGFloat) { + + self.digitFieldsStack?.subviews.forEach { $0.removeFromSuperview() } + digitFieldsStack?.removeFromSuperview() var accessibleElements: [Any] = [titleLabel] - // Remove all current UI. - if !digitFields.isEmpty { - digitFieldsStack?.subviews.forEach { $0.removeFromSuperview() } + // Remove all current views in UI. + if let stackViews = digitFieldsStack?.subviews, !stackViews.isEmpty { + stackViews.forEach { $0.removeFromSuperview() } } if numberOfDigits > 0 { let digitFields = [DigitBox](repeating: createDigitField(), count: numberOfDigits) - - digitFields.forEach { - $0.updateView(size) - accessibleElements.append($0) - } - self.digitFields = digitFields - setupTextFieldsView(forSize: size) + guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize() else { return } + var prevBox: DigitBox? + + for (index, box) in digitFields.enumerated() { + // $0.updateView(size) + accessibleElements.append(box) + fieldContainer.addSubview(box) + + box.widthAnchor.constraint(equalToConstant: 39).isActive = true + box.heightAnchor.constraint(equalToConstant: 44).isActive = true + + box.topAnchor.constraint(equalTo: fieldContainer.topAnchor).isActive = true + fieldContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true + + if index == 0 { + box.leadingAnchor.constraint(equalTo: fieldContainer.leadingAnchor).isActive = true + + } else if index == digitFields.count - 1 { + + box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true + fieldContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true + } else { + box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor).isActive = true + } + + prevBox = box + } } else { digitFields = [] } @@ -244,45 +267,37 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.titleLabel.updateView(size) - if !self.digitFields.isEmpty { self.digitFieldsStack?.subviews.forEach { $0.removeFromSuperview() } self.digitFields.forEach { $0.updateView(size) } + self.layoutIfNeeded() } // Layout text boxes. - self.setupTextFieldsView(forSize: size) +// self.setupDigitFieldsView(size: size) } +// hideBorder = true } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - func removeError() { - - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.digitFields.forEach { $0.showErrorState(false) } - } - } - override func valueChanged() { super.valueChanged() - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - if let feedback = self.feedback, !feedback.isEmpty { - self.feedbackContainerDistance?.constant = 10 - - } else { - self.feedbackContainerDistance?.constant = 0 - } - } + // TODO: is feedbackContainerDistance needed? +// DispatchQueue.main.async { [weak self] in +// guard let self = self else { return } +// +// if let feedback = self.feedback, !feedback.isEmpty { +// self.feedbackContainerDistance?.constant = 10 +// +// } else { +// self.feedbackContainerDistance?.constant = 0 +// } +// } } func setAsSecureTextEntry(_ secureEntry: Bool) { @@ -299,17 +314,6 @@ import UIKit } } - func setupTextFieldsView(forSize size: CGFloat) { - - guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize() else { return } - - let stack = UIStackView(arrangedSubviews: digitFields) - self.digitFieldsStack = stack - textField.addSubview(stack) - stack.distribution = .equalSpacing - stack.spacing = space - } - public override func defaultValidationBlock() { validationBlock = { enteredValue in @@ -319,7 +323,7 @@ import UIKit } } - func selectPreviousTextField(_ currentTextField: UITextField?, clear: Bool) { + public func selectPreviousTextField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false @@ -340,7 +344,7 @@ import UIKit } } - func selectNextTextField(_ currentTextField: UITextField?, clear: Bool) { + public func selectNextTextField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false @@ -380,7 +384,7 @@ import UIKit } } - buildTextFieldsView(size: MVMCoreUIUtility.getWidth()) + setupDigitFieldsView(size: MVMCoreUIUtility.getWidth()) } open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 4d418ede..84f1d83f 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -16,10 +16,8 @@ import UIKit let dropDownCaretView: CaretView = { let caret = CaretView() -// caret.size = .medium caret.direction = .down caret.lineWidth = 1.5 -// caret.isHidden = true caret.isUserInteractionEnabled = true caret.heightAnchor.constraint(equalToConstant: 9).isActive = true caret.widthAnchor.constraint(equalToConstant: 16).isActive = true @@ -162,7 +160,7 @@ extension DropdownEntryField { if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter.string(from: fromDate) + text = formatter.string(from: fromDate) } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index c40042b3..275a2925 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -174,7 +174,6 @@ import UIKit open func clearErrorState() { /* - [MVMCoreDispatchUtility performBlockOnMainThread:^{ self.separatorHeightConstraint.constant = 1; self.separatorView.backgroundColor = [UIColor blackColor]; From df1a2a0da00546855ac2d8f35aabb6260ef2d575 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 12 Nov 2019 11:06:43 -0500 Subject: [PATCH 044/112] latest changes for entry. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 +-- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 6 +-- .../Atoms/TextFields/DigitEntryField.swift | 47 ++++------------ .../Atoms/TextFields/DropdownEntryField.swift | 21 ++------ ...{FormEntryField.swift => EntryField.swift} | 54 +++++++++---------- .../Atoms/TextFields/MdnEntryField.swift | 17 +++--- .../Atoms/TextFields/TextEntryField.swift | 18 +------ MVMCoreUI/Atoms/Views/Checkbox.swift | 1 - 8 files changed, 56 insertions(+), 116 deletions(-) rename MVMCoreUI/Atoms/TextFields/{FormEntryField.swift => EntryField.swift} (92%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 90a8421f..6d76c854 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -25,7 +25,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 /* FormEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* FormEntryField.swift */; }; + 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.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 */; }; @@ -225,7 +225,7 @@ 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 /* FormEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormEntryField.swift; sourceTree = ""; }; + 0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.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 = ""; }; @@ -783,7 +783,7 @@ D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */, - 0A21DB7E235DECC500C160A2 /* FormEntryField.swift */, + 0A21DB7E235DECC500C160A2 /* EntryField.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */, @@ -1078,7 +1078,7 @@ D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, - 0A21DB7F235DECC500C160A2 /* FormEntryField.swift in Sources */, + 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index b8f04db6..09ecee72 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -12,6 +12,7 @@ import UIKit @objc optional func textFieldDidDelete(_ textField: UITextField?) } + @objcMembers open class DigitBox: UITextField, MVMCoreViewProtocol { //-------------------------------------------------- // MARK: - Properties @@ -77,7 +78,6 @@ import UIKit backgroundColor = .white textAlignment = .center keyboardType = .numberPad -// layer.borderWidth = 1 showErrorState(false) widthConstraint = widthAnchor.constraint(equalToConstant: 39) @@ -157,8 +157,8 @@ import UIKit }) sizeObject?.performBlockBase(onSize: size) -// self.widthConstraint?.constant = digitWidth -// self.heightConstraint?.constant = digitHeight + self.widthConstraint?.constant = digitWidth + self.heightConstraint?.constant = digitHeight self.previousSize = size } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index a705587d..9417fc27 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -9,16 +9,9 @@ import UIKit /** - * Subclass of TextEntryField due to the conveniences provided. + * Subclass of TextEntryField as it is to use similar logic as a singular textField but appear separate.. */ @objcMembers open class DigitEntryField: TextEntryField, DigitBoxDelegate { - //-------------------------------------------------- - // MARK: - Outlets - //-------------------------------------------------- - - /// UI Container of digitFields - public private(set) var digitFieldsStack: UIStackView? - //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -122,8 +115,8 @@ import UIKit digitFields.forEach { $0.borderStrokeColor = .mfPumpkin() $0.bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor + $0.showErrorState(true) } - self.digitFields.forEach { $0.showErrorState(true) } case .lock: digitFields.forEach { $0.isUserInteractionEnabled = false @@ -150,7 +143,6 @@ import UIKit public override init(frame: CGRect) { super.init(frame: frame) - setup() } public convenience init() { @@ -161,14 +153,11 @@ import UIKit super.init(frame: .zero) self.numberOfDigits = numberOfDigits - setup() } public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?, size: CGFloat? = nil) { - super.init(bothDelegates: delegate) - self.numberOfDigits = numberOfDigits - setup() + super.init(bothDelegates: delegate) } required public init?(coder: NSCoder) { @@ -180,7 +169,9 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - open func setup() { + public override func setupFieldContainerContent(_ container: UIView) { + +// textField.removeFromSuperview() alignCenterHorizontal() isAccessibilityElement = false @@ -188,14 +179,8 @@ import UIKit bottomBar.backgroundColor = UIColor.clear.cgColor bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height, width: 0, height: 0) setupDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) - } - - open override func setupFieldContainerContent(_ container: UIView) { - // Will not be using textField. - // TODO: May subclass from FormEntry if minimal overriding. textField.removeFromSuperview() - setupDigitFieldsView(size: MVMCoreUIUtility.getWidth()) } private func createDigitField() -> DigitBox { @@ -209,16 +194,8 @@ import UIKit func setupDigitFieldsView(size: CGFloat) { - self.digitFieldsStack?.subviews.forEach { $0.removeFromSuperview() } - digitFieldsStack?.removeFromSuperview() - var accessibleElements: [Any] = [titleLabel] - // Remove all current views in UI. - if let stackViews = digitFieldsStack?.subviews, !stackViews.isEmpty { - stackViews.forEach { $0.removeFromSuperview() } - } - if numberOfDigits > 0 { let digitFields = [DigitBox](repeating: createDigitField(), count: numberOfDigits) self.digitFields = digitFields @@ -227,25 +204,22 @@ import UIKit var prevBox: DigitBox? for (index, box) in digitFields.enumerated() { - // $0.updateView(size) accessibleElements.append(box) fieldContainer.addSubview(box) - - box.widthAnchor.constraint(equalToConstant: 39).isActive = true - box.heightAnchor.constraint(equalToConstant: 44).isActive = true - + box.topAnchor.constraint(equalTo: fieldContainer.topAnchor).isActive = true fieldContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true + box.centerYAnchor.constraint(equalTo: fieldContainer.centerYAnchor).isActive = true if index == 0 { box.leadingAnchor.constraint(equalTo: fieldContainer.leadingAnchor).isActive = true } else if index == digitFields.count - 1 { - box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true fieldContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true + } else { - box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor).isActive = true + box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true } prevBox = box @@ -269,7 +243,6 @@ import UIKit if !self.digitFields.isEmpty { - self.digitFieldsStack?.subviews.forEach { $0.removeFromSuperview() } self.digitFields.forEach { $0.updateView(size) } self.layoutIfNeeded() } diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 84f1d83f..7358d9f6 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -53,7 +53,6 @@ import UIKit public override init(frame: CGRect) { super.init(frame: frame) - setup() } public convenience init() { @@ -64,7 +63,6 @@ import UIKit public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) - setup() MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) setBothTextDelegates(to: bothDelegates) } @@ -78,17 +76,7 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - private func setup() { - - let tapOnCaret = UITapGestureRecognizer(target: self, action: #selector(startEditing)) - dropDownCaretView.addGestureRecognizer(tapOnCaret) - } - - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - - open override func setupFieldContainerContent(_ container: UIView) { + public override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) container.addSubview(dropDownCaretView) @@ -101,6 +89,9 @@ import UIKit container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true + + let caretTap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCaretView.addGestureRecognizer(caretTap) } //-------------------------------------------------- @@ -206,9 +197,7 @@ extension DropdownEntryField { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - guard let dictionary = json, - !dictionary.isEmpty - else { return } + guard let dictionary = json, !dictionary.isEmpty else { return } if let _ = dictionary[KeyType] as? String { dropDownCaretView.isHidden = false diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift similarity index 92% rename from MVMCoreUI/Atoms/TextFields/FormEntryField.swift rename to MVMCoreUI/Atoms/TextFields/EntryField.swift index 22d971d8..9b437901 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -1,5 +1,5 @@ // -// FormEntryField.swift +// EntryField.swift // MVMCoreUI // // Created by Kevin Christiano on 10/21/19. @@ -14,7 +14,7 @@ import UIKit * * When subclassing, be sure to override setupFieldContainerContent(). In this method you will setup all the content bound to the field container. */ -@objcMembers open class FormEntryField: ViewConstrainingView { +@objcMembers open class EntryField: ViewConstrainingView { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -22,20 +22,9 @@ import UIKit public private(set) var titleLabel: Label = { let label = Label() label.font = MFStyler.fontB3() - label.textColor = UIColor.mfBattleshipGrey() + label.textColor = .mfBattleshipGrey() label.isAccessibilityElement = true label.setContentCompressionResistancePriority(.required, for: .vertical) - label.setContentCompressionResistancePriority(.required, for: .horizontal) - return label - }() - - public private(set) var feedbackLabel: Label = { - let label = Label() - label.font = MFStyler.fontForTextFieldUnderLabel() - label.textColor = .black - label.isAccessibilityElement = true - label.setContentCompressionResistancePriority(.required, for: .vertical) - label.setContentCompressionResistancePriority(.required, for: .horizontal) return label }() @@ -46,6 +35,15 @@ import UIKit return view }() + public private(set) var feedbackLabel: Label = { + let label = Label() + label.font = MFStyler.fontForTextFieldUnderLabel() + label.textColor = .black + label.isAccessibilityElement = true + label.setContentCompressionResistancePriority(.required, for: .vertical) + return label + }() + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -90,6 +88,7 @@ import UIKit } } + /// Sets the text of titleLabel public var title: String? { get { return titleLabel.text } set { @@ -101,7 +100,7 @@ import UIKit /// Override this to conveniently get/set the textfield(s). public var text: String? { get { return nil } - set { fatalError("You need to override FormEntryField's 'text' variable in your subclass.") } + set { fatalError("You need to override EntryField's 'text' variable in your subclass.") } } /// Sets feedback text in the textField. @@ -149,7 +148,6 @@ import UIKit /// This must be overriden by a subclass. public override init(frame: CGRect) { super.init(frame: frame) - setupView() } public convenience init() { @@ -158,13 +156,13 @@ import UIKit public init(title: String) { super.init(frame: .zero) - setupView() + titleLabel.text = title } required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("FormEntryField does not support xib.") + fatalError("EntryField does not support xib.") } //-------------------------------------------------- @@ -225,7 +223,7 @@ import UIKit super.updateView(size) titleLabel.updateView(size) - feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() + feedbackLabel.updateView(size) refreshBorderUI() } @@ -250,7 +248,7 @@ import UIKit let size: CGFloat = appearance == .error ? 4 : 1 bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) - self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) setNeedsDisplay() layoutIfNeeded() } @@ -353,21 +351,19 @@ import UIKit } // MARK: - Molecular -extension FormEntryField { +extension EntryField { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) self.delegateObject = delegateObject - guard let dictionary = json, - !dictionary.isEmpty - else { return } + guard let dictionary = json, !dictionary.isEmpty else { return } - if let formText = dictionary[KeyLabel] as? String { - title = formText + if let titleText = dictionary["title"] as? String { + title = titleText } - if let text = dictionary[KeyDisable] as? String, text.isEqual(StringY) || dictionary.boolForKey(KeyDisable) { + if let disable = dictionary[KeyDisable] as? String, disable.isEqual(StringY) || dictionary.boolForKey(KeyDisable) { isEnabled = false } @@ -395,7 +391,7 @@ extension FormEntryField { } // MARK: - Form Validation -extension FormEntryField: FormValidationProtocol { +extension EntryField: FormValidationProtocol { public func isValidField() -> Bool { return isValid @@ -411,7 +407,7 @@ extension FormEntryField: FormValidationProtocol { } // MARK: - Accessibility -extension FormEntryField { +extension EntryField { @objc open func pushAccessibilityNotification() { // To Be Overriden diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 6ca54314..ab4155bd 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -17,7 +17,7 @@ import MVMCore // MARK: - Properties //-------------------------------------------------- - public var isNationalMdn = true + public var isNationalMDN = true public var shouldValidateMDN = false private weak var outsiderTextDelegate: UITextFieldDelegate? @@ -45,7 +45,6 @@ import MVMCore public override init(frame: CGRect) { super.init(frame: .zero) - setup() } public convenience init() { @@ -55,7 +54,6 @@ import MVMCore /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { super.init(frame: .zero) - setup() setBothTextDelegates(to: bothDelegates) } @@ -68,7 +66,8 @@ import MVMCore // MARK: - Setup //-------------------------------------------------- - private func setup() { + public override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) textField.keyboardType = .numberPad @@ -86,11 +85,9 @@ import MVMCore public func hasValidMDN() -> Bool { - guard let MDN = mdn, - !MDN.isEmpty - else { return true } + guard let MDN = mdn, !MDN.isEmpty else { return true } - if isNationalMdn { + if isNationalMDN { return MVMCoreUIUtility.validateMDNString(MDN) } @@ -137,7 +134,7 @@ import MVMCore var unformattedMDN = MVMCoreUIUtility.removeMdnFormat(MDN) // Sometimes user add extra 1 in front of mdn in their address book - if isNationalMdn, + if isNationalMDN, let unformedMDN = unformattedMDN, unformedMDN.count == 11, unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" { @@ -181,7 +178,7 @@ import MVMCore outsiderTextDelegate?.textFieldDidEndEditing?(textField) - if validateAndColor() && isNationalMdn { + if validateAndColor() && isNationalMDN { textField.text = MVMCoreUIUtility.formatMdn(textField.text) } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 275a2925..63d3d0e1 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -19,7 +19,7 @@ import UIKit } -@objcMembers open class TextEntryField: FormEntryField, UITextFieldDelegate { +@objcMembers open class TextEntryField: EntryField, UITextFieldDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -55,7 +55,7 @@ import UIKit } } - // The text of this textField. + /// The text of this textField. public override var text: String? { get { return textField.text } set { @@ -173,20 +173,6 @@ import UIKit open func clearErrorState() { - /* - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - self.separatorHeightConstraint.constant = 1; - self.separatorView.backgroundColor = [UIColor blackColor]; - [self layoutIfNeeded]; - self.errorShowing = NO; - self.label.textColor = [UIColor blackColor]; - self.label.text = @""; - self.textField.accessibilityValue = nil; - [self setNeedsDisplay]; - [self layoutIfNeeded]; - }]; - */ - textField.accessibilityValue = nil updateUI(appearance: .original) } diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index e5a82b1d..aadfba64 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -396,7 +396,6 @@ import MVMCore } public func updateView(_ size: CGFloat) { - layoutIfNeeded() } From 48bcd87bc1c16dfa63de2001bfab51f302ea622f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 12 Nov 2019 14:29:37 -0500 Subject: [PATCH 045/112] Refactored UI drawing to new class. Reflected change. --- MVMCoreUI.xcodeproj/project.pbxproj | 12 ++ MVMCoreUI/Atoms/TextFields/DigitBox.swift | 99 ++++------ .../Atoms/TextFields/DigitEntryField.swift | 101 +++------- MVMCoreUI/Atoms/TextFields/EntryField.swift | 182 ++++-------------- .../Atoms/TextFields/TextEntryField.swift | 9 +- MVMCoreUI/BaseClasses/View.swift | 20 +- MVMCoreUI/Containers/views/FormView.swift | 171 ++++++++++++++++ 7 files changed, 311 insertions(+), 283 deletions(-) create mode 100644 MVMCoreUI/Containers/views/FormView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 9978a4c0..fcbd1272 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 0ABD136B237B193A0081388D /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormView.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; @@ -237,6 +238,7 @@ 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; + 0ABD136A237B193A0081388D /* FormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; @@ -441,6 +443,14 @@ path = FormUIHelpers; sourceTree = ""; }; + 0ABD1369237B18EE0081388D /* views */ = { + isa = PBXGroup; + children = ( + 0ABD136A237B193A0081388D /* FormView.swift */, + ); + path = views; + sourceTree = ""; + }; D224798823142BF2003FCCF9 /* SwitchMolecules */ = { isa = PBXGroup; children = ( @@ -645,6 +655,7 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( + 0ABD1369237B18EE0081388D /* views */, D29DF2B721E7BE79003B2FB9 /* TabBarController */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */, @@ -1171,6 +1182,7 @@ 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, + 0ABD136B237B193A0081388D /* FormView.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 09ecee72..40972d5a 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -13,28 +13,19 @@ import UIKit } -@objcMembers open class DigitBox: UITextField, MVMCoreViewProtocol { +@objcMembers open class DigitBox: FormView, UITextFieldDelegate { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + let digitField = UITextField(frame: .zero) + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var bottomBar: CAShapeLayer = { - let layer = CAShapeLayer() - layer.backgroundColor = UIColor.black.cgColor - layer.drawsAsynchronously = true - layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); - return layer - }() - private var previousSize: CGFloat = 0.0 - /// Determines if a border should be drawn. - var hideBorder = false - var showError = false - - var borderStrokeColor: UIColor = .mfSilver() - private var borderPath: UIBezierPath = UIBezierPath() - //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -76,8 +67,9 @@ import UIKit translatesAutoresizingMaskIntoConstraints = false backgroundColor = .white - textAlignment = .center - keyboardType = .numberPad + digitField.delegate = self + digitField.textAlignment = .center + digitField.keyboardType = .numberPad showErrorState(false) widthConstraint = widthAnchor.constraint(equalToConstant: 39) @@ -90,30 +82,6 @@ import UIKit updateView(MVMCoreUISplitViewController.getDetailViewWidth()) } - open override func draw(_ rect: CGRect) { - super.draw(rect) - - borderPath.removeAllPoints() - - if !hideBorder { - // Brings the other half of the line inside the view to prevent cropping. - let origin = frame.origin - let size = frame.size - let insetLean: CGFloat = 0.5 - borderPath.lineWidth = 1 - - 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() - } - - layoutIfNeeded() - } - open override func layoutSubviews() { super.layoutSubviews() @@ -125,40 +93,44 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - override open func deleteBackward() { - super.deleteBackward() - - textBoxDelegate?.textFieldDidDelete?(self) + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if string.isBackspace { + textBoxDelegate?.textFieldDidDelete?(self.digitField) + } + + return true } - public func updateView(_ size: CGFloat) { + public override func updateView(_ size: CGFloat) { + super.updateView(size) DispatchQueue.main.async { [weak self] in guard let self = self else { return } if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(self.previousSize)) { - MFStyler.styleTextField(self) - self.font = MFFonts.mfFont55Rg(28) + MFStyler.styleTextField(self.digitField) + self.digitField.font = MFFonts.mfFont55Rg(28) - var digitWidth: CGFloat = 0 - var digitHeight: CGFloat = 0 + var width: CGFloat = 0 + var height: CGFloat = 0 let sizeObject = MFSizeObject(standardBlock: { - digitWidth = 39 - digitHeight = 44 + width = 39 + height = 44 }, smalliPhone: { - digitWidth = 35 - digitHeight = 38 + width = 35 + height = 38 }, standardiPadPortraitBlock: { - digitWidth = 59 - digitHeight = 74 + width = 59 + height = 74 }) sizeObject?.performBlockBase(onSize: size) - self.widthConstraint?.constant = digitWidth - self.heightConstraint?.constant = digitHeight + self.widthConstraint?.constant = width + self.heightConstraint?.constant = height self.previousSize = size } @@ -178,3 +150,12 @@ import UIKit layoutIfNeeded() } } + + +// TODO: Move if working properly. +extension String { + var isBackspace: Bool { + let char = self.cString(using: String.Encoding.utf8)! + return strcmp(char, "\\b") == -92 + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 9417fc27..c455294a 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -23,12 +23,14 @@ import UIKit public override var isEnabled: Bool { didSet { - titleLabel.textColor = isEnabled ? .black : .mfBattleshipGrey() + titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() - for digitBox in digitFields { - digitBox.isUserInteractionEnabled = isEnabled - digitBox.isEnabled = isEnabled - digitBox.textColor = isEnabled ? .black : .mfBattleshipGrey() + digitFields.forEach { + $0.isEnabled = self.isEnabled + + $0.isUserInteractionEnabled = isEnabled + $0.digitField.isEnabled = isEnabled + $0.digitField.textColor = isEnabled ? .black : .mfBattleshipGrey() } } } @@ -37,7 +39,7 @@ import UIKit get { var string = "" - digitFields.forEach { string += $0.attributedPlaceholder?.string ?? "" } + digitFields.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" } return !string.isEmpty ? string : nil } @@ -47,7 +49,7 @@ import UIKit for (index, field) in digitFields.enumerated() { if index < newValue.count { let indexChar = newValue.index(newValue.startIndex, offsetBy: index) - field.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [ + field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [ NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) } } @@ -59,12 +61,6 @@ import UIKit feedback = "" } -// if let feedback = feedback, !feedback.isEmpty { -// feedbackContainerDistance?.constant = 10 -// } else { -// feedbackContainerDistance?.constant = 0 -// } - /* * adding missing accessibilityLabel value * if we have some value in accessibilityLabel, @@ -79,7 +75,7 @@ import UIKit get { var string = "" - digitFields.forEach { string += $0.text ?? "" } + digitFields.forEach { string += $0.digitField.text ?? "" } return string } @@ -89,7 +85,7 @@ import UIKit for (index, field) in digitFields.enumerated() { if index < newValue.count { let indexChar = newValue.index(newValue.startIndex, offsetBy: index) - field.text = String(newValue[indexChar]) + field.digitField.text = String(newValue[indexChar]) } } @@ -97,46 +93,6 @@ import UIKit } } - /// Updates the visual appearance of the container, with some logical laterations as well. - public override func updateUI(appearance: Appearance) { - - self.appearance = appearance - isUserInteractionEnabled = true - titleLabel.textColor = .mfBattleshipGrey() - feedback = showError ? errorMessage : nil - - switch appearance { - case .original: - digitFields.forEach { - $0.borderStrokeColor = .mfSilver() - $0.bottomBar.backgroundColor = UIColor.black.cgColor - } - case .error: - digitFields.forEach { - $0.borderStrokeColor = .mfPumpkin() - $0.bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor - $0.showErrorState(true) - } - case .lock: - digitFields.forEach { - $0.isUserInteractionEnabled = false - $0.bottomBar.backgroundColor = UIColor.clear.cgColor - } - case .select: - digitFields.forEach { - $0.borderStrokeColor = .black - $0.bottomBar.backgroundColor = UIColor.black.cgColor - } - case .disable: - digitFields.forEach { - $0.isUserInteractionEnabled = false - $0.borderStrokeColor = .mfSilver() - $0.bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor - } - titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() - } - } - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -171,13 +127,10 @@ import UIKit public override func setupFieldContainerContent(_ container: UIView) { -// textField.removeFromSuperview() - alignCenterHorizontal() isAccessibilityElement = false - hideBorder = true - bottomBar.backgroundColor = UIColor.clear.cgColor - bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height, width: 0, height: 0) + entryContainer.bottomBar.backgroundColor = UIColor.clear.cgColor + entryContainer.bottomBar.frame = CGRect(x: 0, y: entryContainer.bounds.height, width: 0, height: 0) setupDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) textField.removeFromSuperview() @@ -185,11 +138,11 @@ import UIKit private func createDigitField() -> DigitBox { - let textField = DigitBox() - textField.isAccessibilityElement = true - textField.delegate = self - textField.textBoxDelegate = self - return textField + let digitBox = DigitBox() + digitBox.isAccessibilityElement = true + digitBox.digitField.delegate = self + digitBox.textBoxDelegate = self + return digitBox } func setupDigitFieldsView(size: CGFloat) { @@ -205,18 +158,18 @@ import UIKit for (index, box) in digitFields.enumerated() { accessibleElements.append(box) - fieldContainer.addSubview(box) + entryContainer.addSubview(box) - box.topAnchor.constraint(equalTo: fieldContainer.topAnchor).isActive = true - fieldContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true - box.centerYAnchor.constraint(equalTo: fieldContainer.centerYAnchor).isActive = true + box.topAnchor.constraint(equalTo: entryContainer.topAnchor).isActive = true + entryContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true + box.centerYAnchor.constraint(equalTo: entryContainer.centerYAnchor).isActive = true if index == 0 { - box.leadingAnchor.constraint(equalTo: fieldContainer.leadingAnchor).isActive = true + box.leadingAnchor.constraint(equalTo: entryContainer.leadingAnchor).isActive = true } else if index == digitFields.count - 1 { box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true - fieldContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true + entryContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true } else { box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true @@ -279,7 +232,7 @@ import UIKit guard let self = self else { return } for (index, field) in self.digitFields.enumerated() { - field.isSecureTextEntry = secureEntry + field.digitField.isSecureTextEntry = secureEntry // Accessibility - 33704 fix voice over will read what pin user is filling field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(self.digitFields.count)) @@ -352,8 +305,8 @@ import UIKit } if !dictionary.isEmpty{ - for textField in digitFields { - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegateObject as? UITextFieldDelegate) + for digitBox in digitFields { + MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 9b437901..8cb48268 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -28,10 +28,9 @@ import UIKit return label }() - public private(set) var fieldContainer: UIView = { - let view = UIView(frame: .zero) + public private(set) var entryContainer: FormView = { + let view = FormView() view.isAccessibilityElement = false - view.translatesAutoresizingMaskIntoConstraints = false return view }() @@ -57,23 +56,11 @@ import UIKit public var isValid = false public var fieldKey: String? - /// Determines if a border should be drawn. - var hideBorder = false - - private var borderStrokeColor: UIColor = .mfSilver() - private var borderPath: UIBezierPath = UIBezierPath() - - public var bottomBar: CAShapeLayer = { - let layer = CAShapeLayer() - layer.backgroundColor = UIColor.black.cgColor - layer.drawsAsynchronously = true - layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); - return layer - }() - - public var appearance: Appearance = .original - - public var showError = false + public var showError = false { + didSet { + entryContainer.showError = showError + } + } public var errorMessage: String? @@ -83,7 +70,21 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.updateUI(appearance: self.isEnabled ? .original : .disable) + self.isUserInteractionEnabled = self.isEnabled + self.entryContainer.isEnabled = self.isEnabled + self.feedback = self.showError ? self.errorMessage : nil + self.titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() + } + } + } + + public var isLocked = false { + didSet { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.isUserInteractionEnabled = !self.isLocked + self.entryContainer.lockedUI() } } } @@ -109,7 +110,7 @@ import UIKit set { feedbackLabel.text = newValue setAccessibilityString(newValue) - refreshBorderUI() + entryContainer.refreshUI() } } @@ -129,8 +130,8 @@ import UIKit // MARK: - Constraints //-------------------------------------------------- - public var fieldContainerLeading: NSLayoutConstraint? - public var fieldContainerTrailing: NSLayoutConstraint? + public var entryContainerLeading: NSLayoutConstraint? + public var entryContainerTrailing: NSLayoutConstraint? public var feedbackLabelTrailing: NSLayoutConstraint? public var feedbackLabelLeading: NSLayoutConstraint? @@ -175,6 +176,7 @@ import UIKit guard subviews.isEmpty else { return } translatesAutoresizingMaskIntoConstraints = false + isAccessibilityElement = false setContentCompressionResistancePriority(.required, for: .vertical) accessibilityElements = [titleLabel, feedbackLabel] backgroundColor = .clear @@ -187,19 +189,19 @@ import UIKit titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor) titleLabelLeading?.isActive = true - addSubview(fieldContainer) - setupFieldContainerContent(fieldContainer) + addSubview(entryContainer) + setupFieldContainerContent(entryContainer) - titleContainerDistance = fieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) + titleContainerDistance = entryContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) titleContainerDistance?.isActive = true - fieldContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - fieldContainerLeading?.isActive = true - fieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) - fieldContainerTrailing?.isActive = true + entryContainerLeading = entryContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + entryContainerLeading?.isActive = true + entryContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryContainer.trailingAnchor) + entryContainerTrailing?.isActive = true addSubview(feedbackLabel) - feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne) + feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryContainer.bottomAnchor, constant: PaddingOne) feedbackContainerDistance?.isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true @@ -207,14 +209,12 @@ import UIKit feedbackLabelTrailing?.isActive = true layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true - fieldContainer.layer.addSublayer(bottomBar) - setNeedsDisplay() layoutIfNeeded() } /// Method to override. - /// Intended to add the interactive content (i.e. textField) to the fieldContainer. + /// Intended to add the interactive content (i.e. textField) to the entryContainer. open func setupFieldContainerContent(_ container: UIView) { // To be overridden by subclass. } @@ -224,7 +224,7 @@ import UIKit titleLabel.updateView(size) feedbackLabel.updateView(size) - refreshBorderUI() + entryContainer.updateView(size) } open override func reset() { @@ -233,53 +233,8 @@ import UIKit backgroundColor = .clear titleLabel.reset() feedbackLabel.reset() - fieldContainer.subviews.forEach { $0.removeFromSuperview() } - updateUI(appearance: .original) - } - - open override func layoutSubviews() { - super.layoutSubviews() - - refreshBorderUI(bottomBarSize: 1) - } - - open func refreshBorderUI(bottomBarSize: CGFloat? = nil) { - - let size: CGFloat = appearance == .error ? 4 : 1 - bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size) - - delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) - setNeedsDisplay() - layoutIfNeeded() - } - - //-------------------------------------------------- - // MARK: - Drawing - //-------------------------------------------------- - - open override func draw(_ rect: CGRect) { - super.draw(rect) - - borderPath.removeAllPoints() - - if !hideBorder { - - // Brings the other half of the line inside the view to prevent thinness from cropping. - let origin = fieldContainer.frame.origin - let size = fieldContainer.frame.size - let insetLean: CGFloat = 0.5 - borderPath.lineWidth = 1 - - 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() - } - - layoutIfNeeded() + entryContainer.subviews.forEach { $0.removeFromSuperview() } + titleLabel.textColor = .mfBattleshipGrey() } //-------------------------------------------------- @@ -288,66 +243,17 @@ import UIKit open override func setLeftPinConstant(_ constant: CGFloat) { - fieldContainerLeading?.constant = constant + entryContainerLeading?.constant = constant feedbackLabelLeading?.constant = constant titleLabelLeading?.constant = constant } open override func setRightPinConstant(_ constant: CGFloat) { - fieldContainerTrailing?.constant = constant + entryContainerTrailing?.constant = constant feedbackLabelTrailing?.constant = constant titleLabelTrailing?.constant = constant } - - //-------------------------------------------------- - // MARK: - Form UI Base Appearance - //-------------------------------------------------- - - public enum Appearance: String { - case original - case error - case lock - case select - case disable - } - - /// Updates the visual appearance of the container, with some logical laterations as well. - public func updateUI(appearance: Appearance) { - - self.appearance = appearance - isUserInteractionEnabled = true - titleLabel.textColor = .mfBattleshipGrey() - hideBorder = false - feedback = showError ? errorMessage : nil - - switch appearance { - case .original: - borderStrokeColor = .mfSilver() - bottomBar.backgroundColor = UIColor.black.cgColor - - case .error: - borderStrokeColor = .mfPumpkin() - bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor - - case .lock: - isUserInteractionEnabled = false - hideBorder = true - bottomBar.backgroundColor = UIColor.clear.cgColor - - case .select: - borderStrokeColor = .black - bottomBar.backgroundColor = UIColor.black.cgColor - - case .disable: - isUserInteractionEnabled = false - borderStrokeColor = .mfSilver() - titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() - bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor - } - - refreshBorderUI(bottomBarSize: appearance == .error ? 4 : 1) - } } // MARK: - Molecular @@ -371,12 +277,8 @@ extension EntryField { errorMessage = errMessage } - if let hideBorder = dictionary["hideBorder"] as? Bool { - self.hideBorder = hideBorder - } - - if let appearance = dictionary["appearance"] as? String, let kind = Appearance(rawValue: appearance) { - updateUI(appearance: kind) + if let isLocked = dictionary["isLocked"] as? Bool { + self.isLocked = isLocked } // Key used to send text value to server diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 63d3d0e1..ff243339 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -174,7 +174,7 @@ import UIKit open func clearErrorState() { textField.accessibilityValue = nil - updateUI(appearance: .original) + entryContainer.originalUI() } public func setBothTextDelegates(to delegate: (UITextFieldDelegate & TextFieldDelegate)?) { @@ -224,7 +224,7 @@ import UIKit if isValid { clearErrorState() - bottomBar.backgroundColor = UIColor.black.cgColor + entryContainer.bottomBar.backgroundColor = UIColor.black.cgColor } else if let errMessage = errorMessage { feedback = errMessage @@ -233,10 +233,7 @@ import UIKit @objc func startEditing() { - if appearance != .original { - updateUI(appearance: .original) - } - + entryContainer.originalUI() textField.becomeFirstResponder() } } diff --git a/MVMCoreUI/BaseClasses/View.swift b/MVMCoreUI/BaseClasses/View.swift index bc6e7634..8ae475b9 100644 --- a/MVMCoreUI/BaseClasses/View.swift +++ b/MVMCoreUI/BaseClasses/View.swift @@ -9,18 +9,25 @@ import UIKit @objcMembers open class View: UIView { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + open var json: [AnyHashable: Any]? private var initialSetupPerformed = false + //-------------------------------------------------- + // MARK: - Initialization + //-------------------------------------------------- + public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } - public init() { - super.init(frame: .zero) - initialSetup() + public convenience init() { + self.init(frame: .zero) } public required init?(coder: NSCoder) { @@ -29,6 +36,7 @@ import UIKit } public func initialSetup() { + if !initialSetupPerformed { initialSetupPerformed = true setupView() @@ -36,7 +44,9 @@ import UIKit } } +/// MARK:- MVMCoreViewProtocol extension View: MVMCoreViewProtocol { + open func updateView(_ size: CGFloat) {} /// Will be called only once. @@ -46,8 +56,10 @@ extension View: MVMCoreViewProtocol { } } +/// MARK:- MVMCoreUIMoleculeViewProtocol extension View: MVMCoreUIMoleculeViewProtocol { - open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + + open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormView.swift new file mode 100644 index 00000000..3e09bb12 --- /dev/null +++ b/MVMCoreUI/Containers/views/FormView.swift @@ -0,0 +1,171 @@ +// +// FormView.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class FormView: View { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var bottomBar: CAShapeLayer = { + let layer = CAShapeLayer() + layer.backgroundColor = UIColor.black.cgColor + layer.drawsAsynchronously = true + layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); + return layer + }() + + public var borderStrokeColor: UIColor = .mfSilver() + public var borderPath: UIBezierPath = UIBezierPath() + + /// Determines if a border should be drawn. + public var hideBorder = false + public var showError = false { + didSet { + _ = showError ? errorUI() : originalUI() + } + } + + public var isEnabled = true { + didSet { + isEnabled ? originalUI() : disabledUI() + } + } + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + weak var delegateObject: MVMCoreUIDelegateObject? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func layoutSubviews() { + super.layoutSubviews() + + refreshUI(bottomBarSize: showError ? 4 : 1) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + refreshUI() + } + + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath.removeAllPoints() + + if !hideBorder { + // Brings the other half of the line inside the view to prevent cropping. + let origin = frame.origin + let size = frame.size + let insetLean: CGFloat = 0.5 + borderPath.lineWidth = 1 + + 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() + } + + layoutIfNeeded() + } + + override open func setupView() { + super.setupView() + + layer.addSublayer(bottomBar) + } + + //-------------------------------------------------- + // MARK: - Draw States + //-------------------------------------------------- + + open func originalUI() { + + isUserInteractionEnabled = true + hideBorder = false + borderStrokeColor = .mfSilver() + bottomBar.backgroundColor = UIColor.black.cgColor + + refreshUI(bottomBarSize: 1) + } + + open func errorUI() { + + isUserInteractionEnabled = true + hideBorder = false + borderStrokeColor = .mfPumpkin() + bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor + + refreshUI(bottomBarSize: 4) + } + + open func selectedUI() { + + isUserInteractionEnabled = true + hideBorder = false + borderStrokeColor = .black + bottomBar.backgroundColor = UIColor.black.cgColor + + refreshUI(bottomBarSize: 1) + } + + open func lockedUI() { + + isUserInteractionEnabled = false + hideBorder = true + bottomBar.backgroundColor = UIColor.clear.cgColor + + refreshUI(bottomBarSize: 1) + } + + open func disabledUI() { + + isUserInteractionEnabled = false + borderStrokeColor = .mfSilver() + hideBorder = false + bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor + + refreshUI(bottomBarSize: 1) + } + + open func refreshUI(bottomBarSize: CGFloat? = nil) { + + let size: CGFloat = showError ? 4 : 1 + bottomBar.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) + + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + setNeedsDisplay() + layoutIfNeeded() + } + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + + guard let dictionary = json, !dictionary.isEmpty else { return } + + if let hideBorder = dictionary["hideBorder"] as? Bool { + self.hideBorder = hideBorder + } + } +} From d6005290db334fcdb79bef3d5066048bdfbaf9b3 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 12 Nov 2019 14:56:22 -0500 Subject: [PATCH 046/112] improvements made. --- MVMCoreUI/Atoms/TextFields/EntryField.swift | 9 +++++++++ MVMCoreUI/Containers/views/FormView.swift | 1 + 2 files changed, 10 insertions(+) diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 8cb48268..86dad0f6 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -213,6 +213,13 @@ import UIKit layoutIfNeeded() } + open override func layoutSubviews() { + super.layoutSubviews() + + entryContainer.refreshUI() + setNeedsLayout() + } + /// Method to override. /// Intended to add the interactive content (i.e. textField) to the entryContainer. open func setupFieldContainerContent(_ container: UIView) { @@ -265,6 +272,8 @@ extension EntryField { guard let dictionary = json, !dictionary.isEmpty else { return } + entryContainer.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) + if let titleText = dictionary["title"] as? String { title = titleText } diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormView.swift index 3e09bb12..1897766d 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormView.swift @@ -88,6 +88,7 @@ import UIKit override open func setupView() { super.setupView() + isOpaque = false layer.addSublayer(bottomBar) } From 926c2c4cf30621d18f3881fd7ea68239e379f2ba Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 12 Nov 2019 15:05:23 -0500 Subject: [PATCH 047/112] resolved drawing prob. --- MVMCoreUI/Atoms/TextFields/EntryField.swift | 4 ---- MVMCoreUI/Containers/views/FormView.swift | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 86dad0f6..9c344521 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -208,16 +208,12 @@ import UIKit feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) feedbackLabelTrailing?.isActive = true layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true - - setNeedsDisplay() - layoutIfNeeded() } open override func layoutSubviews() { super.layoutSubviews() entryContainer.refreshUI() - setNeedsLayout() } /// Method to override. diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormView.swift index 1897766d..058f2a25 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormView.swift @@ -68,7 +68,7 @@ import UIKit if !hideBorder { // Brings the other half of the line inside the view to prevent cropping. - let origin = frame.origin + let origin = bounds.origin let size = frame.size let insetLean: CGFloat = 0.5 borderPath.lineWidth = 1 @@ -81,8 +81,6 @@ import UIKit borderStrokeColor.setStroke() borderPath.stroke() } - - layoutIfNeeded() } override open func setupView() { From 87b0316d4777a032d7b0e05e3913c82b6e50f5f8 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 13 Nov 2019 09:23:58 -0500 Subject: [PATCH 048/112] save. --- MVMCoreUI/Atoms/TextFields/EntryField.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 9c344521..af9034d1 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -10,7 +10,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. + * The EntryForm provides the base logic for the description label, placeholder/error label and field container. * * When subclassing, be sure to override setupFieldContainerContent(). In this method you will setup all the content bound to the field container. */ @@ -56,6 +56,7 @@ import UIKit public var isValid = false public var fieldKey: String? + /// Toggles error or original UI. public var showError = false { didSet { entryContainer.showError = showError @@ -64,7 +65,7 @@ import UIKit public var errorMessage: String? - /// Toggles the enables state of this component. + /// Toggles original or disabled UI. public var isEnabled = true { didSet { DispatchQueue.main.async { [weak self] in @@ -78,6 +79,7 @@ import UIKit } } + // Toggles original or locked UI. public var isLocked = false { didSet { DispatchQueue.main.async { [weak self] in From fca540afc2fa45501079d026eacd15b2653ee316 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 13 Nov 2019 16:17:10 -0500 Subject: [PATCH 049/112] shaping up to be more responsive. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 3 +- .../Atoms/TextFields/DigitEntryField.swift | 4 +++ .../Atoms/TextFields/DropdownEntryField.swift | 8 +++-- MVMCoreUI/Atoms/TextFields/EntryField.swift | 27 ++++++++++++++--- .../Atoms/TextFields/MdnEntryField.swift | 4 +++ .../Atoms/TextFields/TextEntryField.swift | 17 +++++++++-- MVMCoreUI/Containers/views/FormView.swift | 30 +++++++++++++++---- 7 files changed, 78 insertions(+), 15 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 40972d5a..95135f69 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -142,9 +142,10 @@ import UIKit showError = show borderStrokeColor = show ? .mfPumpkin() : .mfSilver() - bottomBar.backgroundColor = show ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor + let barHeight: CGFloat = show ? 4 : 1 bottomBar.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) + bottomBar.backgroundColor = show ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor setNeedsDisplay() layoutIfNeeded() diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index c455294a..8a46a994 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -21,6 +21,10 @@ import UIKit public var digitFields: [DigitBox] = [] + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + public override var isEnabled: Bool { didSet { titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 7358d9f6..951b0a1f 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -37,6 +37,10 @@ import UIKit public var dropDownIsDisplayed = false + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + public override var isEnabled: Bool { didSet { showDropDown(isEnabled) } } @@ -212,8 +216,8 @@ extension DropdownEntryField { var accessibilityString = accessibilityString ?? "" - if dropDownIsDisplayed, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { - accessibilityString += txtPickerItem + if dropDownIsDisplayed, let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { + accessibilityString += textPickerItem } else if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") { accessibilityString += txtRegular diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index af9034d1..64cde29c 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -56,15 +56,24 @@ import UIKit public var isValid = false public var fieldKey: String? + public var errorMessage: String? + + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + /// Toggles error or original UI. public var showError = false { didSet { - entryContainer.showError = showError + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.entryContainer.showError = self.showError + self.feedback = self.errorMessage + } } } - public var errorMessage: String? - /// Toggles original or disabled UI. public var isEnabled = true { didSet { @@ -86,7 +95,17 @@ import UIKit guard let self = self else { return } self.isUserInteractionEnabled = !self.isLocked - self.entryContainer.lockedUI() + self.entryContainer.isLocked = self.isLocked + } + } + } + + public var isSelected = false { + didSet { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.entryContainer.isSelected = self.isSelected } } } diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index ab4155bd..a9c76e83 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -22,6 +22,10 @@ import MVMCore private weak var outsiderTextDelegate: UITextFieldDelegate? + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + public var mdn: String? { get { return MVMCoreUIUtility.removeMdnFormat(text) } set { text = MVMCoreUIUtility.formatMdn(newValue) } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index ff243339..ae2ff63f 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -40,10 +40,15 @@ import UIKit // MARK: - Properties //-------------------------------------------------- - public var textColor: (enabled: UIColor?, disabled: UIColor?) + /// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property. + public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.black, .mfSilver()) public var observingForChange = false + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + public override var isEnabled: Bool { didSet { DispatchQueue.main.async { [weak self] in @@ -91,6 +96,7 @@ import UIKit 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 && observingForChange { observingForChange = false NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: textField) @@ -190,9 +196,16 @@ import UIKit } } - @objc func dismissTextFieldResponder(_ sender: Any?) { + override open func resignFirstResponder() -> Bool { textField.resignFirstResponder() + + return true + } + + @objc func dismissTextFieldResponder(_ sender: Any?) { + + _ = textField.resignFirstResponder() } //-------------------------------------------------- diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormView.swift index 058f2a25..a89f2c8f 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormView.swift @@ -11,7 +11,7 @@ import UIKit @objcMembers open class FormView: View { //-------------------------------------------------- - // MARK: - Properties + // MARK: - Drawing Properties //-------------------------------------------------- public var bottomBar: CAShapeLayer = { @@ -22,14 +22,19 @@ import UIKit return layer }() - public var borderStrokeColor: UIColor = .mfSilver() - public var borderPath: UIBezierPath = UIBezierPath() - /// Determines if a border should be drawn. public var hideBorder = false + + public var borderStrokeColor: UIColor = .mfSilver() + private var borderPath: UIBezierPath = UIBezierPath() + + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + public var showError = false { didSet { - _ = showError ? errorUI() : originalUI() + showError ? errorUI() : originalUI() } } @@ -39,10 +44,23 @@ import UIKit } } + public var isLocked = false { + didSet { + isLocked ? lockedUI() : originalUI() + } + } + + public var isSelected = false { + didSet { + isSelected ? selectedUI() : originalUI() + } + } + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- + /// Holds reference to delegateObject to inform molecular tableView of an update. weak var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- @@ -145,7 +163,7 @@ import UIKit open func refreshUI(bottomBarSize: CGFloat? = nil) { - let size: CGFloat = showError ? 4 : 1 + let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) bottomBar.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) From 80c700ba552ddc233a8d1a97626efb33639fd04b Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 14 Nov 2019 11:41:02 -0500 Subject: [PATCH 050/112] Further improvements made to dropdaown. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++ .../TextFields/DateDropdownEntryField.swift | 107 ++++++++++++++ .../Atoms/TextFields/DigitEntryField.swift | 53 ++++--- .../Atoms/TextFields/DropdownEntryField.swift | 130 +----------------- MVMCoreUI/Atoms/TextFields/EntryField.swift | 12 -- .../TextFields/ItemDropdownEntryField.swift | 62 +++++++++ MVMCoreUI/Atoms/Views/CaretView.swift | 6 +- 7 files changed, 220 insertions(+), 158 deletions(-) create mode 100644 MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift create mode 100644 MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index fcbd1272..b54ad255 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -46,6 +46,8 @@ 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 0ABD136B237B193A0081388D /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormView.swift */; }; + 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; + 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; @@ -239,6 +241,8 @@ 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; 0ABD136A237B193A0081388D /* FormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = ""; }; + 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; + 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; @@ -804,6 +808,8 @@ 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */, + 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */, + 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */, ); path = TextFields; sourceTree = ""; @@ -1115,6 +1121,7 @@ D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, + 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, @@ -1148,6 +1155,7 @@ D29DF29521E7ADB8003B2FB9 /* ProgrammaticScrollViewController.m in Sources */, D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */, D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, + 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift new file mode 100644 index 00000000..e2f5b9f2 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -0,0 +1,107 @@ +// +// DateDropdownEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +class DateDropdownEntryField: DropdownEntryField { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public weak var datePicker: UIDatePicker? + + private var calendar: Calendar = { + var calendar: Calendar = .current + calendar.timeZone = NSTimeZone.system + return calendar + }() + + // TODO: Pull this out into Styler or some class akin to it. + public var formatter: DateFormatter = { + + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = NSTimeZone.system + formatter.locale = .current + formatter.formatterBehavior = .default + + return formatter + }() + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: frame) + + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + datePicker?.timeZone = NSTimeZone.system + } + + public convenience init() { + self.init(frame: .zero) + } + + public convenience init(startDate: Date, endDate: Date, showStartDate: Bool = true) { + self.init(frame: .zero) + + setDatePickerDuration(from: startDate, to: endDate, showStartDate: showStartDate) + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("DateDropdownEntryField does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + public func setDatePickerDuration(from startDate: Date?, to endDate: Date?, showStartDate: Bool = true) { + + datePicker?.minimumDate = startDate + datePicker?.maximumDate = endDate + + if showStartDate { + setTextWith(date: startDate) + } + } + + public func dismissDatePicker() -> Date? { + + let pickedDate = datePicker?.date + setTextWith(date: pickedDate) + + textField.resignFirstResponder() + return pickedDate + } + + private func setTextWith(date: Date?) { + + guard let date = date else { return } + + if calendar.isDate(date, inSameDayAs: Date()) { + text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") + } else { + text = formatter.string(from: date) + } + } +} + +// MARK: - Molecular +extension DateDropdownEntryField { + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json, !dictionary.isEmpty else { return } + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 8a46a994..fe8340e0 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -294,31 +294,17 @@ import UIKit } } - //-------------------------------------------------- - // MARK: - Molecule - //-------------------------------------------------- - - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + class func getEnabledTextfields(_ textFieldToDetermine: [TextEntryField]?) -> [AnyHashable]? { - guard let dictionary = json else { return } + var enabledTextFields = [AnyHashable]() - let digits = dictionary["digits"] as? Int ?? 4 - if digits != numberOfDigits { - numberOfDigits = digits - } - - if !dictionary.isEmpty{ - for digitBox in digitFields { - MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) + for textfield in textFieldToDetermine ?? [] { + if textfield.isEnabled { + enabledTextFields.append(textfield) } } - setupDigitFieldsView(size: MVMCoreUIUtility.getWidth()) - } - - open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 44 + return enabledTextFields } //-------------------------------------------------- @@ -409,3 +395,30 @@ import UIKit return uiTextFieldDelegate?.textFieldShouldEndEditing?(textField) ?? true } } + +// MARK: - Molecular +extension DigitEntryField { + + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json else { return } + + let digits = dictionary["digits"] as? Int ?? 4 + if digits != numberOfDigits { + numberOfDigits = digits + } + + if !dictionary.isEmpty{ + for digitBox in digitFields { + MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) + } + } + + setupDigitFieldsView(size: MVMCoreUIUtility.getWidth()) + } + + open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 44 + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 951b0a1f..c36834c5 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -24,33 +24,18 @@ import UIKit return caret }() - //-------------------------------------------------- - // MARK: - Accessories - //-------------------------------------------------- - public weak var datePicker: UIDatePicker? - private var calendar: Calendar? - - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - public var dropDownIsDisplayed = false //-------------------------------------------------- // MARK: - Property Observers //-------------------------------------------------- public override var isEnabled: Bool { - didSet { showDropDown(isEnabled) } + didSet { + dropDownCaretView.isEnabled = isEnabled + } } - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - public var dropDownCaretWidth: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -63,14 +48,6 @@ import UIKit self.init(frame: .zero) } - /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - super.init(frame: .zero) - - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: bothDelegates) - setBothTextDelegates(to: bothDelegates) - } - required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("DropdownEntryField does not support xib.") @@ -89,6 +66,7 @@ import UIKit dropDownCaretView.topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor, constant: 13).isActive = true textFieldTrailingConstraint = dropDownCaretView.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) textFieldTrailingConstraint?.isActive = true + container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true @@ -97,103 +75,8 @@ import UIKit let caretTap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) dropDownCaretView.addGestureRecognizer(caretTap) } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - public func showDropDown(_ show: Bool) { - - dropDownCaretView.isHidden = !show - dropDownCaretWidth?.isActive = !show - setNeedsLayout() - layoutIfNeeded() - } - - //-------------------------------------------------- - // MARK: - Observing for change - //-------------------------------------------------- - - override func startEditing() { - super.startEditing() - - showDropDown(!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 DropdownEntryField { - - private func createDatePicker() { - - 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 { - 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 - } -} // MARK: - Molecular extension DropdownEntryField { @@ -216,11 +99,8 @@ extension DropdownEntryField { var accessibilityString = accessibilityString ?? "" - if dropDownIsDisplayed, let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { + if let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { accessibilityString += textPickerItem - - } else if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") { - accessibilityString += txtRegular } textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 64cde29c..63c506cc 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -135,18 +135,6 @@ import UIKit } } - // TODO: Pull this out into Styler or some class akin to it. - public var formatter: DateFormatter = { - - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeZone = NSTimeZone.system - formatter.locale = .current - formatter.formatterBehavior = .default - - return formatter - }() - //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift new file mode 100644 index 00000000..44982d4d --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -0,0 +1,62 @@ +// +// ItemDropdownEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/14/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class ItemDropdownEntryField: DropdownEntryField { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var pickerData: [String] = [] + var pickerView: UIPickerView? + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public convenience init() { + self.init(frame: .zero) + } + + public convenience init(pickerData: [String]) { + self.init(frame: .zero) + + pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) + } +} + +// MARK:- Base Picker Delegate +extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { + + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return pickerData.count + } + + public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return pickerData[row] + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + textField.text = pickerData[row] + } +} + +// MARK: - Molecular +extension ItemDropdownEntryField { + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json, !dictionary.isEmpty else { return } + } +} diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 70056219..aa1ad8e3 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -22,6 +22,10 @@ public var enabledColor: UIColor = .black public var disabledColor: UIColor = .mfSilver() + //------------------------------------------------------ + // MARK: - Property Observer + //------------------------------------------------------ + public var isEnabled: Bool = true { didSet { strokeColor = isEnabled ? enabledColor : disabledColor @@ -39,7 +43,7 @@ case medium(Orientation) case large(Orientation) - /// Orientation based on the longest line of the view + /// Orientation based on the longest line of the view. public enum Orientation { case vertical case horizontal From ebccc6bd28b01476b0d8d04b1bfc4282cebc3f31 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 14 Nov 2019 14:59:39 -0500 Subject: [PATCH 051/112] The latest state of entry. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 9 +++-- .../Atoms/TextFields/DigitEntryField.swift | 2 +- .../Atoms/TextFields/DropdownEntryField.swift | 2 -- MVMCoreUI/Atoms/TextFields/EntryField.swift | 12 ++++--- .../TextFields/ItemDropdownEntryField.swift | 1 + .../Atoms/TextFields/MdnEntryField.swift | 28 +++++++++------- .../Atoms/TextFields/TextEntryField.swift | 33 +++++++++---------- MVMCoreUI/Containers/views/FormView.swift | 14 ++++---- .../FormValidator+TextFields.swift | 2 +- 9 files changed, 56 insertions(+), 47 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 95135f69..1c1776d8 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -18,7 +18,12 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - let digitField = UITextField(frame: .zero) + let digitField: UITextField = { + let textField = UITextField(frame: .zero) + textField.textAlignment = .center + textField.keyboardType = .numberPad + return textField + }() //-------------------------------------------------- // MARK: - Properties @@ -68,8 +73,6 @@ import UIKit translatesAutoresizingMaskIntoConstraints = false backgroundColor = .white digitField.delegate = self - digitField.textAlignment = .center - digitField.keyboardType = .numberPad showErrorState(false) widthConstraint = widthAnchor.constraint(equalToConstant: 39) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index fe8340e0..f8a2ccbf 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -115,7 +115,7 @@ import UIKit self.numberOfDigits = numberOfDigits } - public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?, size: CGFloat? = nil) { + public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?, size: CGFloat? = nil) { self.numberOfDigits = numberOfDigits super.init(bothDelegates: delegate) } diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index c36834c5..2bb89424 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -24,8 +24,6 @@ import UIKit return caret }() - - //-------------------------------------------------- // MARK: - Property Observers //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 63c506cc..d72d4be7 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -9,7 +9,6 @@ import UIKit /** - * This class is intended to be subclassed by a class that will add views subclassed under UIControl. * The EntryForm provides the base logic for the description label, placeholder/error label and field container. * * When subclassing, be sure to override setupFieldContainerContent(). In this method you will setup all the content bound to the field container. @@ -74,7 +73,7 @@ import UIKit } } - /// Toggles original or disabled UI. + /// Toggles enabled (original) or disabled UI. public var isEnabled = true { didSet { DispatchQueue.main.async { [weak self] in @@ -82,13 +81,13 @@ import UIKit self.isUserInteractionEnabled = self.isEnabled self.entryContainer.isEnabled = self.isEnabled - self.feedback = self.showError ? self.errorMessage : nil + self.feedbackLabel.textColor = self.isEnabled ? .black : .mfSilver() self.titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() } } } - // Toggles original or locked UI. + /// Toggles original or locked UI. public var isLocked = false { didSet { DispatchQueue.main.async { [weak self] in @@ -100,6 +99,7 @@ import UIKit } } + /// Toggles selected or original (unselected) UI. public var isSelected = false { didSet { DispatchQueue.main.async { [weak self] in @@ -110,6 +110,10 @@ import UIKit } } + //-------------------------------------------------- + // MARK: - Computed Properties for Outlets + //-------------------------------------------------- + /// Sets the text of titleLabel public var title: String? { get { return titleLabel.text } diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 44982d4d..41efd185 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -8,6 +8,7 @@ import UIKit + class ItemDropdownEntryField: DropdownEntryField { //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index a9c76e83..26a5266f 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -11,7 +11,9 @@ import ContactsUI import UIKit import MVMCore - +/** + This class provides the convenience of formatting the MDN entered/displayer for the user. + */ @objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { //-------------------------------------------------- // MARK: - Properties @@ -20,12 +22,18 @@ import MVMCore public var isNationalMDN = true public var shouldValidateMDN = false + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. private weak var outsiderTextDelegate: UITextFieldDelegate? //-------------------------------------------------- // MARK: - Property Observers //-------------------------------------------------- + /// Formats the MDN when setting and removes format of MDN when reading. public var mdn: String? { get { return MVMCoreUIUtility.removeMdnFormat(text) } set { text = MVMCoreUIUtility.formatMdn(newValue) } @@ -35,11 +43,8 @@ import MVMCore public override weak var uiTextFieldDelegate: UITextFieldDelegate? { get { return textField.delegate } set { - textField.delegate = newValue + textField.delegate = self outsiderTextDelegate = newValue - if newValue != nil { - textField.delegate = self - } } } @@ -56,10 +61,10 @@ import MVMCore } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public override init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - super.init(frame: .zero) - setBothTextDelegates(to: bothDelegates) - } + public override init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + super.init(frame: .zero) + setBothTextDelegates(to: bothDelegates) + } required public init?(coder: NSCoder) { super.init(coder: coder) @@ -78,7 +83,7 @@ import MVMCore 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(dismissTextFieldResponder(_:))) + let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissField(_:))) toolbar.items = [contacts, space, dismissButton] textField.inputAccessoryView = toolbar } @@ -143,7 +148,8 @@ import MVMCore unformedMDN.count == 11, unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" { - unformattedMDN = (unformedMDN as NSString).substring(from: 1) + let startIndex = unformedMDN.index(unformedMDN.startIndex, offsetBy: 1) + unformattedMDN = String(unformedMDN[startIndex...]) } text = unformattedMDN diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index ae2ff63f..8d4aa4aa 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -9,7 +9,7 @@ import UIKit -@objc public protocol TextFieldDelegate: NSObjectProtocol { +@objc public protocol ObservingTextFieldDelegate: NSObjectProtocol { /// Called when the entered text becomes valid based on the validation block @objc optional func isValid(textfield: TextEntryField?) /// Called when the entered text becomes invalid based on the validation block @@ -69,6 +69,7 @@ import UIKit } } + /// Placeholder access for the TextField. public var placeholder: String? { get { return textField.placeholder } set { textField.placeholder = newValue } @@ -89,15 +90,15 @@ import UIKit //-------------------------------------------------- /// 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? { + public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? { didSet { - if mfTextFieldDelegate != nil && !observingForChange { + if observingTextFieldDelegate != nil && !observingForChange { observingForChange = 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 && observingForChange { + } else if observingTextFieldDelegate == nil && observingForChange { observingForChange = false NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: textField) NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: textField) @@ -131,7 +132,7 @@ import UIKit } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + public init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { super.init(frame: .zero) setBothTextDelegates(to: bothDelegates) } @@ -180,12 +181,13 @@ import UIKit open func clearErrorState() { textField.accessibilityValue = nil + feedback = nil entryContainer.originalUI() } - public func setBothTextDelegates(to delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - mfTextFieldDelegate = delegate + observingTextFieldDelegate = delegate uiTextFieldDelegate = delegate } @@ -199,17 +201,16 @@ import UIKit override open func resignFirstResponder() -> Bool { textField.resignFirstResponder() - return true } - @objc func dismissTextFieldResponder(_ sender: Any?) { + @objc func dismissField(_ sender: Any?) { - _ = textField.resignFirstResponder() + _ = self.resignFirstResponder() } //-------------------------------------------------- - // MARK: - Observing for change + // MARK: - Observing for Change (TextFieldDelegate) //-------------------------------------------------- @objc func valueChanged() { @@ -225,11 +226,11 @@ import UIKit if previousValidity && !isValid { feedback = errorMessage - mfTextFieldDelegate?.isInvalid?(textfield: self) + observingTextFieldDelegate?.isInvalid?(textfield: self) } else if !previousValidity && isValid { clearErrorState() - mfTextFieldDelegate?.isValid?(textfield: self) + observingTextFieldDelegate?.isValid?(textfield: self) } } @@ -258,8 +259,7 @@ extension TextEntryField { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let delegateObject = delegateObject, - let dictionary = json, - !dictionary.isEmpty + let dictionary = json else { return } FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol) @@ -306,11 +306,10 @@ extension TextEntryField { } if let formValidationProtocol = delegateObject.formValidationProtocol { - mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) + observingTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) as? ObservingTextFieldDelegate } uiTextFieldDelegate = delegateObject.uiTextFieldDelegate - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) } } diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormView.swift index a89f2c8f..201bfb3d 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormView.swift @@ -14,6 +14,7 @@ import UIKit // MARK: - Drawing Properties //-------------------------------------------------- + /// The bottom border line. public var bottomBar: CAShapeLayer = { let layer = CAShapeLayer() layer.backgroundColor = UIColor.black.cgColor @@ -62,7 +63,7 @@ import UIKit /// Holds reference to delegateObject to inform molecular tableView of an update. weak var delegateObject: MVMCoreUIDelegateObject? - + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -79,6 +80,7 @@ import UIKit refreshUI() } + /// This handles the top, left, and right border lines. open override func draw(_ rect: CGRect) { super.draw(rect) @@ -111,14 +113,13 @@ import UIKit //-------------------------------------------------- // MARK: - Draw States //-------------------------------------------------- - + open func originalUI() { isUserInteractionEnabled = true hideBorder = false borderStrokeColor = .mfSilver() bottomBar.backgroundColor = UIColor.black.cgColor - refreshUI(bottomBarSize: 1) } @@ -128,7 +129,6 @@ import UIKit hideBorder = false borderStrokeColor = .mfPumpkin() bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor - refreshUI(bottomBarSize: 4) } @@ -138,7 +138,6 @@ import UIKit hideBorder = false borderStrokeColor = .black bottomBar.backgroundColor = UIColor.black.cgColor - refreshUI(bottomBarSize: 1) } @@ -146,8 +145,8 @@ import UIKit isUserInteractionEnabled = false hideBorder = true + borderStrokeColor = .clear bottomBar.backgroundColor = UIColor.clear.cgColor - refreshUI(bottomBarSize: 1) } @@ -156,8 +155,7 @@ import UIKit isUserInteractionEnabled = false borderStrokeColor = .mfSilver() hideBorder = false - bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor - + bottomBar.backgroundColor = UIColor.mfSilver().cgColor refreshUI(bottomBarSize: 1) } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift index e8d79245..21599abf 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift @@ -39,7 +39,7 @@ import Foundation } // Temporary: Looking to either combine or separate entirely with MFTextFieldDelegate. -extension FormValidator: TextFieldDelegate { +extension FormValidator: ObservingTextFieldDelegate { public func dismissField(_ sender: Any?) { From 92b988d9fb1296b0883da48f071532cb25826544 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 Nov 2019 08:52:29 -0500 Subject: [PATCH 052/112] setting up dropdown. --- .../TextFields/DateDropdownEntryField.swift | 2 +- .../Atoms/TextFields/DropdownEntryField.swift | 9 ++++----- MVMCoreUI/Atoms/TextFields/EntryField.swift | 2 +- .../TextFields/ItemDropdownEntryField.swift | 17 ++++++++++++++--- MVMCoreUI/Atoms/TextFields/MdnEntryField.swift | 3 ++- MVMCoreUI/Atoms/TextFields/TextEntryField.swift | 6 +++--- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index e2f5b9f2..965c5495 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -9,7 +9,7 @@ import UIKit -class DateDropdownEntryField: DropdownEntryField { +open class DateDropdownEntryField: DropdownEntryField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 2bb89424..04f8dacb 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -8,7 +8,10 @@ import UIKit - +/** + This class is intended to be subclassed. + See ItemDropdownEntryField and DateDropdownEntryField. + */ @objcMembers open class DropdownEntryField: TextEntryField { //-------------------------------------------------- // MARK: - Outlets @@ -42,10 +45,6 @@ import UIKit super.init(frame: frame) } - public convenience init() { - self.init(frame: .zero) - } - required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("DropdownEntryField does not support xib.") diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index d72d4be7..65e4e44e 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -68,7 +68,7 @@ import UIKit guard let self = self else { return } self.entryContainer.showError = self.showError - self.feedback = self.errorMessage + self.feedback = self.showError ? self.errorMessage : nil } } } diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 41efd185..12c5cedd 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -9,13 +9,13 @@ import UIKit -class ItemDropdownEntryField: DropdownEntryField { +open class ItemDropdownEntryField: DropdownEntryField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - var pickerData: [String] = [] - var pickerView: UIPickerView? + public var pickerData: [String] = [] + public var pickerView: UIPickerView? //-------------------------------------------------- // MARK: - Initializer @@ -28,8 +28,19 @@ class ItemDropdownEntryField: DropdownEntryField { public convenience init(pickerData: [String]) { self.init(frame: .zero) + self.pickerData = pickerData pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + public func setPickerDelegate(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { + + pickerView?.delegate = delegate + pickerView?.dataSource = delegate + } } // MARK:- Base Picker Delegate diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 26a5266f..7f7bbeb3 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -83,7 +83,7 @@ import MVMCore 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(dismissField(_:))) + let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFieldInput(_:))) toolbar.items = [contacts, space, dismissButton] textField.inputAccessoryView = toolbar } @@ -112,6 +112,7 @@ import MVMCore clearErrorState() } else { errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") + showError = true UIAccessibility.post(notification: .layoutChanged, argument: textField) } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 8d4aa4aa..49d9b1e3 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -15,7 +15,7 @@ import UIKit /// Called when the entered text becomes invalid based on the validation block @objc optional func isInvalid(textfield: TextEntryField?) /// Dismisses the keyboard. - @objc optional func dismissField(sender: Any?) + @objc optional func dismissFieldInput(sender: Any?) } @@ -182,7 +182,7 @@ import UIKit textField.accessibilityValue = nil feedback = nil - entryContainer.originalUI() + showError = false } public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { @@ -204,7 +204,7 @@ import UIKit return true } - @objc func dismissField(_ sender: Any?) { + @objc func dismissFieldInput(_ sender: Any?) { _ = self.resignFirstResponder() } From 588f801322463581e5c8e6c4cf27d0d73fdf6652 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 Nov 2019 14:18:36 -0500 Subject: [PATCH 053/112] Adding Base TextField. More improvements. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../TextFields/DateDropdownEntryField.swift | 2 +- .../Atoms/TextFields/DropdownEntryField.swift | 16 ---- .../TextFields/ItemDropdownEntryField.swift | 69 ++++++++++++-- .../Atoms/TextFields/TextEntryField.swift | 8 +- MVMCoreUI/BaseClasses/TextField.swift | 93 +++++++++++++++++++ 6 files changed, 165 insertions(+), 27 deletions(-) create mode 100644 MVMCoreUI/BaseClasses/TextField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index b54ad255..6f4709e4 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 0ABD136B237B193A0081388D /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormView.swift */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; + 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; @@ -243,6 +244,7 @@ 0ABD136A237B193A0081388D /* FormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; + 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; @@ -915,6 +917,7 @@ children = ( D2B18B7E2360913400A9AEDC /* Control.swift */, D2B18B802360945C00A9AEDC /* View.swift */, + 0AE14F63238315D2005417F8 /* TextField.swift */, ); path = BaseClasses; sourceTree = ""; @@ -1162,6 +1165,7 @@ D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */, D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, + 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 965c5495..97bb8740 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -41,9 +41,9 @@ open class DateDropdownEntryField: DropdownEntryField { public override init(frame: CGRect) { super.init(frame: frame) - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) datePicker?.timeZone = NSTimeZone.system + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) } public convenience init() { diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 04f8dacb..a9d5d693 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -74,7 +74,6 @@ import UIKit } } - // MARK: - Molecular extension DropdownEntryField { @@ -88,18 +87,3 @@ extension DropdownEntryField { } } } - -// MARK: - Accessibility -extension DropdownEntryField { - - open override func setAccessibilityString(_ accessibilityString: String?) { - - var accessibilityString = accessibilityString ?? "" - - if let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { - accessibilityString += textPickerItem - } - - textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" - } -} diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 12c5cedd..33345a55 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -17,19 +17,38 @@ open class ItemDropdownEntryField: DropdownEntryField { public var pickerData: [String] = [] public var pickerView: UIPickerView? + public var componentsCount = 1 + + /// When selecting first responder, allow initial selected value to appear in empty text field. + public var setInitialValueInTextField = true + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + private weak var outsiderTextDelegate: UITextFieldDelegate? + + /// If you're using a MFViewController, you must set this to it + public override weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { return textField.delegate } + set { + textField.delegate = self + outsiderTextDelegate = newValue + } + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public convenience init() { - self.init(frame: .zero) - } - public convenience init(pickerData: [String]) { self.init(frame: .zero) self.pickerData = pickerData pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) + textField.hideBlinkingCaret = true + uiTextFieldDelegate = self } //-------------------------------------------------- @@ -41,13 +60,26 @@ open class ItemDropdownEntryField: DropdownEntryField { pickerView?.delegate = delegate pickerView?.dataSource = delegate } + + private func setInitialValueFromPicker() { + + if setInitialValueInTextField, let pickerIndex = pickerView?.selectedRow(inComponent: 0) { + text = pickerData[pickerIndex] + } + } + + @objc override func startEditing() { + super.startEditing() + + setInitialValueFromPicker() + } } // MARK:- Base Picker Delegate extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { public func numberOfComponents(in pickerView: UIPickerView) -> Int { - return 1 + return componentsCount } public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { @@ -59,7 +91,7 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { } public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - textField.text = pickerData[row] + text = pickerData[row] } } @@ -72,3 +104,28 @@ extension ItemDropdownEntryField { guard let dictionary = json, !dictionary.isEmpty else { return } } } + +// MARK: - UITextField Intercept +extension ItemDropdownEntryField { + + public func textFieldDidBeginEditing(_ textField: UITextField) { + + setInitialValueFromPicker() + outsiderTextDelegate?.textFieldDidBeginEditing?(textField) + } +} + +// MARK: - Accessibility +extension DropdownEntryField { + + open override func setAccessibilityString(_ accessibilityString: String?) { + + var accessibilityString = accessibilityString ?? "" + + if let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { + accessibilityString += textPickerItem + } + + textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" + } +} diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 49d9b1e3..1c783cd4 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -24,10 +24,9 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - public private(set) var textField: UITextField = { - let textField = UITextField(frame: .zero) + public private(set) var textField: TextField = { + let textField = TextField(frame: .zero) textField.isAccessibilityElement = true - textField.translatesAutoresizingMaskIntoConstraints = false textField.setContentCompressionResistancePriority(.required, for: .vertical) textField.font = MFStyler.fontForTextField() textField.smartQuotesType = .no @@ -201,6 +200,7 @@ import UIKit override open func resignFirstResponder() -> Bool { textField.resignFirstResponder() + isSelected = false return true } @@ -247,7 +247,7 @@ import UIKit @objc func startEditing() { - entryContainer.originalUI() + isSelected = true textField.becomeFirstResponder() } } diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift new file mode 100644 index 00000000..2fdc9e4e --- /dev/null +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -0,0 +1,93 @@ +// +// TextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/18/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +open class TextField: UITextField { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + open var json: [AnyHashable: Any]? + + private var initialSetupPerformed = false + + /// Set to true to hide the blinking textField cursor. + public var hideBlinkingCaret = false + + //-------------------------------------------------- + // MARK: - Initialization + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + public convenience init() { + self.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + initialSetup() + } + + public func initialSetup() { + + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } + + open override func caretRect(for position: UITextPosition) -> CGRect { + + if hideBlinkingCaret { + return .zero + } + + return super.caretRect(for: position) + } +} + +/// MARK:- MVMCoreViewProtocol +extension TextField: MVMCoreViewProtocol { + + open func updateView(_ size: CGFloat) {} + + /// Will be called only once. + open func setupView() { + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + } +} + + +/// MARK:- MVMCoreUIMoleculeViewProtocol +extension TextField: MVMCoreUIMoleculeViewProtocol { + + open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.json = json + + guard let dictionary = json else { return } + + if let backgroundColorString = dictionary.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + + if let text = dictionary[KeyText] as? String { + self.text = text + } + } + + open func reset() { + backgroundColor = .clear + } +} From 9b581a61320cc20496400fc3a8407bb044a24138 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 Nov 2019 14:30:42 -0500 Subject: [PATCH 054/112] no line. --- MVMCoreUI/BaseClasses/TextField.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift index 2fdc9e4e..c0bcd2c1 100644 --- a/MVMCoreUI/BaseClasses/TextField.swift +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -69,7 +69,6 @@ extension TextField: MVMCoreViewProtocol { } } - /// MARK:- MVMCoreUIMoleculeViewProtocol extension TextField: MVMCoreUIMoleculeViewProtocol { From 1d08842d47e7916c0e916470eb5ca7e30248c779 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 Nov 2019 15:40:11 -0500 Subject: [PATCH 055/112] wip. --- .../TextFields/DateDropdownEntryField.swift | 2 ++ MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 12 ++++-------- .../TextFields/ItemDropdownEntryField.swift | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 97bb8740..a6bd390e 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -103,5 +103,7 @@ extension DateDropdownEntryField { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } + + } } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index f8a2ccbf..b352a89b 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -306,10 +306,10 @@ import UIKit return enabledTextFields } - - //-------------------------------------------------- - // MARK: - Text Field Delegate - //-------------------------------------------------- +} + +// MARK: - TextField Delegate +extension DigitEntryField { @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -381,10 +381,6 @@ import UIKit return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true } - //-------------------------------------------------- - // MARK: - Passed Along TextField delegate - //-------------------------------------------------- - @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { return uiTextFieldDelegate?.textFieldShouldBeginEditing?(textField) ?? true diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 33345a55..8f689e6b 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -42,10 +42,20 @@ open class ItemDropdownEntryField: DropdownEntryField { // MARK: - Initializer //-------------------------------------------------- + public convenience init() { + self.init(frame: .zero) + setup() + } + public convenience init(pickerData: [String]) { self.init(frame: .zero) self.pickerData = pickerData + setup() + } + + private func setup() { + pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) textField.hideBlinkingCaret = true uiTextFieldDelegate = self @@ -55,7 +65,7 @@ open class ItemDropdownEntryField: DropdownEntryField { // MARK: - Methods //-------------------------------------------------- - public func setPickerDelegate(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { + public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { pickerView?.delegate = delegate pickerView?.dataSource = delegate @@ -102,6 +112,10 @@ extension ItemDropdownEntryField { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } + + if let options = dictionary["options"] as? [String] { + pickerData = options + } } } From 1af47ab98d72b31e4cbdbfa95a6c1d8f5306a8b3 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 Nov 2019 15:40:27 -0500 Subject: [PATCH 056/112] wip. --- .../TextFields/DateDropdownEntryField.swift | 49 ++++++++++++++----- .../TextFields/ItemDropdownEntryField.swift | 8 +-- .../Atoms/TextFields/MdnEntryField.swift | 18 +++---- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 97bb8740..600af64c 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -24,41 +24,44 @@ open class DateDropdownEntryField: DropdownEntryField { // TODO: Pull this out into Styler or some class akin to it. public var formatter: DateFormatter = { - let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeZone = NSTimeZone.system formatter.locale = .current formatter.formatterBehavior = .default - return formatter }() + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + private weak var proprietorTextDelegate: UITextFieldDelegate? + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public override init(frame: CGRect) { - super.init(frame: frame) - - datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) - datePicker?.timeZone = NSTimeZone.system - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) - } - public convenience init() { self.init(frame: .zero) + + setup() } public convenience init(startDate: Date, endDate: Date, showStartDate: Bool = true) { self.init(frame: .zero) + setup() setDatePickerDuration(from: startDate, to: endDate, showStartDate: showStartDate) } - required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("DateDropdownEntryField does not support xib.") + private func setup() { + + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + datePicker?.addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged) + datePicker?.timeZone = NSTimeZone.system + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: self) } //-------------------------------------------------- @@ -94,6 +97,26 @@ open class DateDropdownEntryField: DropdownEntryField { text = formatter.string(from: date) } } + + @objc override func dismissFieldInput(_ sender: Any?) { + setTextWith(date: datePicker?.date) + super.dismissFieldInput(sender) + } + + @objc func valueChanged(_ sender: UIDatePicker){ + + setTextWith(date: datePicker?.date) + } +} + +// MARK: - UITextField Intercept +extension DateDropdownEntryField { + + public func textFieldDidBeginEditing(_ textField: UITextField) { + +// setInitialValueFromPicker() + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) + } } // MARK: - Molecular diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 33345a55..63162cd8 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -27,14 +27,14 @@ open class ItemDropdownEntryField: DropdownEntryField { //-------------------------------------------------- /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. - private weak var outsiderTextDelegate: UITextFieldDelegate? + private weak var proprietorTextDelegate: UITextFieldDelegate? /// If you're using a MFViewController, you must set this to it public override weak var uiTextFieldDelegate: UITextFieldDelegate? { get { return textField.delegate } set { textField.delegate = self - outsiderTextDelegate = newValue + proprietorTextDelegate = newValue } } @@ -55,7 +55,7 @@ open class ItemDropdownEntryField: DropdownEntryField { // MARK: - Methods //-------------------------------------------------- - public func setPickerDelegate(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { + public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { pickerView?.delegate = delegate pickerView?.dataSource = delegate @@ -111,7 +111,7 @@ extension ItemDropdownEntryField { public func textFieldDidBeginEditing(_ textField: UITextField) { setInitialValueFromPicker() - outsiderTextDelegate?.textFieldDidBeginEditing?(textField) + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } } diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 7f7bbeb3..8c43beed 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -27,7 +27,7 @@ import MVMCore //-------------------------------------------------- /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. - private weak var outsiderTextDelegate: UITextFieldDelegate? + private weak var proprietorTextDelegate: UITextFieldDelegate? //-------------------------------------------------- // MARK: - Property Observers @@ -44,7 +44,7 @@ import MVMCore get { return textField.delegate } set { textField.delegate = self - outsiderTextDelegate = newValue + proprietorTextDelegate = newValue } } @@ -167,7 +167,7 @@ import MVMCore textField.resignFirstResponder() - return outsiderTextDelegate?.textFieldShouldReturn?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true } public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { @@ -176,18 +176,18 @@ import MVMCore return false } - return outsiderTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } public func textFieldDidBeginEditing(_ textField: UITextField) { textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) - outsiderTextDelegate?.textFieldDidBeginEditing?(textField) + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } public func textFieldDidEndEditing(_ textField: UITextField) { - outsiderTextDelegate?.textFieldDidEndEditing?(textField) + proprietorTextDelegate?.textFieldDidEndEditing?(textField) if validateAndColor() && isNationalMDN { textField.text = MVMCoreUIUtility.formatMdn(textField.text) @@ -200,16 +200,16 @@ import MVMCore public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - return outsiderTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - return outsiderTextDelegate?.textFieldShouldEndEditing?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true } public func textFieldShouldClear(_ textField: UITextField) -> Bool { - return outsiderTextDelegate?.textFieldShouldClear?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true } } From 0cbaf386448a84e6f49db67f9141ac4bce715801 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 Nov 2019 17:03:59 -0500 Subject: [PATCH 057/112] date picker. --- MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index e083eadc..0a9952ed 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -59,9 +59,10 @@ open class DateDropdownEntryField: DropdownEntryField { private func setup() { datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) - datePicker?.addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged) + datePicker?.addTarget(self, action: #selector(pickerValueChanged), for: .valueChanged) datePicker?.timeZone = NSTimeZone.system MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: self) + uiTextFieldDelegate = self } //-------------------------------------------------- @@ -103,7 +104,7 @@ open class DateDropdownEntryField: DropdownEntryField { super.dismissFieldInput(sender) } - @objc func valueChanged(_ sender: UIDatePicker){ + @objc func pickerValueChanged(_ sender: UIDatePicker){ setTextWith(date: datePicker?.date) } @@ -114,7 +115,7 @@ extension DateDropdownEntryField { public func textFieldDidBeginEditing(_ textField: UITextField) { -// setInitialValueFromPicker() + isSelected = true proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } } @@ -126,7 +127,5 @@ extension DateDropdownEntryField { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } - - } } From df34e1574c31ecc5770fab9fc30144b825bbaad4 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 Nov 2019 17:04:16 -0500 Subject: [PATCH 058/112] latest, updating. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 83 ++++++++++++------- .../Atoms/TextFields/DigitEntryField.swift | 42 +++------- 2 files changed, 62 insertions(+), 63 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 1c1776d8..c0b161b3 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -18,8 +18,11 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - let digitField: UITextField = { - let textField = UITextField(frame: .zero) + let digitField: TextField = { + let textField = TextField() + textField.isAccessibilityElement = true + textField.setContentCompressionResistancePriority(.required, for: .vertical) + textField.setContentCompressionResistancePriority(.required, for: .horizontal) textField.textAlignment = .center textField.keyboardType = .numberPad return textField @@ -31,6 +34,27 @@ import UIKit private var previousSize: CGFloat = 0.0 + //-------------------------------------------------- + // MARK: - Property Observer + //-------------------------------------------------- + + public override var showError: Bool { + didSet { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.borderStrokeColor = self.showError ? .mfPumpkin() : .mfSilver() + + let barHeight: CGFloat = self.showError ? 4 : 1 + self.bottomBar.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight) + self.bottomBar.backgroundColor = self.showError ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor + + self.setNeedsDisplay() + self.layoutIfNeeded() + } + } + } + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -59,7 +83,7 @@ import UIKit required public init?(coder: NSCoder) { super.init(coder: coder) - fatalError("DigitBox has not been implemented") + fatalError("DigitBox does not support xibs.") } //-------------------------------------------------- @@ -72,12 +96,19 @@ import UIKit translatesAutoresizingMaskIntoConstraints = false backgroundColor = .white + + addSubview(digitField) digitField.delegate = self - showErrorState(false) + + NSLayoutConstraint.activate([ + digitField.heightAnchor.constraint(equalToConstant: 24), + digitField.topAnchor.constraint(equalTo: topAnchor, constant: 12), + digitField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), + bottomAnchor.constraint(equalTo: digitField.bottomAnchor, constant: 12), + trailingAnchor.constraint(equalTo: digitField.trailingAnchor, constant: 12)]) widthConstraint = widthAnchor.constraint(equalToConstant: 39) widthConstraint?.isActive = true - heightConstraint = heightAnchor.constraint(equalToConstant: 44) heightConstraint?.isActive = true @@ -96,14 +127,14 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if string.isBackspace { - textBoxDelegate?.textFieldDidDelete?(self.digitField) - } - - return true - } +// public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { +// +// if string.isBackspace { +// textBoxDelegate?.textFieldDidDelete?(self.digitField) +// } +// +// return true +// } public override func updateView(_ size: CGFloat) { super.updateView(size) @@ -140,26 +171,14 @@ import UIKit self.setNeedsLayout() } } - - func showErrorState(_ show: Bool) { - - showError = show - borderStrokeColor = show ? .mfPumpkin() : .mfSilver() - - let barHeight: CGFloat = show ? 4 : 1 - bottomBar.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) - bottomBar.backgroundColor = show ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor - - setNeedsDisplay() - layoutIfNeeded() - } } // TODO: Move if working properly. -extension String { - var isBackspace: Bool { - let char = self.cString(using: String.Encoding.utf8)! - return strcmp(char, "\\b") == -92 - } -} +//extension String { +// +// var isBackspace: Bool { +// let char = self.cString(using: String.Encoding.utf8)! +// return strcmp(char, "\\b") == -92 +// } +//} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index b352a89b..5fae6aa9 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -21,6 +21,10 @@ import UIKit public var digitFields: [DigitBox] = [] + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + //-------------------------------------------------- // MARK: - Property Observers //-------------------------------------------------- @@ -31,7 +35,6 @@ import UIKit digitFields.forEach { $0.isEnabled = self.isEnabled - $0.isUserInteractionEnabled = isEnabled $0.digitField.isEnabled = isEnabled $0.digitField.textColor = isEnabled ? .black : .mfBattleshipGrey() @@ -42,7 +45,6 @@ import UIKit public override var placeholder: String? { get { var string = "" - digitFields.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" } return !string.isEmpty ? string : nil @@ -58,7 +60,7 @@ import UIKit } } - // If there is already text in the textfield, set the place holder label below. + // If there is already text in the textfield, set the placeholder label below. if let text = text, !text.isEmpty && !showError { feedback = placeholder } else if !showError { @@ -78,7 +80,6 @@ import UIKit public override var text: String? { get { var string = "" - digitFields.forEach { string += $0.digitField.text ?? "" } return string @@ -109,9 +110,8 @@ import UIKit self.init(frame: .zero) } - public init(numberOfDigits: Int) { - super.init(frame: .zero) - + public convenience init(numberOfDigits: Int) { + self.init(frame: .zero) self.numberOfDigits = numberOfDigits } @@ -133,9 +133,10 @@ import UIKit alignCenterHorizontal() isAccessibilityElement = false + entryContainer.hideBorder = true entryContainer.bottomBar.backgroundColor = UIColor.clear.cgColor entryContainer.bottomBar.frame = CGRect(x: 0, y: entryContainer.bounds.height, width: 0, height: 0) - setupDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) + assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) textField.removeFromSuperview() } @@ -149,7 +150,7 @@ import UIKit return digitBox } - func setupDigitFieldsView(size: CGFloat) { + func assembleDigitFieldsView(size: CGFloat) { var accessibleElements: [Any] = [titleLabel] @@ -166,7 +167,6 @@ import UIKit box.topAnchor.constraint(equalTo: entryContainer.topAnchor).isActive = true entryContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true - box.centerYAnchor.constraint(equalTo: entryContainer.centerYAnchor).isActive = true if index == 0 { box.leadingAnchor.constraint(equalTo: entryContainer.leadingAnchor).isActive = true @@ -203,33 +203,13 @@ import UIKit self.digitFields.forEach { $0.updateView(size) } self.layoutIfNeeded() } - - // Layout text boxes. -// self.setupDigitFieldsView(size: size) } -// hideBorder = true } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - override func valueChanged() { - super.valueChanged() - - // TODO: is feedbackContainerDistance needed? -// DispatchQueue.main.async { [weak self] in -// guard let self = self else { return } -// -// if let feedback = self.feedback, !feedback.isEmpty { -// self.feedbackContainerDistance?.constant = 10 -// -// } else { -// self.feedbackContainerDistance?.constant = 0 -// } -// } - } - func setAsSecureTextEntry(_ secureEntry: Bool) { DispatchQueue.main.async { [weak self] in @@ -411,7 +391,7 @@ extension DigitEntryField { } } - setupDigitFieldsView(size: MVMCoreUIUtility.getWidth()) + assembleDigitFieldsView(size: MVMCoreUIUtility.getWidth()) } open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { From c38e3c07122946eb525358889221e4e4878555a3 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 19 Nov 2019 09:57:43 -0500 Subject: [PATCH 059/112] DigitEntry in decent place. --- .../Atoms/TextFields/DigitEntryField.swift | 78 +++++++++---------- MVMCoreUI/Containers/views/FormView.swift | 43 +++++----- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 5fae6aa9..5e049d48 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -17,14 +17,10 @@ import UIKit //-------------------------------------------------- private(set) var numberOfDigits = 4 - private var switchedAutomatically = false + public var switchFieldsAutomatically = false public var digitFields: [DigitBox] = [] - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - //-------------------------------------------------- // MARK: - Property Observers //-------------------------------------------------- @@ -133,12 +129,10 @@ import UIKit alignCenterHorizontal() isAccessibilityElement = false - entryContainer.hideBorder = true + entryContainer.hideBorders = true entryContainer.bottomBar.backgroundColor = UIColor.clear.cgColor entryContainer.bottomBar.frame = CGRect(x: 0, y: entryContainer.bounds.height, width: 0, height: 0) assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) - - textField.removeFromSuperview() } private func createDigitField() -> DigitBox { @@ -155,7 +149,11 @@ import UIKit var accessibleElements: [Any] = [titleLabel] if numberOfDigits > 0 { - let digitFields = [DigitBox](repeating: createDigitField(), count: numberOfDigits) + var digitFields = [DigitBox]() + for _ in 0.. [AnyHashable]? { + open class func getEnabledDigitFields(_ textFieldToDetermine: [TextEntryField]) -> [AnyHashable]? { - var enabledTextFields = [AnyHashable]() - - for textfield in textFieldToDetermine ?? [] { - if textfield.isEnabled { - enabledTextFields.append(textfield) - } - } - - return enabledTextFields + return textFieldToDetermine.filter { $0.isEnabled } } } @@ -316,12 +312,12 @@ extension DigitEntryField { // One character, switch old value with new, select next textfield textField.text = string - selectNextTextField(textField, clear: false) + selectNextDigitField(textField, clear: false) valueChanged() return false } else if replacementLength == 0 && oldLength == 1 { - // non empty cell, clear and stay. + // Non empty cell, clear and stay. textField.text = "" valueChanged() return false @@ -336,12 +332,12 @@ extension DigitEntryField { func textFieldDidDelete(_ textField: UITextField?) { // Empty cell, go back to previous cell and clear. - selectPreviousTextField(textField, clear: true) + selectPreviousDigitField(textField, clear: true) } @objc public func textFieldDidBeginEditing(_ textField: UITextField) { - if !switchedAutomatically { + if !switchFieldsAutomatically { textField.text = "" valueChanged() } @@ -356,7 +352,7 @@ extension DigitEntryField { @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { - selectPreviousTextField(textField, clear: false) + selectPreviousDigitField(textField, clear: false) return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true } @@ -374,7 +370,7 @@ extension DigitEntryField { // MARK: - Molecular extension DigitEntryField { - + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) @@ -385,16 +381,16 @@ extension DigitEntryField { numberOfDigits = digits } + assembleDigitFieldsView(size: MVMCoreUIUtility.getWidth()) + if !dictionary.isEmpty{ for digitBox in digitFields { MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) } } - - assembleDigitFieldsView(size: MVMCoreUIUtility.getWidth()) } open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 44 + return 115 } } diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormView.swift index 201bfb3d..44a5c4b5 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormView.swift @@ -24,7 +24,7 @@ import UIKit }() /// Determines if a border should be drawn. - public var hideBorder = false + public var hideBorders = false public var borderStrokeColor: UIColor = .mfSilver() private var borderPath: UIBezierPath = UIBezierPath() @@ -35,13 +35,17 @@ import UIKit public var showError = false { didSet { - showError ? errorUI() : originalUI() + if !hideBorders { + showError ? errorUI() : originalUI() + } } } public var isEnabled = true { didSet { - isEnabled ? originalUI() : disabledUI() + if !hideBorders { + isEnabled ? originalUI() : disabledUI() + } } } @@ -53,7 +57,9 @@ import UIKit public var isSelected = false { didSet { - isSelected ? selectedUI() : originalUI() + if !hideBorders { + isSelected ? selectedUI() : originalUI() + } } } @@ -86,7 +92,7 @@ import UIKit borderPath.removeAllPoints() - if !hideBorder { + if !hideBorders { // Brings the other half of the line inside the view to prevent cropping. let origin = bounds.origin let size = frame.size @@ -117,7 +123,7 @@ import UIKit open func originalUI() { isUserInteractionEnabled = true - hideBorder = false + hideBorders = false borderStrokeColor = .mfSilver() bottomBar.backgroundColor = UIColor.black.cgColor refreshUI(bottomBarSize: 1) @@ -126,7 +132,7 @@ import UIKit open func errorUI() { isUserInteractionEnabled = true - hideBorder = false + hideBorders = false borderStrokeColor = .mfPumpkin() bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor refreshUI(bottomBarSize: 4) @@ -135,7 +141,7 @@ import UIKit open func selectedUI() { isUserInteractionEnabled = true - hideBorder = false + hideBorders = false borderStrokeColor = .black bottomBar.backgroundColor = UIColor.black.cgColor refreshUI(bottomBarSize: 1) @@ -144,7 +150,7 @@ import UIKit open func lockedUI() { isUserInteractionEnabled = false - hideBorder = true + hideBorders = true borderStrokeColor = .clear bottomBar.backgroundColor = UIColor.clear.cgColor refreshUI(bottomBarSize: 1) @@ -154,19 +160,20 @@ import UIKit isUserInteractionEnabled = false borderStrokeColor = .mfSilver() - hideBorder = false bottomBar.backgroundColor = UIColor.mfSilver().cgColor refreshUI(bottomBarSize: 1) } open func refreshUI(bottomBarSize: CGFloat? = nil) { - let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) - bottomBar.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) - - delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) - setNeedsDisplay() - layoutIfNeeded() + if !hideBorders { + let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) + bottomBar.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) + + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + setNeedsDisplay() + layoutIfNeeded() + } } //-------------------------------------------------- @@ -179,8 +186,8 @@ import UIKit guard let dictionary = json, !dictionary.isEmpty else { return } - if let hideBorder = dictionary["hideBorder"] as? Bool { - self.hideBorder = hideBorder + if let hideBorders = dictionary["hideBorders"] as? Bool { + self.hideBorders = hideBorders } } } From 1fc2963fca4ecbe81bd3026f87e3d109f0ccf0f5 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 19 Nov 2019 10:05:44 -0500 Subject: [PATCH 060/112] ok. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index b352a89b..b422a0df 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -294,7 +294,7 @@ import UIKit } } - class func getEnabledTextfields(_ textFieldToDetermine: [TextEntryField]?) -> [AnyHashable]? { + class func getEnabledTextfields(_ textFieldToDetermine: [DigitBox]?) -> [AnyHashable]? { var enabledTextFields = [AnyHashable]() From 26c73070d93e316709d6c8a5caafe59a96ae084f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 20 Nov 2019 09:12:31 -0500 Subject: [PATCH 061/112] latestest and greatest. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 68 ++++--- .../Atoms/TextFields/DigitEntryField.swift | 167 +++++++++++++----- MVMCoreUI/Atoms/TextFields/EntryField.swift | 24 ++- .../Atoms/TextFields/MdnEntryField.swift | 8 +- .../Atoms/TextFields/TextEntryField.swift | 2 +- MVMCoreUI/BaseClasses/TextField.swift | 17 ++ MVMCoreUI/Containers/views/FormView.swift | 85 ++++++--- 7 files changed, 269 insertions(+), 102 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index c0b161b3..4ad1b1ad 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -9,11 +9,12 @@ import UIKit @objc protocol DigitBoxDelegate: NSObjectProtocol { - @objc optional func textFieldDidDelete(_ textField: UITextField?) + @objc optional func digitFieldDidDelete(_ textField: UITextField?) + @objc optional func textFieldDidChange(_ textField: UITextField) } -@objcMembers open class DigitBox: FormView, UITextFieldDelegate { +@objcMembers open class DigitBox: FormFieldContainer, UITextFieldDelegate { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -46,8 +47,8 @@ import UIKit self.borderStrokeColor = self.showError ? .mfPumpkin() : .mfSilver() let barHeight: CGFloat = self.showError ? 4 : 1 - self.bottomBar.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight) - self.bottomBar.backgroundColor = self.showError ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor + self.bottomBar?.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight) + self.bottomBar?.backgroundColor = self.showError ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor self.setNeedsDisplay() self.layoutIfNeeded() @@ -59,7 +60,7 @@ import UIKit // MARK: - Delegate //-------------------------------------------------- - weak var textBoxDelegate: DigitBoxDelegate? + weak var digitBoxDelegate: DigitBoxDelegate? //-------------------------------------------------- // MARK: - Constraints @@ -102,39 +103,54 @@ import UIKit NSLayoutConstraint.activate([ digitField.heightAnchor.constraint(equalToConstant: 24), - digitField.topAnchor.constraint(equalTo: topAnchor, constant: 12), - digitField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), - bottomAnchor.constraint(equalTo: digitField.bottomAnchor, constant: 12), - trailingAnchor.constraint(equalTo: digitField.trailingAnchor, constant: 12)]) + digitField.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + digitField.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), + bottomAnchor.constraint(greaterThanOrEqualTo: digitField.bottomAnchor), + trailingAnchor.constraint(greaterThanOrEqualTo: digitField.trailingAnchor), + digitField.centerYAnchor.constraint(equalTo: centerYAnchor), + digitField.centerXAnchor.constraint(equalTo: centerXAnchor)]) widthConstraint = widthAnchor.constraint(equalToConstant: 39) widthConstraint?.isActive = true heightConstraint = heightAnchor.constraint(equalToConstant: 44) heightConstraint?.isActive = true - layer.addSublayer(bottomBar) + if let bottomBar = bottomBar { + layer.addSublayer(bottomBar) + } updateView(MVMCoreUISplitViewController.getDetailViewWidth()) + digitField.addTarget(self, action:#selector(textfieldChanged) , for: .valueChanged) + } + + func textfieldChanged() { + } open override func layoutSubviews() { super.layoutSubviews() let barHeight: CGFloat = showError ? 4 : 1 - bottomBar.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) + bottomBar?.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) + } + + open override func reset() { + super.reset() + + digitField.text = nil } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- -// public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { -// -// if string.isBackspace { -// textBoxDelegate?.textFieldDidDelete?(self.digitField) -// } -// -// return true -// } + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if string.isBackspace { + digitBoxDelegate?.digitFieldDidDelete?(self.digitField) + } + + return true + } public override func updateView(_ size: CGFloat) { super.updateView(size) @@ -175,10 +191,10 @@ import UIKit // TODO: Move if working properly. -//extension String { -// -// var isBackspace: Bool { -// let char = self.cString(using: String.Encoding.utf8)! -// return strcmp(char, "\\b") == -92 -// } -//} +extension String { + + var isBackspace: Bool { + let char = self.cString(using: String.Encoding.utf8)! + return strcmp(char, "\\b") == -92 + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 5e049d48..6caee0c9 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -19,7 +19,8 @@ import UIKit private(set) var numberOfDigits = 4 public var switchFieldsAutomatically = false - public var digitFields: [DigitBox] = [] + public var digitBoxes: [DigitBox] = [] + var selectedDigitField: DigitBox? //-------------------------------------------------- // MARK: - Property Observers @@ -29,7 +30,7 @@ import UIKit didSet { titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() - digitFields.forEach { + digitBoxes.forEach { $0.isEnabled = self.isEnabled $0.isUserInteractionEnabled = isEnabled $0.digitField.isEnabled = isEnabled @@ -38,17 +39,29 @@ import UIKit } } + public override var showError: Bool { + didSet { + digitBoxes.forEach { $0.showError = self.showError } + } + } + + public override var isLocked: Bool { + didSet { + digitBoxes.forEach { $0.isLocked = self.isLocked } + } + } + public override var placeholder: String? { get { var string = "" - digitFields.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" } + digitBoxes.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" } return !string.isEmpty ? string : nil } set { guard let newValue = newValue else { return } - for (index, field) in digitFields.enumerated() { + for (index, field) in digitBoxes.enumerated() { if index < newValue.count { let indexChar = newValue.index(newValue.startIndex, offsetBy: index) field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [ @@ -76,14 +89,14 @@ import UIKit public override var text: String? { get { var string = "" - digitFields.forEach { string += $0.digitField.text ?? "" } + digitBoxes.forEach { string += $0.digitField.text ?? "" } return string } set { guard let newValue = newValue else { return } - for (index, field) in digitFields.enumerated() { + for (index, field) in digitBoxes.enumerated() { if index < newValue.count { let indexChar = newValue.index(newValue.startIndex, offsetBy: index) field.digitField.text = String(newValue[indexChar]) @@ -94,6 +107,22 @@ import UIKit } } + /// If you're using a MFViewController, you must set this to it + public override weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { return textField.delegate } + set { + textField.delegate = self + proprietorTextDelegate = newValue + } + } + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + private weak var proprietorTextDelegate: UITextFieldDelegate? + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -129,9 +158,7 @@ import UIKit alignCenterHorizontal() isAccessibilityElement = false - entryContainer.hideBorders = true - entryContainer.bottomBar.backgroundColor = UIColor.clear.cgColor - entryContainer.bottomBar.frame = CGRect(x: 0, y: entryContainer.bounds.height, width: 0, height: 0) + entryContainer.disableBorders = true assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } @@ -139,8 +166,9 @@ import UIKit let digitBox = DigitBox() digitBox.isAccessibilityElement = true + MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: self) digitBox.digitField.delegate = self - digitBox.textBoxDelegate = self + digitBox.digitBoxDelegate = self return digitBox } @@ -149,17 +177,17 @@ import UIKit var accessibleElements: [Any] = [titleLabel] if numberOfDigits > 0 { - var digitFields = [DigitBox]() + var digitBoxes = [DigitBox]() for _ in 0.. 0 && enteredValue.count == self.digitFields.count + return enteredValue.count > 0 && enteredValue.count == self.digitBoxes.count } } public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { - var selectNextField = false + var selectPreviousField = false - for field in digitFields { + for field in digitBoxes.reversed() { - if field.digitField == currentTextField { - selectNextField = true + if field.isSelected { + selectPreviousField = true field.isSelected = false - } else if selectNextField { + } else if selectPreviousField { if !clear { switchFieldsAutomatically = true } @@ -252,6 +287,7 @@ import UIKit switchFieldsAutomatically = false UIAccessibility.post(notification: .layoutChanged, argument: field.digitField) + return } } } @@ -260,42 +296,77 @@ import UIKit var selectNextField = false - for field in digitFields { - if field.digitField == currentTextField { - selectNextField = true - field.isSelected = false + for (index, field) in digitBoxes.enumerated() { + + if field.isSelected { + if index == digitBoxes.count - 1 { + return + } else { + selectNextField = true + field.isSelected = false + } } else if selectNextField { if !clear { switchFieldsAutomatically = true } field.isSelected = true - field.becomeFirstResponder() + field.digitField.becomeFirstResponder() switchFieldsAutomatically = false UIAccessibility.post(notification: .layoutChanged, argument: field.digitField) + return } } } - open class func getEnabledDigitFields(_ textFieldToDetermine: [TextEntryField]) -> [AnyHashable]? { + @objc override func startEditing() { - return textFieldToDetermine.filter { $0.isEnabled } + selectedDigitField?.isSelected = true + selectedDigitField?.digitField.becomeFirstResponder() + } + + override open func resignFirstResponder() -> Bool { + + selectedDigitField?.digitField.resignFirstResponder() + selectedDigitField?.isSelected = false + return true + } + + @objc override func dismissFieldInput(_ sender: Any?) { + + for box in digitBoxes { + if box.isSelected { + box.digitField.resignFirstResponder() + box.isSelected = false + } + } + } + + open class func getEnabledDigitFields(_ textFieldToDetermine: [DigitBox]) -> [TextField]? { + + return textFieldToDetermine.filter { $0.isEnabled }.compactMap { $0.digitField } } } // MARK: - TextField Delegate extension DigitEntryField { + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() - return uiTextFieldDelegate?.textFieldShouldReturn?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true } public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if string.isBackspace { + selectPreviousDigitField(textField, clear: true) + return true + } + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false } @@ -329,7 +400,7 @@ extension DigitEntryField { return false } - func textFieldDidDelete(_ textField: UITextField?) { + func digitFieldDidDelete(_ textField: UITextField?) { // Empty cell, go back to previous cell and clear. selectPreviousDigitField(textField, clear: true) @@ -337,34 +408,46 @@ extension DigitEntryField { @objc public func textFieldDidBeginEditing(_ textField: UITextField) { + for digitBox in digitBoxes { + + if digitBox.isSelected { + digitBox.isSelected = false + } + + if digitBox.digitField == textField { + selectedDigitField = digitBox + digitBox.isSelected = true + } + } + if !switchFieldsAutomatically { textField.text = "" valueChanged() } - uiTextFieldDelegate?.textFieldDidBeginEditing?(textField) + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } @objc public func textFieldDidEndEditing(_ textField: UITextField) { - uiTextFieldDelegate?.textFieldDidEndEditing?(textField) + proprietorTextDelegate?.textFieldDidEndEditing?(textField) } @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { selectPreviousDigitField(textField, clear: false) - return uiTextFieldDelegate?.textFieldShouldClear?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true } @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - return uiTextFieldDelegate?.textFieldShouldBeginEditing?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - return uiTextFieldDelegate?.textFieldShouldEndEditing?(textField) ?? true + return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true } } @@ -384,7 +467,7 @@ extension DigitEntryField { assembleDigitFieldsView(size: MVMCoreUIUtility.getWidth()) if !dictionary.isEmpty{ - for digitBox in digitFields { + for digitBox in digitBoxes { MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 65e4e44e..ef6ee97e 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -27,8 +27,8 @@ import UIKit return label }() - public private(set) var entryContainer: FormView = { - let view = FormView() + public private(set) var entryContainer: FormFieldContainer = { + let view = FormFieldContainer() view.isAccessibilityElement = false return view }() @@ -63,6 +63,11 @@ import UIKit /// Toggles error or original UI. public var showError = false { + willSet { + isLocked = false + isSelected = false + isEnabled = false + } didSet { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -75,6 +80,11 @@ import UIKit /// Toggles enabled (original) or disabled UI. public var isEnabled = true { + willSet { + isLocked = false + isSelected = false + showError = false + } didSet { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -89,6 +99,11 @@ import UIKit /// Toggles original or locked UI. public var isLocked = false { + willSet { + isEnabled = true + isSelected = false + showError = false + } didSet { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -101,6 +116,11 @@ import UIKit /// Toggles selected or original (unselected) UI. public var isSelected = false { + willSet { + isLocked = false + isEnabled = true + showError = false + } didSet { DispatchQueue.main.async { [weak self] in guard let self = self else { return } diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 8c43beed..0dec727a 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -61,10 +61,10 @@ import MVMCore } /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public override init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - super.init(frame: .zero) - setBothTextDelegates(to: bothDelegates) - } + public override init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + super.init(frame: .zero) + setBothTextDelegates(to: bothDelegates) + } required public init?(coder: NSCoder) { super.init(coder: coder) diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 1c783cd4..d3d33bcc 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -238,7 +238,7 @@ import UIKit if isValid { clearErrorState() - entryContainer.bottomBar.backgroundColor = UIColor.black.cgColor + entryContainer.bottomBar?.backgroundColor = UIColor.black.cgColor } else if let errMessage = errorMessage { feedback = errMessage diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift index c0bcd2c1..ca813983 100644 --- a/MVMCoreUI/BaseClasses/TextField.swift +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -8,6 +8,10 @@ import UIKit +protocol TextFieldDelegate { + func textFieldDidDelete() +} + open class TextField: UITextField { //-------------------------------------------------- @@ -21,6 +25,14 @@ open class TextField: UITextField { /// Set to true to hide the blinking textField cursor. public var hideBlinkingCaret = false + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + private weak var proprietorTextDelegate: UITextFieldDelegate? + //-------------------------------------------------- // MARK: - Initialization //-------------------------------------------------- @@ -55,6 +67,11 @@ open class TextField: UITextField { return super.caretRect(for: position) } + + open override func deleteBackward() { + super.deleteBackward() +// proprietorTextDelegate?.textFieldDidDelete() + } } /// MARK:- MVMCoreViewProtocol diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormView.swift index 44a5c4b5..38bef4b8 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormView.swift @@ -9,13 +9,13 @@ import UIKit -@objcMembers open class FormView: View { +@objcMembers open class FormFieldContainer: View { //-------------------------------------------------- // MARK: - Drawing Properties //-------------------------------------------------- - /// The bottom border line. - public var bottomBar: CAShapeLayer = { + /// The bottom border line. Height is dynamic based on scenario. + public var bottomBar: CAShapeLayer? = { let layer = CAShapeLayer() layer.backgroundColor = UIColor.black.cgColor layer.drawsAsynchronously = true @@ -23,8 +23,15 @@ import UIKit return layer }() + /// Total control over bottom bar and the drawn borders. + public var disableBorders = false { + didSet { + bottomBar?.isHidden = disableBorders + } + } + /// Determines if a border should be drawn. - public var hideBorders = false + private var hideBorders = false public var borderStrokeColor: UIColor = .mfSilver() private var borderPath: UIBezierPath = UIBezierPath() @@ -35,17 +42,13 @@ import UIKit public var showError = false { didSet { - if !hideBorders { - showError ? errorUI() : originalUI() - } + showError ? errorUI() : originalUI() } } public var isEnabled = true { didSet { - if !hideBorders { - isEnabled ? originalUI() : disabledUI() - } + isEnabled ? originalUI() : disabledUI() } } @@ -57,9 +60,7 @@ import UIKit public var isSelected = false { didSet { - if !hideBorders { - isSelected ? selectedUI() : originalUI() - } + isSelected ? selectedUI() : originalUI() } } @@ -92,7 +93,7 @@ import UIKit borderPath.removeAllPoints() - if !hideBorders { + if !disableBorders && !hideBorders { // Brings the other half of the line inside the view to prevent cropping. let origin = bounds.origin let size = frame.size @@ -113,46 +114,75 @@ import UIKit super.setupView() isOpaque = false - layer.addSublayer(bottomBar) + if let bottomBar = bottomBar { + layer.addSublayer(bottomBar) + } } //-------------------------------------------------- // MARK: - Draw States //-------------------------------------------------- + public enum State { + case original + case error + case selected + case locked + case disabled + + public func setStateUI(for formField: FormFieldContainer) { + switch self { + case .original: + formField.originalUI() + + case .error: + formField.errorUI() + + case .selected: + formField.selectedUI() + + case .locked: + formField.lockedUI() + + case .disabled: + formField.disabledUI() + } + } + } + open func originalUI() { isUserInteractionEnabled = true hideBorders = false borderStrokeColor = .mfSilver() - bottomBar.backgroundColor = UIColor.black.cgColor + bottomBar?.backgroundColor = UIColor.black.cgColor refreshUI(bottomBarSize: 1) } open func errorUI() { isUserInteractionEnabled = true - hideBorders = false borderStrokeColor = .mfPumpkin() - bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor + hideBorders = false + bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor refreshUI(bottomBarSize: 4) } open func selectedUI() { isUserInteractionEnabled = true - hideBorders = false borderStrokeColor = .black - bottomBar.backgroundColor = UIColor.black.cgColor + hideBorders = false + bottomBar?.backgroundColor = UIColor.black.cgColor refreshUI(bottomBarSize: 1) } open func lockedUI() { isUserInteractionEnabled = false - hideBorders = true borderStrokeColor = .clear - bottomBar.backgroundColor = UIColor.clear.cgColor + hideBorders = true + bottomBar?.backgroundColor = UIColor.clear.cgColor refreshUI(bottomBarSize: 1) } @@ -160,15 +190,16 @@ import UIKit isUserInteractionEnabled = false borderStrokeColor = .mfSilver() - bottomBar.backgroundColor = UIColor.mfSilver().cgColor + hideBorders = false + bottomBar?.backgroundColor = UIColor.mfSilver().cgColor refreshUI(bottomBarSize: 1) } open func refreshUI(bottomBarSize: CGFloat? = nil) { - if !hideBorders { + if !disableBorders { let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) - bottomBar.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) + bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) setNeedsDisplay() @@ -186,8 +217,8 @@ import UIKit guard let dictionary = json, !dictionary.isEmpty else { return } - if let hideBorders = dictionary["hideBorders"] as? Bool { - self.hideBorders = hideBorders + if let disableBorders = dictionary["disableBorders"] as? Bool { + self.disableBorders = disableBorders } } } From 88442fe59c503d7c74cd8702119e26a061304611 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 21 Nov 2019 11:47:12 -0500 Subject: [PATCH 062/112] latest. --- MVMCoreUI.xcodeproj/project.pbxproj | 16 +- ...eld.swift => BaseDropdownEntryField.swift} | 31 ++-- .../TextFields/DateDropdownEntryField.swift | 61 +++---- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 30 +--- .../Atoms/TextFields/DigitEntryField.swift | 73 ++++----- MVMCoreUI/Atoms/TextFields/EntryField.swift | 155 ++++++++++-------- .../TextFields/ItemDropdownEntryField.swift | 74 +++------ .../Atoms/TextFields/MdnEntryField.swift | 46 ++---- .../Atoms/TextFields/TextEntryField.swift | 97 +++++------ MVMCoreUI/Atoms/Views/CaretView.swift | 26 +-- MVMCoreUI/BaseClasses/TextField.swift | 1 - ...ormView.swift => FormFieldContainer.swift} | 103 +++++++++--- .../MVMCoreUIMoleculeMappingObject.m | 3 +- 13 files changed, 361 insertions(+), 355 deletions(-) rename MVMCoreUI/Atoms/TextFields/{DropdownEntryField.swift => BaseDropdownEntryField.swift} (74%) rename MVMCoreUI/Containers/views/{FormView.swift => FormFieldContainer.swift} (71%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 6f4709e4..880487b4 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -42,10 +42,10 @@ 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; - 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */; }; + 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; - 0ABD136B237B193A0081388D /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormView.swift */; }; + 0ABD136B237B193A0081388D /* FormFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormFieldContainer.swift */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; @@ -236,12 +236,12 @@ 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; - 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownEntryField.swift; sourceTree = ""; }; + 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = ""; }; 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; - 0ABD136A237B193A0081388D /* FormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = ""; }; + 0ABD136A237B193A0081388D /* FormFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldContainer.swift; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; @@ -452,7 +452,7 @@ 0ABD1369237B18EE0081388D /* views */ = { isa = PBXGroup; children = ( - 0ABD136A237B193A0081388D /* FormView.swift */, + 0ABD136A237B193A0081388D /* FormFieldContainer.swift */, ); path = views; sourceTree = ""; @@ -809,7 +809,7 @@ 0A21DB7E235DECC500C160A2 /* EntryField.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, - 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */, + 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */, 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */, 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */, ); @@ -1187,14 +1187,14 @@ D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, - 0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */, + 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, - 0ABD136B237B193A0081388D /* FormView.swift in Sources */, + 0ABD136B237B193A0081388D /* FormFieldContainer.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift similarity index 74% rename from MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift rename to MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift index a9d5d693..11b6917f 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -1,5 +1,5 @@ // -// DropdownEntryField.swift +// BaseDropdownEntryField.swift // MVMCoreUI // // Created by Kevin Christiano on 10/23/19. @@ -9,10 +9,10 @@ import UIKit /** - This class is intended to be subclassed. - See ItemDropdownEntryField and DateDropdownEntryField. + This class is intended to be subclassed. + See ItemDropdownEntryField and DateDropdownEntryField. */ -@objcMembers open class DropdownEntryField: TextEntryField { +@objcMembers open class BaseDropdownEntryField: TextEntryField { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -31,9 +31,11 @@ import UIKit // MARK: - Property Observers //-------------------------------------------------- - public override var isEnabled: Bool { - didSet { - dropDownCaretView.isEnabled = isEnabled + @objc public override var isEnabled: Bool { + get { super.isEnabled } + set (enabled) { + dropDownCaretView.isEnabled = enabled + super.isEnabled = enabled } } @@ -41,11 +43,11 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("DropdownEntryField does not support xib.") } @@ -54,7 +56,7 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - public override func setupFieldContainerContent(_ container: UIView) { + @objc public override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) container.addSubview(dropDownCaretView) @@ -66,7 +68,6 @@ import UIKit container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true - dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true let caretTap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) @@ -75,15 +76,13 @@ import UIKit } // MARK: - Molecular -extension DropdownEntryField { +extension BaseDropdownEntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } - if let _ = dictionary[KeyType] as? String { - dropDownCaretView.isHidden = false - } + dropDownCaretView.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) } } diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 0a9952ed..f0a7a368 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -9,7 +9,7 @@ import UIKit -open class DateDropdownEntryField: DropdownEntryField { +@objcMembers open class DateDropdownEntryField: BaseDropdownEntryField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -32,44 +32,41 @@ open class DateDropdownEntryField: DropdownEntryField { return formatter }() - //-------------------------------------------------- - // MARK: - Delegate - //-------------------------------------------------- - - /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. - private weak var proprietorTextDelegate: UITextFieldDelegate? - //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public convenience init() { - self.init(frame: .zero) - + @objc public override init(frame: CGRect) { + super.init(frame: frame) setup() } - public convenience init(startDate: Date, endDate: Date, showStartDate: Bool = true) { + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc public convenience init(startDate: Date, endDate: Date, showStartDate: Bool = true) { self.init(frame: .zero) - - setup() setDatePickerDuration(from: startDate, to: endDate, showStartDate: showStartDate) } - private func setup() { + @objc required public init?(coder: NSCoder) { + fatalError("DateDropdownEntryField init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + @objc private func setup() { datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) datePicker?.addTarget(self, action: #selector(pickerValueChanged), for: .valueChanged) datePicker?.timeZone = NSTimeZone.system MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: self) - uiTextFieldDelegate = self } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - public func setDatePickerDuration(from startDate: Date?, to endDate: Date?, showStartDate: Bool = true) { + @objc public func setDatePickerDuration(from startDate: Date?, to endDate: Date?, showStartDate: Bool = true) { datePicker?.minimumDate = startDate datePicker?.maximumDate = endDate @@ -79,7 +76,7 @@ open class DateDropdownEntryField: DropdownEntryField { } } - public func dismissDatePicker() -> Date? { + @objc public func dismissDatePicker() -> Date? { let pickedDate = datePicker?.date setTextWith(date: pickedDate) @@ -88,8 +85,8 @@ open class DateDropdownEntryField: DropdownEntryField { return pickedDate } - private func setTextWith(date: Date?) { - + @objc private func setTextWith(date: Date?) { + guard let date = date else { return } if calendar.isDate(date, inSameDayAs: Date()) { @@ -104,26 +101,16 @@ open class DateDropdownEntryField: DropdownEntryField { super.dismissFieldInput(sender) } - @objc func pickerValueChanged(_ sender: UIDatePicker){ - - setTextWith(date: datePicker?.date) - } -} - -// MARK: - UITextField Intercept -extension DateDropdownEntryField { - - public func textFieldDidBeginEditing(_ textField: UITextField) { + @objc func pickerValueChanged(_ sender: UIDatePicker) { - isSelected = true - proprietorTextDelegate?.textFieldDidBeginEditing?(textField) + setTextWith(date: datePicker?.date) } } // MARK: - Molecular extension DateDropdownEntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 4ad1b1ad..79d4e554 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -8,9 +8,8 @@ import UIKit -@objc protocol DigitBoxDelegate: NSObjectProtocol { +@objc protocol DigitBoxDelegate { @objc optional func digitFieldDidDelete(_ textField: UITextField?) - @objc optional func textFieldDidChange(_ textField: UITextField) } @@ -40,11 +39,12 @@ import UIKit //-------------------------------------------------- public override var showError: Bool { - didSet { + get { return super.showError } + set (error) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.borderStrokeColor = self.showError ? .mfPumpkin() : .mfSilver() + self.borderStrokeColor = error ? .mfPumpkin() : .mfSilver() let barHeight: CGFloat = self.showError ? 4 : 1 self.bottomBar?.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight) @@ -53,6 +53,7 @@ import UIKit self.setNeedsDisplay() self.layoutIfNeeded() } + super.showError = error } } @@ -142,16 +143,7 @@ import UIKit //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if string.isBackspace { - digitBoxDelegate?.digitFieldDidDelete?(self.digitField) - } - - return true - } - + public override func updateView(_ size: CGFloat) { super.updateView(size) @@ -188,13 +180,3 @@ import UIKit } } } - - -// TODO: Move if working properly. -extension String { - - var isBackspace: Bool { - let char = self.cString(using: String.Encoding.utf8)! - return strcmp(char, "\\b") == -92 - } -} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 6caee0c9..ce53c0d1 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -27,14 +27,15 @@ import UIKit //-------------------------------------------------- public override var isEnabled: Bool { - didSet { - titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() + get { return super.isEnabled } + set (enabled) { + titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() digitBoxes.forEach { - $0.isEnabled = self.isEnabled - $0.isUserInteractionEnabled = isEnabled - $0.digitField.isEnabled = isEnabled - $0.digitField.textColor = isEnabled ? .black : .mfBattleshipGrey() + $0.isEnabled = enabled + $0.isUserInteractionEnabled = enabled + $0.digitField.isEnabled = enabled + $0.digitField.textColor = enabled ? .black : .mfBattleshipGrey() } } } @@ -46,8 +47,9 @@ import UIKit } public override var isLocked: Bool { - didSet { - digitBoxes.forEach { $0.isLocked = self.isLocked } + get { return super.isLocked } + set (locked) { + digitBoxes.forEach { $0.isLocked = locked } } } @@ -127,25 +129,20 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - public convenience init(numberOfDigits: Int) { + @objc public convenience init(numberOfDigits: Int) { self.init(frame: .zero) self.numberOfDigits = numberOfDigits } - public init(numberOfDigits: Int, bothDelegates delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?, size: CGFloat? = nil) { - self.numberOfDigits = numberOfDigits - super.init(bothDelegates: delegate) - } - - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("DigitEntryField xib has not been implemented") } @@ -154,15 +151,15 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - public override func setupFieldContainerContent(_ container: UIView) { + @objc public override func setupFieldContainerContent(_ container: UIView) { alignCenterHorizontal() isAccessibilityElement = false - entryContainer.disableBorders = true + entryContainer.disableAllBorders = true assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } - private func createDigitField() -> DigitBox { + @objc private func createDigitField() -> DigitBox { let digitBox = DigitBox() digitBox.isAccessibilityElement = true @@ -172,7 +169,7 @@ import UIKit return digitBox } - func assembleDigitFieldsView(size: CGFloat) { + @objc func assembleDigitFieldsView(size: CGFloat) { var accessibleElements: [Any] = [titleLabel] @@ -218,10 +215,10 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - open override func updateView(_ size: CGFloat) { + @objc open override func updateView(_ size: CGFloat) { super.updateView(size) - entryContainer.disableBorders = true + entryContainer.disableAllBorders = true DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -234,10 +231,10 @@ import UIKit } } - open override func reset() { + @objc open override func reset() { super.reset() - entryContainer.disableBorders = false + entryContainer.disableAllBorders = false digitBoxes.forEach { $0.reset() } } @@ -245,7 +242,7 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - public func setAsSecureTextEntry(_ secureEntry: Bool) { + @objc public func setAsSecureTextEntry(_ secureEntry: Bool) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -259,7 +256,7 @@ import UIKit } } - public override func defaultValidationBlock() { + @objc public override func defaultValidationBlock() { validationBlock = { enteredValue in guard let enteredValue = enteredValue else { return false } @@ -268,7 +265,7 @@ import UIKit } } - public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { + @objc public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { var selectPreviousField = false @@ -292,7 +289,7 @@ import UIKit } } - public func selectNextDigitField(_ currentTextField: UITextField?, clear: Bool) { + @objc public func selectNextDigitField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false @@ -326,7 +323,7 @@ import UIKit selectedDigitField?.digitField.becomeFirstResponder() } - override open func resignFirstResponder() -> Bool { + @objc override open func resignFirstResponder() -> Bool { selectedDigitField?.digitField.resignFirstResponder() selectedDigitField?.isSelected = false @@ -343,7 +340,7 @@ import UIKit } } - open class func getEnabledDigitFields(_ textFieldToDetermine: [DigitBox]) -> [TextField]? { + @objc open class func getEnabledDigitFields(_ textFieldToDetermine: [DigitBox]) -> [TextField]? { return textFieldToDetermine.filter { $0.isEnabled }.compactMap { $0.digitField } } @@ -352,7 +349,6 @@ import UIKit // MARK: - TextField Delegate extension DigitEntryField { - @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() @@ -360,12 +356,7 @@ extension DigitEntryField { return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true } - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if string.isBackspace { - selectPreviousDigitField(textField, clear: true) - return true - } + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false @@ -400,7 +391,7 @@ extension DigitEntryField { return false } - func digitFieldDidDelete(_ textField: UITextField?) { + @objc func digitFieldDidDelete(_ textField: UITextField?) { // Empty cell, go back to previous cell and clear. selectPreviousDigitField(textField, clear: true) @@ -454,7 +445,7 @@ extension DigitEntryField { // MARK: - Molecular extension DigitEntryField { - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json else { return } @@ -473,7 +464,7 @@ extension DigitEntryField { } } - open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + @objc open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 115 } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index ef6ee97e..e31199bc 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -49,83 +49,97 @@ import UIKit weak var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- - public var isValid = false + public var isValid: Bool = false public var fieldKey: String? public var errorMessage: String? + /// Determines whther the feedback label will clear itself after user interaction or display update. +// public var fixedFeedback: Bool = false + + private var _isEnabled: Bool = true + private var _showError: Bool = false + private var _isLocked: Bool = false + private var _isSelected: Bool = false + //-------------------------------------------------- - // MARK: - Property Observers + // MARK: - Computed Properties //-------------------------------------------------- - /// Toggles error or original UI. - public var showError = false { - willSet { - isLocked = false - isSelected = false - isEnabled = false - } - didSet { + /// Toggles enabled (original) or disabled UI. + public var isEnabled: Bool { + get { return _isEnabled } + set (enabled) { + + _isEnabled = enabled + _isLocked = false + _isSelected = false + _showError = false + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.showError = self.showError - self.feedback = self.showError ? self.errorMessage : nil + self.entryContainer.isEnabled = enabled + self.feedbackLabel.textColor = enabled ? .black : .mfSilver() + self.titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() } } } - /// Toggles enabled (original) or disabled UI. - public var isEnabled = true { - willSet { - isLocked = false - isSelected = false - showError = false - } - didSet { + /// Toggles error or original UI. + public var showError: Bool { + get { return _showError } + set (error) { + + _showError = error + _isLocked = false + _isSelected = false + _isEnabled = true + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isUserInteractionEnabled = self.isEnabled - self.entryContainer.isEnabled = self.isEnabled - self.feedbackLabel.textColor = self.isEnabled ? .black : .mfSilver() - self.titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver() + self.entryContainer.showError = error + self.feedback = error ? self.errorMessage : nil } } } /// Toggles original or locked UI. - public var isLocked = false { - willSet { - isEnabled = true - isSelected = false - showError = false - } - didSet { + public var isLocked: Bool { + get { return _isLocked } + set (locked) { + + _isLocked = locked + _isEnabled = true + _isSelected = false + _showError = false + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isUserInteractionEnabled = !self.isLocked - self.entryContainer.isLocked = self.isLocked + self.entryContainer.isLocked = locked } } } /// Toggles selected or original (unselected) UI. - public var isSelected = false { - willSet { - isLocked = false - isEnabled = true - showError = false - } - didSet { + public var isSelected: Bool { + get { return _isSelected } + set (selected) { + + _isSelected = selected + _isLocked = false + _isEnabled = true + _showError = false + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.isSelected = self.isSelected + self.entryContainer.isSelected = selected } } } @@ -137,9 +151,9 @@ import UIKit /// Sets the text of titleLabel public var title: String? { get { return titleLabel.text } - set { - titleLabel.text = newValue - setAccessibilityString(newValue) + set (newText) { + titleLabel.text = newText + setAccessibilityString(newText) } } @@ -152,9 +166,9 @@ import UIKit /// Sets feedback text in the textField. public var feedback: String? { get { return feedbackLabel.text } - set { - feedbackLabel.text = newValue - setAccessibilityString(newValue) + set (newFeedback) { + feedbackLabel.text = newFeedback + setAccessibilityString(newFeedback) entryContainer.refreshUI() } } @@ -180,21 +194,21 @@ import UIKit //-------------------------------------------------- /// This must be overriden by a subclass. - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - public init(title: String) { + @objc public init(title: String) { super.init(frame: .zero) titleLabel.text = title } - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("EntryField does not support xib.") } @@ -204,7 +218,7 @@ import UIKit //-------------------------------------------------- /// Initial configuration of class and view. - final public override func setupView() { + @objc final public override func setupView() { guard subviews.isEmpty else { return } @@ -243,7 +257,7 @@ import UIKit layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true } - open override func layoutSubviews() { + @objc open override func layoutSubviews() { super.layoutSubviews() entryContainer.refreshUI() @@ -251,11 +265,11 @@ import UIKit /// Method to override. /// Intended to add the interactive content (i.e. textField) to the entryContainer. - open func setupFieldContainerContent(_ container: UIView) { + @objc open func setupFieldContainerContent(_ container: UIView) { // To be overridden by subclass. } - open override func updateView(_ size: CGFloat) { + @objc open override func updateView(_ size: CGFloat) { super.updateView(size) titleLabel.updateView(size) @@ -263,13 +277,18 @@ import UIKit entryContainer.updateView(size) } - open override func reset() { + @objc open override func reset() { super.reset() + isEnabled = true + _isLocked = false + _isSelected = false + _showError = false + backgroundColor = .clear titleLabel.reset() feedbackLabel.reset() - entryContainer.subviews.forEach { $0.removeFromSuperview() } + entryContainer.reset() titleLabel.textColor = .mfBattleshipGrey() } @@ -277,14 +296,14 @@ import UIKit // MARK: - Constraint Methods //-------------------------------------------------- - open override func setLeftPinConstant(_ constant: CGFloat) { + @objc open override func setLeftPinConstant(_ constant: CGFloat) { entryContainerLeading?.constant = constant feedbackLabelLeading?.constant = constant titleLabelLeading?.constant = constant } - open override func setRightPinConstant(_ constant: CGFloat) { + @objc open override func setRightPinConstant(_ constant: CGFloat) { entryContainerTrailing?.constant = constant feedbackLabelTrailing?.constant = constant @@ -295,7 +314,7 @@ import UIKit // MARK: - Molecular extension EntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) self.delegateObject = delegateObject @@ -303,7 +322,7 @@ extension EntryField { entryContainer.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) - if let titleText = dictionary["title"] as? String { + if let titleText = dictionary[KeyTitle] as? String { title = titleText } @@ -319,13 +338,17 @@ extension EntryField { self.isLocked = isLocked } + if let isSelected = dictionary["isSelected"] as? Bool { + self.isSelected = isSelected + } + // Key used to send text value to server if let fieldKey = dictionary[KeyFieldKey] as? String { self.fieldKey = fieldKey } } - override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + @objc override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 115 } } @@ -333,15 +356,15 @@ extension EntryField { // MARK: - Form Validation extension EntryField: FormValidationProtocol { - public func isValidField() -> Bool { + @objc public func isValidField() -> Bool { return isValid } - public func formFieldName() -> String? { + @objc public func formFieldName() -> String? { return fieldKey } - public func formFieldValue() -> Any? { + @objc public func formFieldValue() -> Any? { return text } } diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index f77dcf78..b8085014 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -9,7 +9,7 @@ import UIKit -open class ItemDropdownEntryField: DropdownEntryField { +open class ItemDropdownEntryField: BaseDropdownEntryField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,59 +19,49 @@ open class ItemDropdownEntryField: DropdownEntryField { public var componentsCount = 1 - /// When selecting first responder, allow initial selected value to appear in empty text field. + /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true - //-------------------------------------------------- - // MARK: - Delegate - //-------------------------------------------------- - - /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. - private weak var proprietorTextDelegate: UITextFieldDelegate? - - /// If you're using a MFViewController, you must set this to it - public override weak var uiTextFieldDelegate: UITextFieldDelegate? { - get { return textField.delegate } - set { - textField.delegate = self - proprietorTextDelegate = newValue - } - } - //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public convenience init() { - self.init(frame: .zero) + @objc public override init(frame: CGRect) { + super.init(frame: frame) setup() } - public convenience init(pickerData: [String]) { + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc public convenience init(pickerData: [String]) { self.init(frame: .zero) - self.pickerData = pickerData - setup() } - private func setup() { - - pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) - textField.hideBlinkingCaret = true - uiTextFieldDelegate = self + @objc required public init?(coder: NSCoder) { + fatalError("ItemDropdownEntryField init(coder:) has not been implemented") } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { + @objc private func setup() { + + pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) + textField.hideBlinkingCaret = true + uiTextFieldDelegate = self + } + + @objc public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { pickerView?.delegate = delegate pickerView?.dataSource = delegate } - private func setInitialValueFromPicker() { + @objc private func setInitialValueFromPicker() { if setInitialValueInTextField, let pickerIndex = pickerView?.selectedRow(inComponent: 0) { text = pickerData[pickerIndex] @@ -88,19 +78,19 @@ open class ItemDropdownEntryField: DropdownEntryField { // MARK:- Base Picker Delegate extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { - public func numberOfComponents(in pickerView: UIPickerView) -> Int { + @objc public func numberOfComponents(in pickerView: UIPickerView) -> Int { return componentsCount } - public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + @objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return pickerData.count } - public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + @objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return pickerData[row] } - public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + @objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { text = pickerData[row] } } @@ -108,7 +98,7 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { // MARK: - Molecular extension ItemDropdownEntryField { - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } @@ -119,20 +109,10 @@ extension ItemDropdownEntryField { } } -// MARK: - UITextField Intercept +// MARK: - Accessibility extension ItemDropdownEntryField { - public func textFieldDidBeginEditing(_ textField: UITextField) { - - setInitialValueFromPicker() - proprietorTextDelegate?.textFieldDidBeginEditing?(textField) - } -} - -// MARK: - Accessibility -extension DropdownEntryField { - - open override func setAccessibilityString(_ accessibilityString: String?) { + @objc open override func setAccessibilityString(_ accessibilityString: String?) { var accessibilityString = accessibilityString ?? "" diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 0dec727a..0905340d 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -16,7 +16,7 @@ import MVMCore */ @objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- public var isNationalMDN = true @@ -30,7 +30,7 @@ import MVMCore private weak var proprietorTextDelegate: UITextFieldDelegate? //-------------------------------------------------- - // MARK: - Property Observers + // MARK: - Computed Properties //-------------------------------------------------- /// Formats the MDN when setting and removes format of MDN when reading. @@ -52,21 +52,15 @@ import MVMCore // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: .zero) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public override init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - super.init(frame: .zero) - setBothTextDelegates(to: bothDelegates) - } - - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("MdnEntryField xib not supported.") } @@ -75,7 +69,7 @@ import MVMCore // MARK: - Setup //-------------------------------------------------- - public override func setupFieldContainerContent(_ container: UIView) { + @objc public override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) textField.keyboardType = .numberPad @@ -92,7 +86,7 @@ import MVMCore // MARK: - Methods //-------------------------------------------------- - public func hasValidMDN() -> Bool { + @objc public func hasValidMDN() -> Bool { guard let MDN = mdn, !MDN.isEmpty else { return true } @@ -103,13 +97,13 @@ import MVMCore return MVMCoreUIUtility.validateInternationalMDNString(MDN) } - public func validateAndColor() -> Bool { + @objc public func validateAndColor() -> Bool { if !shouldValidateMDN { let isValid = hasValidMDN() if isValid { - clearErrorState() + showError = false } else { errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") showError = true @@ -136,7 +130,7 @@ import MVMCore // MARK: - Contact Picker Delegate //-------------------------------------------------- - public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + @objc public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { if let phoneNumber = contactProperty.value as? CNPhoneNumber { @@ -163,14 +157,14 @@ import MVMCore // MARK: - Implemented TextField Delegate //-------------------------------------------------- - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true } - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { return false @@ -179,13 +173,13 @@ import MVMCore return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true } - public func textFieldDidBeginEditing(_ textField: UITextField) { + @objc public func textFieldDidBeginEditing(_ textField: UITextField) { textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } - public func textFieldDidEndEditing(_ textField: UITextField) { + @objc public func textFieldDidEndEditing(_ textField: UITextField) { proprietorTextDelegate?.textFieldDidEndEditing?(textField) @@ -194,21 +188,17 @@ import MVMCore } } - //-------------------------------------------------- - // MARK: - Passed Along TextField delegate - //-------------------------------------------------- - - public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } - public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true } - public func textFieldShouldClear(_ textField: UITextField) -> Bool { + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index d3d33bcc..2e7c151b 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -36,30 +36,41 @@ import UIKit }() //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- /// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property. public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.black, .mfSilver()) - public var observingForChange = false + public var observingForChange: Bool = false //-------------------------------------------------- - // MARK: - Property Observers + // MARK: - Computed Properties //-------------------------------------------------- public override var isEnabled: Bool { - didSet { + get { return super.isEnabled } + set (enabled) { + super.isEnabled = enabled + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.textField.isEnabled = self.isEnabled - self.textField.textColor = self.isEnabled ? self.textColor.enabled : self.textColor.disabled + self.textField.isEnabled = enabled + self.textField.textColor = enabled ? self.textColor.enabled : self.textColor.disabled } } } - /// The text of this textField. + public override var showError: Bool { + get { return super.showError } + set (error) { + textField.accessibilityValue = nil + super.showError = error + } + } + + /// The text of this TextField. public override var text: String? { get { return textField.text } set { @@ -74,6 +85,10 @@ import UIKit set { textField.placeholder = newValue } } + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + public var validationBlock: ((_ value: String?) -> Bool)? { didSet { valueChanged() } } @@ -122,21 +137,15 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. - public init(bothDelegates: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - super.init(frame: .zero) - setBothTextDelegates(to: bothDelegates) - } - - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("TextEntryField does not support xib.") } @@ -145,7 +154,7 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - open override func setupFieldContainerContent(_ container: UIView) { + @objc open override func setupFieldContainerContent(_ container: UIView) { MFStyler.styleTextField(textField) container.addSubview(textField) @@ -162,42 +171,28 @@ import UIKit accessibilityElements = [titleLabel, textField, feedbackLabel] } - open override func updateView(_ size: CGFloat) { + @objc open override func updateView(_ size: CGFloat) { super.updateView(size) MFStyler.styleTextField(textField) layoutIfNeeded() } - deinit { + @objc deinit { setBothTextDelegates(to: nil) } - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - open func clearErrorState() { - - textField.accessibilityValue = nil - feedback = nil - showError = false - } - - public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { observingTextFieldDelegate = delegate uiTextFieldDelegate = delegate } - public func defaultValidationBlock() { - - validationBlock = { enteredValue in - return (enteredValue?.count ?? 0) > 0 - } - } + //-------------------------------------------------- + // MARK: - Observing for Change (TextFieldDelegate) + //-------------------------------------------------- - override open func resignFirstResponder() -> Bool { + @objc override open func resignFirstResponder() -> Bool { textField.resignFirstResponder() isSelected = false @@ -209,10 +204,14 @@ import UIKit _ = self.resignFirstResponder() } - //-------------------------------------------------- - // MARK: - Observing for Change (TextFieldDelegate) - //-------------------------------------------------- + public func defaultValidationBlock() { + + validationBlock = { enteredValue in + return (enteredValue?.count ?? 0) > 0 + } + } + /// Executes on UITextField.textDidChangeNotification @objc func valueChanged() { if !showError { @@ -225,19 +224,20 @@ import UIKit isValid = validationBlock?(text) ?? true if previousValidity && !isValid { - feedback = errorMessage + showError = true observingTextFieldDelegate?.isInvalid?(textfield: self) } else if !previousValidity && isValid { - clearErrorState() + showError = false observingTextFieldDelegate?.isValid?(textfield: self) } } + /// Executes on UITextField.textDidEndEditingNotification @objc func endInputing() { if isValid { - clearErrorState() + showError = false entryContainer.bottomBar?.backgroundColor = UIColor.black.cgColor } else if let errMessage = errorMessage { @@ -245,6 +245,7 @@ import UIKit } } + /// Executes on UITextField.textDidBeginEditingNotification @objc func startEditing() { isSelected = true @@ -255,7 +256,7 @@ import UIKit // MARK: - Molecular extension TextEntryField { - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let delegateObject = delegateObject, @@ -306,7 +307,7 @@ extension TextEntryField { } if let formValidationProtocol = delegateObject.formValidationProtocol { - observingTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) as? ObservingTextFieldDelegate + observingTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) } uiTextFieldDelegate = delegateObject.uiTextFieldDelegate @@ -317,7 +318,7 @@ extension TextEntryField { // MARK: - Accessibility extension TextEntryField { - open override func pushAccessibilityNotification() { + @objc open override func pushAccessibilityNotification() { DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -326,7 +327,7 @@ extension TextEntryField { } } - open override func setAccessibilityString(_ accessibilityString: String?) { + @objc open override func setAccessibilityString(_ accessibilityString: String?) { var accessibilityString = accessibilityString ?? "" diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index aa1ad8e3..5d4f99c8 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -18,7 +18,7 @@ public var direction: Direction = .right public var size: CaretSize? - + public var enabledColor: UIColor = .black public var disabledColor: UIColor = .mfSilver() @@ -48,17 +48,17 @@ case vertical case horizontal } - - // Dimensions of container; provided by InVision. + + // Dimensions of container; provided by InVision design. func dimensions() -> CGSize { - + switch self { case .small(let o): return o == .vertical ? CGSize(width: 6, height: 10) : CGSize(width: 10, height: 6) case .medium(let o): return o == .vertical ? CGSize(width: 9, height: 16) : CGSize(width: 16, height: 9) - + case .large(let o): return o == .vertical ? CGSize(width: 14, height: 24) : CGSize(width: 24, height: 14) } @@ -114,7 +114,7 @@ caretPath.removeAllPoints() caretPath.lineJoinStyle = .miter caretPath.lineWidth = lineWidth - + let inset = lineWidth / 2 let halfWidth = frame.size.width / 2 let halfHeight = frame.size.height / 2 @@ -168,7 +168,7 @@ @objc public func setConstraints() { guard let dimensions = size?.dimensions() else { return } - + heightAnchor.constraint(equalToConstant: dimensions.height).isActive = true widthAnchor.constraint(equalToConstant: dimensions.width).isActive = true } @@ -191,16 +191,16 @@ strokeColor = UIColor.mfGet(forHex: strokeColorHex) } - if let isHiddenValue = dictionary[KeyIsHidden] as? Bool { - isHidden = isHiddenValue + if let isHidden = dictionary[KeyIsHidden] as? Bool { + self.isHidden = isHidden } - if let isOpaqueValue = dictionary[KeyIsOpaque] as? Bool { - isOpaque = isOpaqueValue + if let isOpaque = dictionary[KeyIsOpaque] as? Bool { + self.isOpaque = isOpaque } - if let lineWidthValue = dictionary["lineWidth"] as? CGFloat { - lineWidth = lineWidthValue + if let lineWidth = dictionary["lineWidth"] as? CGFloat { + self.lineWidth = lineWidth } } diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift index ca813983..c8321f4d 100644 --- a/MVMCoreUI/BaseClasses/TextField.swift +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -29,7 +29,6 @@ open class TextField: UITextField { // MARK: - Delegate //-------------------------------------------------- - /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. private weak var proprietorTextDelegate: UITextFieldDelegate? diff --git a/MVMCoreUI/Containers/views/FormView.swift b/MVMCoreUI/Containers/views/FormFieldContainer.swift similarity index 71% rename from MVMCoreUI/Containers/views/FormView.swift rename to MVMCoreUI/Containers/views/FormFieldContainer.swift index 38bef4b8..1673d2fa 100644 --- a/MVMCoreUI/Containers/views/FormView.swift +++ b/MVMCoreUI/Containers/views/FormFieldContainer.swift @@ -23,14 +23,23 @@ import UIKit return layer }() - /// Total control over bottom bar and the drawn borders. - public var disableBorders = false { + /// Total control overthe drawn top,bottom, left and right borders. + public var disableAllBorders = false { didSet { - bottomBar?.isHidden = disableBorders + bottomBar?.isHidden = disableAllBorders } } - /// Determines if a border should be drawn. + private(set) var fieldState: FieldState = .original { + didSet (oldState) { + // Will not update if new state is the same as old. + if fieldState != oldState { + fieldState.setStateUI(for: self) + } + } + } + + /// Determines if the top, left, and right borders should be drawn. private var hideBorders = false public var borderStrokeColor: UIColor = .mfSilver() @@ -40,27 +49,60 @@ import UIKit // MARK: - Property Observers //-------------------------------------------------- - public var showError = false { - didSet { - showError ? errorUI() : originalUI() + private var _isEnabled: Bool = true + private var _showError: Bool = false + private var _isLocked: Bool = false + private var _isSelected: Bool = false + + public var isEnabled: Bool { + get { return _isEnabled } + set (enabled) { + + _isEnabled = enabled + _isLocked = false + _isSelected = false + _showError = false + + fieldState = enabled ? .original : .disabled } } - public var isEnabled = true { - didSet { - isEnabled ? originalUI() : disabledUI() + public var showError: Bool { + get { return _showError } + set (error) { + + _showError = error + _isEnabled = true + _isLocked = false + _isSelected = false + + fieldState = error ? .error : .original } } - public var isLocked = false { - didSet { - isLocked ? lockedUI() : originalUI() + public var isLocked: Bool { + get { return _isLocked } + set (locked) { + + _isLocked = locked + _isEnabled = true + _isSelected = false + _showError = false + + fieldState = locked ? .locked : .original } } - public var isSelected = false { - didSet { - isSelected ? selectedUI() : originalUI() + public var isSelected: Bool { + get { return _isSelected } + set (selected) { + + _isSelected = selected + _isLocked = false + _isEnabled = true + _showError = false + + fieldState = selected ? .selected : .original } } @@ -93,7 +135,7 @@ import UIKit borderPath.removeAllPoints() - if !disableBorders && !hideBorders { + if !disableAllBorders && !hideBorders { // Brings the other half of the line inside the view to prevent cropping. let origin = bounds.origin let size = frame.size @@ -119,11 +161,22 @@ import UIKit } } + open override func reset() { + super.reset() + + isEnabled = true + _isLocked = false + _isSelected = false + _showError = false + + subviews.forEach { $0.removeFromSuperview() } + } + //-------------------------------------------------- // MARK: - Draw States //-------------------------------------------------- - public enum State { + public enum FieldState { case original case error case selected @@ -162,8 +215,8 @@ import UIKit open func errorUI() { isUserInteractionEnabled = true - borderStrokeColor = .mfPumpkin() hideBorders = false + borderStrokeColor = .mfPumpkin() bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor refreshUI(bottomBarSize: 4) } @@ -171,8 +224,8 @@ import UIKit open func selectedUI() { isUserInteractionEnabled = true - borderStrokeColor = .black hideBorders = false + borderStrokeColor = .black bottomBar?.backgroundColor = UIColor.black.cgColor refreshUI(bottomBarSize: 1) } @@ -180,8 +233,8 @@ import UIKit open func lockedUI() { isUserInteractionEnabled = false - borderStrokeColor = .clear hideBorders = true + borderStrokeColor = .clear bottomBar?.backgroundColor = UIColor.clear.cgColor refreshUI(bottomBarSize: 1) } @@ -189,15 +242,15 @@ import UIKit open func disabledUI() { isUserInteractionEnabled = false - borderStrokeColor = .mfSilver() hideBorders = false + borderStrokeColor = .mfSilver() bottomBar?.backgroundColor = UIColor.mfSilver().cgColor refreshUI(bottomBarSize: 1) } open func refreshUI(bottomBarSize: CGFloat? = nil) { - if !disableBorders { + if !disableAllBorders { let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) @@ -217,8 +270,8 @@ import UIKit guard let dictionary = json, !dictionary.isEmpty else { return } - if let disableBorders = dictionary["disableBorders"] as? Bool { - self.disableBorders = disableBorders + if let disableAllBorders = dictionary["disableAllBorders"] as? Bool { + self.disableAllBorders = disableAllBorders } } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 7dd8538f..237def93 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -38,8 +38,9 @@ @"caretButton": CaretButton.class, @"textField": TextEntryField.class, @"digitEntryField": DigitEntryField.class, + @"itemDropdownEntryField": ItemDropdownEntryField.class, + @"dateDropdownEntryField": DateDropdownEntryField.class, @"mdnEntryField" : MdnEntryField.class, - @"dropdownEntryField" : DropdownEntryField.class, @"checkbox" : Checkbox.class, @"checkboxWithLabel" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, From 8b5fe4a1f4bf91e06a1f90dd59d249802a1b51d3 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 21 Nov 2019 15:21:32 -0500 Subject: [PATCH 063/112] more changes. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 26 ++++++++-------- .../Atoms/TextFields/DigitEntryField.swift | 31 ++++++++++++------- MVMCoreUI/Atoms/TextFields/EntryField.swift | 3 -- .../TextFields/ItemDropdownEntryField.swift | 1 + .../Atoms/TextFields/MdnEntryField.swift | 6 ++-- MVMCoreUI/BaseClasses/TextField.swift | 8 ++--- .../Containers/views/FormFieldContainer.swift | 4 ++- MVMCoreUI/Molecules/Items/TableViewCell.swift | 14 +++++---- 8 files changed, 52 insertions(+), 41 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 79d4e554..37c30b22 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -8,12 +8,12 @@ import UIKit -@objc protocol DigitBoxDelegate { +@objc protocol DigitBoxProtocol { @objc optional func digitFieldDidDelete(_ textField: UITextField?) } -@objcMembers open class DigitBox: FormFieldContainer, UITextFieldDelegate { +@objcMembers open class DigitBox: FormFieldContainer, UITextFieldDelegate, TextFieldDidDeleteProtocol { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -61,7 +61,7 @@ import UIKit // MARK: - Delegate //-------------------------------------------------- - weak var digitBoxDelegate: DigitBoxDelegate? + weak var digitBoxDelegate: DigitBoxProtocol? //-------------------------------------------------- // MARK: - Constraints @@ -74,16 +74,16 @@ import UIKit // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { + @objc public override init(frame: CGRect) { super.init(frame: frame) setup() } - public convenience init() { + @objc public convenience init() { self.init(frame: .zero) } - required public init?(coder: NSCoder) { + @objc required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("DigitBox does not support xibs.") } @@ -92,7 +92,7 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - private func setup() { + @objc private func setup() { guard constraints.isEmpty else { return } @@ -101,6 +101,7 @@ import UIKit addSubview(digitField) digitField.delegate = self + digitField.didDeleteDelegate = self NSLayoutConstraint.activate([ digitField.heightAnchor.constraint(equalToConstant: 24), @@ -120,21 +121,20 @@ import UIKit layer.addSublayer(bottomBar) } updateView(MVMCoreUISplitViewController.getDetailViewWidth()) - digitField.addTarget(self, action:#selector(textfieldChanged) , for: .valueChanged) } - func textfieldChanged() { - + @objc public func textFieldDidDelete() { + digitBoxDelegate?.digitFieldDidDelete?(digitField) } - open override func layoutSubviews() { + @objc open override func layoutSubviews() { super.layoutSubviews() let barHeight: CGFloat = showError ? 4 : 1 bottomBar?.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) } - open override func reset() { + @objc open override func reset() { super.reset() digitField.text = nil @@ -144,7 +144,7 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - public override func updateView(_ size: CGFloat) { + @objc public override func updateView(_ size: CGFloat) { super.updateView(size) DispatchQueue.main.async { [weak self] in diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index ce53c0d1..c3e1f3b5 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -11,7 +11,7 @@ import UIKit /** * Subclass of TextEntryField as it is to use similar logic as a singular textField but appear separate.. */ -@objcMembers open class DigitEntryField: TextEntryField, DigitBoxDelegate { +@objcMembers open class DigitEntryField: TextEntryField, DigitBoxProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -122,7 +122,7 @@ import UIKit // MARK: - Delegate //-------------------------------------------------- - /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + /// Holds a reference to the 'delegating' class so this class can internally influence the TextField behavior as well. private weak var proprietorTextDelegate: UITextFieldDelegate? //-------------------------------------------------- @@ -140,6 +140,7 @@ import UIKit @objc public convenience init(numberOfDigits: Int) { self.init(frame: .zero) self.numberOfDigits = numberOfDigits + assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } @objc required public init?(coder: NSCoder) { @@ -156,7 +157,6 @@ import UIKit alignCenterHorizontal() isAccessibilityElement = false entryContainer.disableAllBorders = true - assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } @objc private func createDigitField() -> DigitBox { @@ -173,6 +173,8 @@ import UIKit var accessibleElements: [Any] = [titleLabel] + digitBoxes.forEach { $0.removeFromSuperview() } + if numberOfDigits > 0 { var digitBoxes = [DigitBox]() for _ in 0.. Bool { + @objc public func validate() -> Bool { if !shouldValidateMDN { let isValid = hasValidMDN() @@ -116,7 +116,7 @@ import MVMCore return true } - @objc func getContacts(_ sender: Any?) { + @objc public func getContacts(_ sender: Any?) { let picker = CNContactPickerViewController() picker.delegate = self @@ -183,7 +183,7 @@ import MVMCore proprietorTextDelegate?.textFieldDidEndEditing?(textField) - if validateAndColor() && isNationalMDN { + if validate() && isNationalMDN { textField.text = MVMCoreUIUtility.formatMdn(textField.text) } } diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift index c8321f4d..5b3a4963 100644 --- a/MVMCoreUI/BaseClasses/TextField.swift +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -8,12 +8,12 @@ import UIKit -protocol TextFieldDelegate { +public protocol TextFieldDidDeleteProtocol: class { func textFieldDidDelete() } -open class TextField: UITextField { +@objcMembers open class TextField: UITextField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -30,7 +30,7 @@ open class TextField: UITextField { //-------------------------------------------------- /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. - private weak var proprietorTextDelegate: UITextFieldDelegate? + public weak var didDeleteDelegate: TextFieldDidDeleteProtocol? //-------------------------------------------------- // MARK: - Initialization @@ -69,7 +69,7 @@ open class TextField: UITextField { open override func deleteBackward() { super.deleteBackward() -// proprietorTextDelegate?.textFieldDidDelete() + didDeleteDelegate?.textFieldDidDelete() } } diff --git a/MVMCoreUI/Containers/views/FormFieldContainer.swift b/MVMCoreUI/Containers/views/FormFieldContainer.swift index 1673d2fa..b890d62b 100644 --- a/MVMCoreUI/Containers/views/FormFieldContainer.swift +++ b/MVMCoreUI/Containers/views/FormFieldContainer.swift @@ -101,7 +101,7 @@ import UIKit _isLocked = false _isEnabled = true _showError = false - +    fieldState = selected ? .selected : .original } } @@ -155,6 +155,8 @@ import UIKit override open func setupView() { super.setupView() + isAccessibilityElement = false + isOpaque = false if let bottomBar = bottomBar { layer.addSublayer(bottomBar) diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index 13faeb42..0e303228 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -208,12 +208,14 @@ import UIKit /// Adds the standard mvm style caret to the accessory view @objc public func addCaretViewAccessory() { guard accessoryView == nil else { return } - let width: CGFloat = 6 - let height: CGFloat = 10 - caretView = CaretView(lineThickness: CaretView.thin) - caretView?.frame = CGRect(x: 0, y: 0, width: width, height: height) - caretViewWidthSizeObject = MFSizeObject(standardSize: width, standardiPadPortraitSize: 9) - caretViewHeightSizeObject = MFSizeObject(standardSize: height, standardiPadPortraitSize: 16) + caretView = CaretView(lineWidth: 1) + caretView?.size = .small(.vertical) + caretView?.setConstraints() + + if let size = caretView?.size?.dimensions() { + caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9) + caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16) + } accessoryView = caretView } From 64bda3cb1d4800f8fd0027d35f7265fa06dba98b Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 21 Nov 2019 16:40:34 -0500 Subject: [PATCH 064/112] wip. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 2 +- .../Atoms/TextFields/DigitEntryField.swift | 107 +++++++++--------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 37c30b22..d590b9e3 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -35,7 +35,7 @@ import UIKit private var previousSize: CGFloat = 0.0 //-------------------------------------------------- - // MARK: - Property Observer + // MARK: - Computed Properties //-------------------------------------------------- public override var showError: Bool { diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index c3e1f3b5..1d355c88 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -13,36 +13,35 @@ import UIKit */ @objcMembers open class DigitEntryField: TextEntryField, DigitBoxProtocol { //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- private(set) var numberOfDigits = 4 - public var switchFieldsAutomatically = false + + /// Monitors if fields are being selected internally. + private var switchFieldsAutomatically = false public var digitBoxes: [DigitBox] = [] var selectedDigitField: DigitBox? //-------------------------------------------------- - // MARK: - Property Observers + // MARK: - Computed Properties //-------------------------------------------------- public override var isEnabled: Bool { get { return super.isEnabled } set (enabled) { titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() - - digitBoxes.forEach { - $0.isEnabled = enabled - $0.isUserInteractionEnabled = enabled - $0.digitField.isEnabled = enabled - $0.digitField.textColor = enabled ? .black : .mfBattleshipGrey() - } + digitBoxes.forEach { $0.isEnabled = enabled } + super.isEnabled = enabled } } public override var showError: Bool { - didSet { - digitBoxes.forEach { $0.showError = self.showError } + get { return super.showError } + set (error) { + digitBoxes.forEach { $0.showError = error } + super.showError = error } } @@ -50,6 +49,7 @@ import UIKit get { return super.isLocked } set (locked) { digitBoxes.forEach { $0.isLocked = locked } + super.isLocked = locked } } @@ -109,6 +109,13 @@ import UIKit } } + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds a reference to the 'delegating' class so this class can internally influence the TextField behavior as well. + private weak var proprietorTextDelegate: UITextFieldDelegate? + /// If you're using a MFViewController, you must set this to it public override weak var uiTextFieldDelegate: UITextFieldDelegate? { get { return textField.delegate } @@ -118,19 +125,15 @@ import UIKit } } - //-------------------------------------------------- - // MARK: - Delegate - //-------------------------------------------------- - - /// Holds a reference to the 'delegating' class so this class can internally influence the TextField behavior as well. - private weak var proprietorTextDelegate: UITextFieldDelegate? - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @objc public override init(frame: CGRect) { super.init(frame: frame) + + isAccessibilityElement = false + entryContainer.disableAllBorders = true } @objc public convenience init() { @@ -152,12 +155,7 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - @objc public override func setupFieldContainerContent(_ container: UIView) { - - alignCenterHorizontal() - isAccessibilityElement = false - entryContainer.disableAllBorders = true - } + @objc public override func setupFieldContainerContent(_ container: UIView) {} @objc private func createDigitField() -> DigitBox { @@ -173,10 +171,11 @@ import UIKit var accessibleElements: [Any] = [titleLabel] - digitBoxes.forEach { $0.removeFromSuperview() } + resetDigitBoxes() if numberOfDigits > 0 { var digitBoxes = [DigitBox]() + for _ in 0.. Date: Fri, 22 Nov 2019 09:20:35 -0500 Subject: [PATCH 065/112] Better functioning selection. --- .../Atoms/TextFields/DigitEntryField.swift | 64 ++++++++----------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 1d355c88..85a9d812 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -22,7 +22,7 @@ import UIKit private var switchFieldsAutomatically = false public var digitBoxes: [DigitBox] = [] - var selectedDigitField: DigitBox? + var selectedDigitBox: DigitBox? //-------------------------------------------------- // MARK: - Computed Properties @@ -273,10 +273,8 @@ import UIKit var selectPreviousField = false - for (index, field) in Array(digitBoxes.reversed()).enumerated() { - - if field.digitField == currentTextField { - field.isSelected = false + for (index, box) in Array(digitBoxes.reversed()).enumerated() { + if box.digitField == currentTextField { if index == digitBoxes.count - 1 { return } else { @@ -284,12 +282,11 @@ import UIKit } } else if selectPreviousField { if !clear { - self.switchFieldsAutomatically = true + switchFieldsAutomatically = true } - field.digitField.becomeFirstResponder() - self.switchFieldsAutomatically = false - field.isSelected = true - UIAccessibility.post(notification: .layoutChanged, argument: field.digitField) + box.digitField.becomeFirstResponder() + switchFieldsAutomatically = false + UIAccessibility.post(notification: .layoutChanged, argument: box.digitField) return } } @@ -299,24 +296,21 @@ import UIKit var selectNextField = false - for (index, field) in digitBoxes.enumerated() { - - if field.digitField == currentTextField { + for (index, box) in digitBoxes.enumerated() { + if box.digitField == currentTextField { if index == digitBoxes.count - 1 { return } else { selectNextField = true - field.isSelected = false } } else if selectNextField { if !clear { - self.switchFieldsAutomatically = true + switchFieldsAutomatically = true } - field.digitField.becomeFirstResponder() - self.switchFieldsAutomatically = false - field.isSelected = true - UIAccessibility.post(notification: .layoutChanged, argument: field.digitField) + box.digitField.becomeFirstResponder() + switchFieldsAutomatically = false + UIAccessibility.post(notification: .layoutChanged, argument: box.digitField) return } } @@ -324,14 +318,14 @@ import UIKit @objc override func startEditing() { - selectedDigitField?.isSelected = true - selectedDigitField?.digitField.becomeFirstResponder() + selectedDigitBox?.isSelected = true + selectedDigitBox?.digitField.becomeFirstResponder() } @objc override open func resignFirstResponder() -> Bool { - selectedDigitField?.digitField.resignFirstResponder() - selectedDigitField?.isSelected = false + selectedDigitBox?.digitField.resignFirstResponder() + selectedDigitBox?.isSelected = false return true } @@ -407,17 +401,12 @@ extension DigitEntryField { textField.text = "" valueChanged() } - - for digitBox in digitBoxes { - - if digitBox.isSelected { - digitBox.isSelected = false - } - - if digitBox.digitField == textField { - selectedDigitField = digitBox - digitBox.isSelected = true - break + + digitBoxes.forEach { + if $0.digitField == textField { + selectedDigitBox = $0 + $0.isSelected = true + return } } @@ -426,9 +415,10 @@ extension DigitEntryField { @objc public func textFieldDidEndEditing(_ textField: UITextField) { - digitBoxes.forEach { box in - if box.digitField == textField { - box.isSelected = false + digitBoxes.forEach { + if $0.digitField == textField { + selectedDigitBox = nil + $0.isSelected = false return } } From 72553935c2495efa18aba40dba296c2e854f6bdd Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 22 Nov 2019 12:53:40 -0500 Subject: [PATCH 066/112] latestst state. --- MVMCoreUI.xcodeproj/project.pbxproj | 9 +- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 6 +- .../Atoms/TextFields/DigitEntryField.swift | 32 +++--- MVMCoreUI/Atoms/TextFields/EntryField.swift | 99 ++++++------------- .../Atoms/TextFields/MdnEntryField.swift | 18 ++-- .../Atoms/TextFields/TextEntryField.swift | 6 +- ...tainer.swift => EntryFieldContainer.swift} | 14 ++- 7 files changed, 73 insertions(+), 111 deletions(-) rename MVMCoreUI/Containers/views/{FormFieldContainer.swift => EntryFieldContainer.swift} (96%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 64e83406..3cec7248 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -45,7 +45,7 @@ 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; - 0ABD136B237B193A0081388D /* FormFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormFieldContainer.swift */; }; + 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; @@ -240,7 +240,7 @@ 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; - 0ABD136A237B193A0081388D /* FormFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldContainer.swift; sourceTree = ""; }; + 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; @@ -451,7 +451,7 @@ 0ABD1369237B18EE0081388D /* views */ = { isa = PBXGroup; children = ( - 0ABD136A237B193A0081388D /* FormFieldContainer.swift */, + 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */, ); path = views; sourceTree = ""; @@ -1139,7 +1139,6 @@ 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, - D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, @@ -1209,7 +1208,7 @@ 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, - 0ABD136B237B193A0081388D /* FormFieldContainer.swift in Sources */, + 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index d590b9e3..d104412a 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -9,11 +9,11 @@ import UIKit @objc protocol DigitBoxProtocol { - @objc optional func digitFieldDidDelete(_ textField: UITextField?) + @objc optional func digitFieldDidDelete(_ textField: TextField?) } -@objcMembers open class DigitBox: FormFieldContainer, UITextFieldDelegate, TextFieldDidDeleteProtocol { +@objcMembers open class DigitBox: EntryFieldContainer, UITextFieldDelegate, TextFieldDidDeleteProtocol { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -29,7 +29,7 @@ import UIKit }() //-------------------------------------------------- - // MARK: - Properties + // MARK: - Stored Properties //-------------------------------------------------- private var previousSize: CGFloat = 0.0 diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 85a9d812..97728ce0 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -57,7 +57,6 @@ import UIKit get { var string = "" digitBoxes.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" } - return !string.isEmpty ? string : nil } set { @@ -92,7 +91,6 @@ import UIKit get { var string = "" digitBoxes.forEach { string += $0.digitField.text ?? "" } - return string } set { @@ -133,17 +131,21 @@ import UIKit super.init(frame: frame) isAccessibilityElement = false - entryContainer.disableAllBorders = true + entryFieldContainer.disableAllBorders = true } @objc public convenience init() { self.init(frame: .zero) } - @objc public convenience init(numberOfDigits: Int) { + @objc public convenience init(numberOfDigits: Int, secureDigits: Bool = false) { self.init(frame: .zero) self.numberOfDigits = numberOfDigits assembleDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) + + if secureDigits { + setAsSecureTextEntry(true) + } } @objc required public init?(coder: NSCoder) { @@ -187,17 +189,17 @@ import UIKit for (index, box) in digitBoxes.enumerated() { accessibleElements.append(box) - entryContainer.addSubview(box) + entryFieldContainer.addSubview(box) - box.topAnchor.constraint(equalTo: entryContainer.topAnchor).isActive = true - entryContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true + box.topAnchor.constraint(equalTo: entryFieldContainer.topAnchor).isActive = true + entryFieldContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true if index == 0 { - box.leadingAnchor.constraint(equalTo: entryContainer.leadingAnchor).isActive = true + box.leadingAnchor.constraint(equalTo: entryFieldContainer.leadingAnchor).isActive = true } else if index == digitBoxes.count - 1 { box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true - entryContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true + entryFieldContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true } else { box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true @@ -217,7 +219,7 @@ import UIKit @objc open override func updateView(_ size: CGFloat) { super.updateView(size) - entryContainer.disableAllBorders = true + entryFieldContainer.disableAllBorders = true DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -232,7 +234,7 @@ import UIKit @objc open override func reset() { super.reset() - + resetDigitBoxes() } @@ -315,7 +317,7 @@ import UIKit } } } - + @objc override func startEditing() { selectedDigitBox?.isSelected = true @@ -390,7 +392,7 @@ extension DigitEntryField { return false } - @objc func digitFieldDidDelete(_ textField: UITextField?) { + @objc func digitFieldDidDelete(_ textField: TextField?) { selectPreviousDigitField(textField, clear: true) } @@ -459,6 +461,10 @@ extension DigitEntryField { assembleDigitFieldsView(size: MVMCoreUIUtility.getWidth()) + if let _ = dictionary["secureEntry"] as? Bool { + setAsSecureTextEntry(true) + } + if !dictionary.isEmpty{ for digitBox in digitBoxes { MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 68c9c20e..5771b300 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -26,10 +26,7 @@ import UIKit return label }() - public private(set) var entryContainer: FormFieldContainer = { - let view = FormFieldContainer() - return view - }() + public private(set) var entryFieldContainer = EntryFieldContainer() public private(set) var feedbackLabel: Label = { let label = Label() @@ -57,29 +54,18 @@ import UIKit /// Determines whther the feedback label will clear itself after user interaction or display update. // public var fixedFeedback: Bool = false - private var _isEnabled: Bool = true - private var _showError: Bool = false - private var _isLocked: Bool = false - private var _isSelected: Bool = false - //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- /// Toggles enabled (original) or disabled UI. public var isEnabled: Bool { - get { return _isEnabled } + get { return entryFieldContainer.isEnabled } set (enabled) { - - _isEnabled = enabled - _isLocked = false - _isSelected = false - _showError = false - DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.isEnabled = enabled + self.entryFieldContainer.isEnabled = enabled self.feedbackLabel.textColor = enabled ? .black : .mfSilver() self.titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() } @@ -88,18 +74,12 @@ import UIKit /// Toggles error or original UI. public var showError: Bool { - get { return _showError } + get { return entryFieldContainer.showError } set (error) { - - _showError = error - _isLocked = false - _isSelected = false - _isEnabled = true - DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.showError = error + self.entryFieldContainer.showError = error self.feedback = error ? self.errorMessage : nil } } @@ -107,44 +87,28 @@ import UIKit /// Toggles original or locked UI. public var isLocked: Bool { - get { return _isLocked } + get { return entryFieldContainer.isLocked } set (locked) { - - _isLocked = locked - _isEnabled = true - _isSelected = false - _showError = false - DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.isLocked = locked + self.entryFieldContainer.isLocked = locked } } } /// Toggles selected or original (unselected) UI. public var isSelected: Bool { - get { return _isSelected } + get { return entryFieldContainer.isSelected } set (selected) { - - _isSelected = selected - _isLocked = false - _isEnabled = true - _showError = false - DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.entryContainer.isSelected = selected + self.entryFieldContainer.isSelected = selected } } } - - //-------------------------------------------------- - // MARK: - Computed Properties for Outlets - //-------------------------------------------------- - + /// Sets the text of titleLabel public var title: String? { get { return titleLabel.text } @@ -157,7 +121,7 @@ import UIKit /// Override this to conveniently get/set the textfield(s). public var text: String? { get { return nil } - set { fatalError("You need to override EntryField's 'text' variable in your subclass.") } + set { fatalError("You MUST override EntryField's 'text' variable in your subclass.") } } /// Sets feedback text in the textField. @@ -166,7 +130,7 @@ import UIKit set (newFeedback) { feedbackLabel.text = newFeedback setAccessibilityString(newFeedback) - entryContainer.refreshUI() + entryFieldContainer.refreshUI() } } @@ -174,8 +138,8 @@ import UIKit // MARK: - Constraints //-------------------------------------------------- - public var entryContainerLeading: NSLayoutConstraint? - public var entryContainerTrailing: NSLayoutConstraint? + public var entryFieldContainerLeading: NSLayoutConstraint? + public var entryFieldContainerTrailing: NSLayoutConstraint? public var feedbackLabelTrailing: NSLayoutConstraint? public var feedbackLabelLeading: NSLayoutConstraint? @@ -233,19 +197,19 @@ import UIKit titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor) titleLabelLeading?.isActive = true - addSubview(entryContainer) - setupFieldContainerContent(entryContainer) + addSubview(entryFieldContainer) + setupFieldContainerContent(entryFieldContainer) - titleContainerDistance = entryContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) + titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) titleContainerDistance?.isActive = true - entryContainerLeading = entryContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - entryContainerLeading?.isActive = true - entryContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryContainer.trailingAnchor) - entryContainerTrailing?.isActive = true + entryFieldContainerLeading = entryFieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + entryFieldContainerLeading?.isActive = true + entryFieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryFieldContainer.trailingAnchor) + entryFieldContainerTrailing?.isActive = true addSubview(feedbackLabel) - feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryContainer.bottomAnchor, constant: PaddingOne) + feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryFieldContainer.bottomAnchor, constant: PaddingOne) feedbackContainerDistance?.isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true @@ -257,11 +221,11 @@ import UIKit @objc open override func layoutSubviews() { super.layoutSubviews() - entryContainer.refreshUI() + entryFieldContainer.refreshUI() } /// Method to override. - /// Intended to add the interactive content (i.e. textField) to the entryContainer. + /// Intended to add the interactive content (i.e. textField) to the entryFieldContainer. @objc open func setupFieldContainerContent(_ container: UIView) { // To be overridden by subclass. } @@ -271,21 +235,16 @@ import UIKit titleLabel.updateView(size) feedbackLabel.updateView(size) - entryContainer.updateView(size) + entryFieldContainer.updateView(size) } @objc open override func reset() { super.reset() - isEnabled = true - _isLocked = false - _isSelected = false - _showError = false - backgroundColor = .clear titleLabel.reset() feedbackLabel.reset() - entryContainer.reset() + entryFieldContainer.reset() titleLabel.textColor = .mfBattleshipGrey() } @@ -295,14 +254,14 @@ import UIKit @objc open override func setLeftPinConstant(_ constant: CGFloat) { - entryContainerLeading?.constant = constant + entryFieldContainerLeading?.constant = constant feedbackLabelLeading?.constant = constant titleLabelLeading?.constant = constant } @objc open override func setRightPinConstant(_ constant: CGFloat) { - entryContainerTrailing?.constant = constant + entryFieldContainerTrailing?.constant = constant feedbackLabelTrailing?.constant = constant titleLabelTrailing?.constant = constant } @@ -317,7 +276,7 @@ extension EntryField { guard let dictionary = json, !dictionary.isEmpty else { return } - entryContainer.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) + entryFieldContainer.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) if let titleText = dictionary[KeyTitle] as? String { title = titleText diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 7d279e54..67cdbb9a 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -29,6 +29,15 @@ import MVMCore /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. private weak var proprietorTextDelegate: UITextFieldDelegate? + /// If you're using a MFViewController, you must set this to it + public override weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { return textField.delegate } + set { + textField.delegate = self + proprietorTextDelegate = newValue + } + } + //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- @@ -39,15 +48,6 @@ import MVMCore set { text = MVMCoreUIUtility.formatMdn(newValue) } } - /// If you're using a MFViewController, you must set this to it - public override weak var uiTextFieldDelegate: UITextFieldDelegate? { - get { return textField.delegate } - set { - textField.delegate = self - proprietorTextDelegate = newValue - } - } - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 2e7c151b..44cf9572 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -52,7 +52,7 @@ import UIKit get { return super.isEnabled } set (enabled) { super.isEnabled = enabled - + DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -201,7 +201,7 @@ import UIKit @objc func dismissFieldInput(_ sender: Any?) { - _ = self.resignFirstResponder() + _ = resignFirstResponder() } public func defaultValidationBlock() { @@ -238,7 +238,7 @@ import UIKit if isValid { showError = false - entryContainer.bottomBar?.backgroundColor = UIColor.black.cgColor + entryFieldContainer.bottomBar?.backgroundColor = UIColor.black.cgColor } else if let errMessage = errorMessage { feedback = errMessage diff --git a/MVMCoreUI/Containers/views/FormFieldContainer.swift b/MVMCoreUI/Containers/views/EntryFieldContainer.swift similarity index 96% rename from MVMCoreUI/Containers/views/FormFieldContainer.swift rename to MVMCoreUI/Containers/views/EntryFieldContainer.swift index b890d62b..4300e37d 100644 --- a/MVMCoreUI/Containers/views/FormFieldContainer.swift +++ b/MVMCoreUI/Containers/views/EntryFieldContainer.swift @@ -1,5 +1,5 @@ // -// FormView.swift +// EntryFieldContainer.swift // MVMCoreUI // // Created by Kevin Christiano on 11/12/19. @@ -9,7 +9,7 @@ import UIKit -@objcMembers open class FormFieldContainer: View { +@objcMembers open class EntryFieldContainer: View { //-------------------------------------------------- // MARK: - Drawing Properties //-------------------------------------------------- @@ -101,7 +101,7 @@ import UIKit _isLocked = false _isEnabled = true _showError = false -    + fieldState = selected ? .selected : .original } } @@ -156,8 +156,8 @@ import UIKit super.setupView() isAccessibilityElement = false - isOpaque = false + if let bottomBar = bottomBar { layer.addSublayer(bottomBar) } @@ -185,7 +185,7 @@ import UIKit case locked case disabled - public func setStateUI(for formField: FormFieldContainer) { + public func setStateUI(for formField: EntryFieldContainer) { switch self { case .original: formField.originalUI() @@ -272,8 +272,6 @@ import UIKit guard let dictionary = json, !dictionary.isEmpty else { return } - if let disableAllBorders = dictionary["disableAllBorders"] as? Bool { - self.disableAllBorders = disableAllBorders - } + } } From dc8293b08b6bce9b2aa617c5fe63609410a139d3 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 22 Nov 2019 14:33:46 -0500 Subject: [PATCH 067/112] more refinement of errors. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 1 + .../Atoms/TextFields/DigitEntryField.swift | 3 +-- MVMCoreUI/Atoms/TextFields/EntryField.swift | 2 +- .../Atoms/TextFields/TextEntryField.swift | 18 ++++++++---------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index d104412a..8cdbcd4f 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -124,6 +124,7 @@ import UIKit } @objc public func textFieldDidDelete() { + digitBoxDelegate?.digitFieldDidDelete?(digitField) } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 97728ce0..1532e0a3 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -65,8 +65,7 @@ import UIKit for (index, field) in digitBoxes.enumerated() { if index < newValue.count { let indexChar = newValue.index(newValue.startIndex, offsetBy: index) - field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [ - NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) + field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 5771b300..2bea185e 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -52,7 +52,7 @@ import UIKit public var errorMessage: String? /// Determines whther the feedback label will clear itself after user interaction or display update. -// public var fixedFeedback: Bool = false + public var affixFeedback: Bool = false //-------------------------------------------------- // MARK: - Computed Properties diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 44cf9572..01636109 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -90,7 +90,9 @@ import UIKit //-------------------------------------------------- public var validationBlock: ((_ value: String?) -> Bool)? { - didSet { valueChanged() } + didSet { //valueChanged() + + } } public override var errorMessage: String? { @@ -214,21 +216,19 @@ import UIKit /// Executes on UITextField.textDidChangeNotification @objc func valueChanged() { - if !showError { + if !showError && !affixFeedback { feedback = "" } - - let previousValidity = isValid - + // If validation not set, input will always be valid isValid = validationBlock?(text) ?? true - if previousValidity && !isValid { + if !isValid { showError = true observingTextFieldDelegate?.isInvalid?(textfield: self) - } else if !previousValidity && isValid { - showError = false + } else if isValid { + isSelected = true observingTextFieldDelegate?.isValid?(textfield: self) } } @@ -240,8 +240,6 @@ import UIKit showError = false entryFieldContainer.bottomBar?.backgroundColor = UIColor.black.cgColor - } else if let errMessage = errorMessage { - feedback = errMessage } } From cfffbabe583e3025b34061c155bdfc25b9e53979 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 25 Nov 2019 12:27:32 -0500 Subject: [PATCH 068/112] identity and callback. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 8 ++++---- .../Atoms/TextFields/ItemDropdownEntryField.swift | 14 ++++++++++++++ MVMCoreUI/Atoms/TextFields/TextEntryField.swift | 6 +----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 1532e0a3..b78928fe 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -275,7 +275,7 @@ import UIKit var selectPreviousField = false for (index, box) in Array(digitBoxes.reversed()).enumerated() { - if box.digitField == currentTextField { + if box.digitField === currentTextField { if index == digitBoxes.count - 1 { return } else { @@ -298,7 +298,7 @@ import UIKit var selectNextField = false for (index, box) in digitBoxes.enumerated() { - if box.digitField == currentTextField { + if box.digitField === currentTextField { if index == digitBoxes.count - 1 { return } else { @@ -404,7 +404,7 @@ extension DigitEntryField { } digitBoxes.forEach { - if $0.digitField == textField { + if $0.digitField === textField { selectedDigitBox = $0 $0.isSelected = true return @@ -417,7 +417,7 @@ extension DigitEntryField { @objc public func textFieldDidEndEditing(_ textField: UITextField) { digitBoxes.forEach { - if $0.digitField == textField { + if $0.digitField === textField { selectedDigitBox = nil $0.isSelected = false return diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 49ce7145..1c48c7c7 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -22,6 +22,8 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true + public var observeDropdownChange: ((String)->())? + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -64,6 +66,8 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { @objc private func setInitialValueFromPicker() { + guard !pickerData.isEmpty else { return } + if setInitialValueInTextField, let pickerIndex = pickerView?.selectedRow(inComponent: 0) { text = pickerData[pickerIndex] } @@ -74,6 +78,16 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { setInitialValueFromPicker() } + + @objc override func endInputing() { + super.endInputing() + + guard !pickerData.isEmpty else { return } + + if let pickerIndex = pickerView?.selectedRow(inComponent: 0) { + observeDropdownChange?(pickerData[pickerIndex]) + } + } } // MARK:- Base Picker Delegate diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 01636109..b813bf9e 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -89,11 +89,7 @@ import UIKit // MARK: - Property Observers //-------------------------------------------------- - public var validationBlock: ((_ value: String?) -> Bool)? { - didSet { //valueChanged() - - } - } + public var validationBlock: ((_ value: String?) -> Bool)? public override var errorMessage: String? { didSet { From 3654b057733aa5943d438bce1229fbbc8f8e670f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 25 Nov 2019 16:35:28 -0500 Subject: [PATCH 069/112] removed unneeded method. mild changes. --- .../Atoms/TextFields/DigitEntryField.swift | 29 +++++++------------ .../Atoms/TextFields/TextEntryField.swift | 3 ++ 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index b78928fe..fcd1d65c 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -269,7 +269,7 @@ import UIKit return enteredValue.count > 0 && enteredValue.count == self.digitBoxes.count } } - + @objc public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { var selectPreviousField = false @@ -339,11 +339,6 @@ import UIKit } } } - - @objc open class func getEnabledDigitFields(_ textFieldToDetermine: [DigitBox]) -> [TextField]? { - - return textFieldToDetermine.filter { $0.isEnabled }.compactMap { $0.digitField } - } } // MARK: - TextField Delegate @@ -374,8 +369,8 @@ extension DigitEntryField { // One character, switch old value with new, select next textfield textField.text = string - selectNextDigitField(textField, clear: false) valueChanged() + selectNextDigitField(textField, clear: false) return false } else if replacementLength == 0 && oldLength == 1 { @@ -398,11 +393,6 @@ extension DigitEntryField { @objc public func textFieldDidBeginEditing(_ textField: UITextField) { - if !switchFieldsAutomatically { - textField.text = "" - valueChanged() - } - digitBoxes.forEach { if $0.digitField === textField { selectedDigitBox = $0 @@ -411,18 +401,19 @@ extension DigitEntryField { } } + if !switchFieldsAutomatically { + textField.text = "" + valueChanged() + } + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } @objc public func textFieldDidEndEditing(_ textField: UITextField) { - digitBoxes.forEach { - if $0.digitField === textField { - selectedDigitBox = nil - $0.isSelected = false - return - } - } + // There should only be one digitBox to deselect. + selectedDigitBox?.isSelected = false + selectedDigitBox = nil proprietorTextDelegate?.textFieldDidEndEditing?(textField) } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index b813bf9e..67361313 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -224,6 +224,9 @@ import UIKit observingTextFieldDelegate?.isInvalid?(textfield: self) } else if isValid { + if showError == true { + showError = false + } isSelected = true observingTextFieldDelegate?.isValid?(textfield: self) } From f2f9d5d0d72bab1d0ef3f334af66022e37098bba Mon Sep 17 00:00:00 2001 From: "Khan, Arshad" Date: Wed, 27 Nov 2019 21:12:41 +0530 Subject: [PATCH 070/112] Adding open to required functions, which need to override in MF component. Adding mapping for dropDown. --- MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift | 1 + MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift | 2 +- MVMCoreUI/Molecules/Items/TableViewCell.swift | 8 ++++---- MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 1c48c7c7..95030a66 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -120,6 +120,7 @@ extension ItemDropdownEntryField { if let options = dictionary["options"] as? [String] { pickerData = options + setPickerDelegates(delegate: self) } } } diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 4769b7e5..f5094508 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -11,7 +11,7 @@ import UIKit @objcMembers open class MoleculeTableViewCell: TableViewCell { // MARK: - MVMCoreUIMoleculeViewProtocol - public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard molecule == nil, let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule), let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) else { return } diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index 0e303228..b74743b0 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -110,7 +110,7 @@ import UIKit } // MARK: - MFViewProtocol - public func updateView(_ size: CGFloat) { + open func updateView(_ size: CGFloat) { MFStyler.setMarginsFor(self, size: size, defaultHorizontal: updateViewHorizontalDefaults, top: topMarginPadding, bottom: bottomMarginPadding) if accessoryView != nil { @@ -133,7 +133,7 @@ import UIKit molecule?.updateView(size) } - public func setupView() { + open func setupView() { selectionStyle = .none insetsLayoutMarginsFromSafeArea = false contentView.insetsLayoutMarginsFromSafeArea = false @@ -141,7 +141,7 @@ import UIKit } // MARK: - MVMCoreUIMoleculeViewProtocol - public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json guard let json = json else { return } @@ -186,7 +186,7 @@ import UIKit } } - public func reset() { + open func reset() { molecule?.reset?() updateViewHorizontalDefaults = true styleStandard() diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 51defe42..a092f214 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -37,6 +37,7 @@ @"caretButton": CaretButton.class, @"textField": TextEntryField.class, @"digitEntryField": DigitEntryField.class, + @"dropDown": ItemDropdownEntryField.class, @"itemDropdownEntryField": ItemDropdownEntryField.class, @"dateDropdownEntryField": DateDropdownEntryField.class, @"mdnEntryField" : MdnEntryField.class, From c999f6f777174f0dfcbc604d66741cdc52ca73f2 Mon Sep 17 00:00:00 2001 From: "Khan, Arshad" Date: Wed, 27 Nov 2019 21:43:42 +0530 Subject: [PATCH 071/112] opening few properties --- MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift | 6 +++--- MVMCoreUI/Atoms/TextFields/TextEntryField.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 95030a66..71a3b35b 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -14,10 +14,10 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { // MARK: - Properties //-------------------------------------------------- - public var pickerData: [String] = [] - public var pickerView: UIPickerView? + open var pickerData: [String] = [] + open var pickerView: UIPickerView? - public var componentsCount = 1 + open var componentsCount = 1 /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 67361313..5392c636 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -24,7 +24,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - public private(set) var textField: TextField = { + open private(set) var textField: TextField = { let textField = TextField(frame: .zero) textField.isAccessibilityElement = true textField.setContentCompressionResistancePriority(.required, for: .vertical) @@ -71,7 +71,7 @@ import UIKit } /// The text of this TextField. - public override var text: String? { + open override var text: String? { get { return textField.text } set { textField.text = newValue From fc7ef567e3f0188ae65cdf329bff70f67dff7ab9 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 2 Dec 2019 08:56:58 -0500 Subject: [PATCH 072/112] Revised Superclass. --- .../Atoms/TextFields/DigitEntryField.swift | 4 ---- MVMCoreUI/Atoms/TextFields/EntryField.swift | 24 +------------------ 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index fcd1d65c..1aa4d8a4 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -461,8 +461,4 @@ extension DigitEntryField { } } } - - @objc open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 115 - } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 2bea185e..eb0fca99 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -13,7 +13,7 @@ import UIKit * * When subclassing, be sure to override setupFieldContainerContent(). In this method you will setup all the content bound to the field container. */ -@objcMembers open class EntryField: ViewConstrainingView { +@objcMembers open class EntryField: View { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -247,24 +247,6 @@ import UIKit entryFieldContainer.reset() titleLabel.textColor = .mfBattleshipGrey() } - - //-------------------------------------------------- - // MARK: - Constraint Methods - //-------------------------------------------------- - - @objc open override func setLeftPinConstant(_ constant: CGFloat) { - - entryFieldContainerLeading?.constant = constant - feedbackLabelLeading?.constant = constant - titleLabelLeading?.constant = constant - } - - @objc open override func setRightPinConstant(_ constant: CGFloat) { - - entryFieldContainerTrailing?.constant = constant - feedbackLabelTrailing?.constant = constant - titleLabelTrailing?.constant = constant - } } // MARK: - Molecular @@ -303,10 +285,6 @@ extension EntryField { self.fieldKey = fieldKey } } - - @objc override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 115 - } } // MARK: - Form Validation From 6a3437008360e2be59ebd2368d64bd8a58e78e1c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 2 Dec 2019 09:50:30 -0500 Subject: [PATCH 073/112] returned func. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 4 ++++ MVMCoreUI/Atoms/TextFields/EntryField.swift | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 1aa4d8a4..9eaf5905 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -461,4 +461,8 @@ extension DigitEntryField { } } } + + @objc open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 115 + } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index eb0fca99..da309427 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -285,6 +285,10 @@ extension EntryField { self.fieldKey = fieldKey } } + + @objc open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 115 + } } // MARK: - Form Validation From 2319c4c70f7be499c8226b81cf17001e8c4913c1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 2 Dec 2019 10:43:08 -0500 Subject: [PATCH 074/112] overrdie applied. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 9eaf5905..fcd1d65c 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -462,7 +462,7 @@ extension DigitEntryField { } } - @objc open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + @objc open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 115 } } From 65586a7568bff8ddedef6098f893fd98e8a4a8b6 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 2 Dec 2019 11:05:08 -0500 Subject: [PATCH 075/112] Adding another location to execute external code. --- MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 71a3b35b..bf083af2 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -22,8 +22,12 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true + /// Closure passed here will run as picker changes items. public var observeDropdownChange: ((String)->())? + /// Closure passed here will run upon dismissing the selection picker. + public var observeDropdownSelection: ((String)->())? + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -85,7 +89,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { guard !pickerData.isEmpty else { return } if let pickerIndex = pickerView?.selectedRow(inComponent: 0) { - observeDropdownChange?(pickerData[pickerIndex]) + observeDropdownSelection?(pickerData[pickerIndex]) } } } @@ -107,6 +111,7 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { @objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { text = pickerData[row] + observeDropdownChange?(pickerData[row]) } } From 6b34b8840d673448d8eeddf0ec79c3dd5f6f5d74 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 4 Dec 2019 16:05:41 -0500 Subject: [PATCH 076/112] Adding values back. --- MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 51a07225..edb67b82 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -38,6 +38,10 @@ @"textField": MFTextField.class, @"dropDown": DropDown.class, @"digitTextField": MFDigitTextField.class, + @"digitEntryField": DigitEntryField.class, + @"textEntryField": TextEntryField.class, + @"itemDropdownEntryField": ItemDropdownEntryField.class, + @"dateDropdownEntryField": DateDropdownEntryField.class, @"checkbox": Checkbox.class, @"checkboxWithLabel": CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, From 420a8747113aafc419ffcd7445e549d1e9684fc5 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 5 Dec 2019 14:07:53 -0500 Subject: [PATCH 077/112] Making container tappabble to increase actionable surface area. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 63 ++++++++++--------- .../Atoms/TextFields/TextEntryField.swift | 3 + .../views/EntryFieldContainer.swift | 2 - 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 8cdbcd4f..dc2ad1c4 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -120,6 +120,10 @@ import UIKit if let bottomBar = bottomBar { layer.addSublayer(bottomBar) } + + let tap = UITapGestureRecognizer(target: self, action: #selector(callTextField)) + addGestureRecognizer(tap) + updateView(MVMCoreUISplitViewController.getDetailViewWidth()) } @@ -144,40 +148,41 @@ import UIKit //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - + + @objc public func callTextField() { + + digitField.becomeFirstResponder() + } + @objc public override func updateView(_ size: CGFloat) { super.updateView(size) - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } + if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(previousSize)) { + MFStyler.styleTextField(digitField) + digitField.font = MFFonts.mfFont55Rg(28) - if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(self.previousSize)) { - MFStyler.styleTextField(self.digitField) - self.digitField.font = MFFonts.mfFont55Rg(28) - - var width: CGFloat = 0 - var height: CGFloat = 0 - - let sizeObject = MFSizeObject(standardBlock: { - width = 39 - height = 44 - - }, smalliPhone: { - width = 35 - height = 38 - - }, standardiPadPortraitBlock: { - width = 59 - height = 74 - }) - - sizeObject?.performBlockBase(onSize: size) - self.widthConstraint?.constant = width - self.heightConstraint?.constant = height - self.previousSize = size - } + var width: CGFloat = 0 + var height: CGFloat = 0 - self.setNeedsLayout() + let sizeObject = MFSizeObject(standardBlock: { + width = 39 + height = 44 + + }, smalliPhone: { + width = 35 + height = 38 + + }, standardiPadPortraitBlock: { + width = 59 + height = 74 + }) + + sizeObject?.performBlockBase(onSize: size) + widthConstraint?.constant = width + heightConstraint?.constant = height + previousSize = size } + + setNeedsLayout() } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 5392c636..369cfae9 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -166,6 +166,9 @@ import UIKit textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16) textFieldTrailingConstraint?.isActive = true + let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + entryFieldContainer.addGestureRecognizer(tap) + accessibilityElements = [titleLabel, textField, feedbackLabel] } diff --git a/MVMCoreUI/Containers/views/EntryFieldContainer.swift b/MVMCoreUI/Containers/views/EntryFieldContainer.swift index 4300e37d..800f73af 100644 --- a/MVMCoreUI/Containers/views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/views/EntryFieldContainer.swift @@ -271,7 +271,5 @@ import UIKit self.delegateObject = delegateObject guard let dictionary = json, !dictionary.isEmpty else { return } - - } } From 59008f87d090e5f1ccf567050060b6338ccdf8ed Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 12 Dec 2019 16:47:07 -0500 Subject: [PATCH 078/112] Making further improvements from design specs. --- .../TextFields/BaseDropdownEntryField.swift | 3 -- MVMCoreUI/Atoms/TextFields/EntryField.swift | 8 ++---- .../Atoms/TextFields/TextEntryField.swift | 2 +- MVMCoreUI/BaseClasses/TextField.swift | 4 ++- .../views/EntryFieldContainer.swift | 28 +++++++++++++++---- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift index 11b6917f..d97d8943 100644 --- a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -69,9 +69,6 @@ import UIKit container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true - - let caretTap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) - dropDownCaretView.addGestureRecognizer(caretTap) } } diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index da309427..7690dd9b 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -50,9 +50,7 @@ import UIKit public var fieldKey: String? public var errorMessage: String? - - /// Determines whther the feedback label will clear itself after user interaction or display update. - public var affixFeedback: Bool = false + public var standardMessage: String? //-------------------------------------------------- // MARK: - Computed Properties @@ -67,7 +65,6 @@ import UIKit self.entryFieldContainer.isEnabled = enabled self.feedbackLabel.textColor = enabled ? .black : .mfSilver() - self.titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() } } } @@ -245,11 +242,10 @@ import UIKit titleLabel.reset() feedbackLabel.reset() entryFieldContainer.reset() - titleLabel.textColor = .mfBattleshipGrey() } } -// MARK: - Molecular +// MARK: - MVMCoreUIMoleculeViewProtocol extension EntryField { @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 369cfae9..3c2b4184 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -215,7 +215,7 @@ import UIKit /// Executes on UITextField.textDidChangeNotification @objc func valueChanged() { - if !showError && !affixFeedback { + if !showError { feedback = "" } diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift index 5b3a4963..b1a13854 100644 --- a/MVMCoreUI/BaseClasses/TextField.swift +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -53,6 +53,7 @@ public protocol TextFieldDidDeleteProtocol: class { public func initialSetup() { if !initialSetupPerformed { + tintColor = .black initialSetupPerformed = true setupView() } @@ -64,7 +65,8 @@ public protocol TextFieldDidDeleteProtocol: class { return .zero } - return super.caretRect(for: position) + let caretRect = super.caretRect(for: position) + return CGRect(origin: caretRect.origin, size: CGSize(width: 1, height: caretRect.height)) } open override func deleteBackward() { diff --git a/MVMCoreUI/Containers/views/EntryFieldContainer.swift b/MVMCoreUI/Containers/views/EntryFieldContainer.swift index 800f73af..fc04b0bf 100644 --- a/MVMCoreUI/Containers/views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/views/EntryFieldContainer.swift @@ -23,7 +23,7 @@ import UIKit return layer }() - /// Total control overthe drawn top,bottom, left and right borders. + /// Total control over the drawn top, bottom, left and right borders. public var disableAllBorders = false { didSet { bottomBar?.isHidden = disableAllBorders @@ -33,9 +33,8 @@ import UIKit private(set) var fieldState: FieldState = .original { didSet (oldState) { // Will not update if new state is the same as old. - if fieldState != oldState { - fieldState.setStateUI(for: self) - } + guard fieldState != oldState else { return } + fieldState.setStateUI(for: self) } } @@ -100,9 +99,12 @@ import UIKit _isSelected = selected _isLocked = false _isEnabled = true - _showError = false - fieldState = selected ? .selected : .original + if selected && showError { + fieldState = .selectedError + } else { + fieldState = selected ? .selected : .original + } } } @@ -181,11 +183,13 @@ import UIKit public enum FieldState { case original case error + case selectedError case selected case locked case disabled public func setStateUI(for formField: EntryFieldContainer) { + switch self { case .original: formField.originalUI() @@ -193,6 +197,9 @@ import UIKit case .error: formField.errorUI() + case .selectedError: + formField.selectedErrorUI() + case .selected: formField.selectedUI() @@ -223,6 +230,15 @@ import UIKit refreshUI(bottomBarSize: 4) } + open func selectedErrorUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .black + bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor + refreshUI(bottomBarSize: 4) + } + open func selectedUI() { isUserInteractionEnabled = true From 44ff5e9d3ce4dbfc9f50e64aa3b19cfb41e5f531 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 13 Dec 2019 12:54:43 -0500 Subject: [PATCH 079/112] Further development of Entry Field. --- .../TextFields/DateDropdownEntryField.swift | 7 +- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 9 +- MVMCoreUI/Atoms/TextFields/EntryField.swift | 4 +- .../Atoms/TextFields/MdnEntryField.swift | 27 +++--- .../Atoms/TextFields/TextEntryField.swift | 86 +++++++++++-------- MVMCoreUI/BaseClasses/View.swift | 4 +- .../views/EntryFieldContainer.swift | 18 ++-- 7 files changed, 86 insertions(+), 69 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index f0a7a368..4838e459 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -22,7 +22,6 @@ import UIKit return calendar }() - // TODO: Pull this out into Styler or some class akin to it. public var formatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .medium @@ -38,7 +37,6 @@ import UIKit @objc public override init(frame: CGRect) { super.init(frame: frame) - setup() } @objc public convenience init() { @@ -58,7 +56,8 @@ import UIKit // MARK: - Methods //-------------------------------------------------- - @objc private func setup() { + public override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) datePicker?.addTarget(self, action: #selector(pickerValueChanged), for: .valueChanged) @@ -81,7 +80,7 @@ import UIKit let pickedDate = datePicker?.date setTextWith(date: pickedDate) - textField.resignFirstResponder() + resignFirstResponder() return pickedDate } diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index dc2ad1c4..ffc1fa01 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -34,6 +34,9 @@ import UIKit private var previousSize: CGFloat = 0.0 + // Default dimensions of the DigitBox + static let size: CGSize = CGSize(width: 39, height: 44) + //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- @@ -97,7 +100,7 @@ import UIKit guard constraints.isEmpty else { return } translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .white + backgroundColor = .clear addSubview(digitField) digitField.delegate = self @@ -112,9 +115,9 @@ import UIKit digitField.centerYAnchor.constraint(equalTo: centerYAnchor), digitField.centerXAnchor.constraint(equalTo: centerXAnchor)]) - widthConstraint = widthAnchor.constraint(equalToConstant: 39) + widthConstraint = widthAnchor.constraint(equalToConstant: DigitBox.size.width) widthConstraint?.isActive = true - heightConstraint = heightAnchor.constraint(equalToConstant: 44) + heightConstraint = heightAnchor.constraint(equalToConstant: DigitBox.size.height) heightConstraint?.isActive = true if let bottomBar = bottomBar { diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 7690dd9b..c790483f 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -105,7 +105,7 @@ import UIKit } } } - + /// Sets the text of titleLabel public var title: String? { get { return titleLabel.text } @@ -177,10 +177,10 @@ import UIKit /// Initial configuration of class and view. @objc final public override func setupView() { + super.setupView() guard subviews.isEmpty else { return } - translatesAutoresizingMaskIntoConstraints = false isAccessibilityElement = false setContentCompressionResistancePriority(.required, for: .vertical) accessibilityElements = [titleLabel, feedbackLabel] diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 67cdbb9a..6911c8c7 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -97,23 +97,22 @@ import MVMCore return MVMCoreUIUtility.validateInternationalMDNString(MDN) } - @objc public func validate() -> Bool { + @objc public override func validateTextField() -> Bool { - if !shouldValidateMDN { - let isValid = hasValidMDN() + guard !shouldValidateMDN else { return true } + + let isValid = hasValidMDN() + + if isValid { + showError = false - if isValid { - showError = false - } else { - errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") - showError = true - UIAccessibility.post(notification: .layoutChanged, argument: textField) - } - - return isValid + } else { + errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") + showError = true + UIAccessibility.post(notification: .layoutChanged, argument: textField) } - return true + return isValid } @objc public func getContacts(_ sender: Any?) { @@ -183,7 +182,7 @@ import MVMCore proprietorTextDelegate?.textFieldDidEndEditing?(textField) - if validate() && isNationalMDN { + if validateTextField() && isNationalMDN { textField.text = MVMCoreUIUtility.formatMdn(textField.text) } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 3c2b4184..0f85c47f 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -42,7 +42,13 @@ import UIKit /// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property. public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.black, .mfSilver()) - public var observingForChange: Bool = false + private var observingForChange: Bool = false + + /// Validate on each entry in the textField. Default: false + public var validateEachCharacter: Bool = false + + /// Validate when user resigns editing. Default: true + public var validateWhenDoneEditing: Bool = true //-------------------------------------------------- // MARK: - Computed Properties @@ -75,7 +81,6 @@ import UIKit get { return textField.text } set { textField.text = newValue - valueChanged() } } @@ -101,7 +106,7 @@ import UIKit // 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. + /// The delegate and block for validation. Validates if the text that the user has entered. public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? { didSet { if observingTextFieldDelegate != nil && !observingForChange { @@ -193,33 +198,30 @@ import UIKit // MARK: - Observing for Change (TextFieldDelegate) //-------------------------------------------------- + public func defaultValidationBlock() { + + validationBlock = { enteredValue in + guard let enteredValue = enteredValue else { return false } + return enteredValue.count > 0 + } + } + + @discardableResult @objc override open func resignFirstResponder() -> Bool { + if validateWhenDoneEditing { + validateTextField() + } + textField.resignFirstResponder() isSelected = false return true } - @objc func dismissFieldInput(_ sender: Any?) { - - _ = resignFirstResponder() - } - - public func defaultValidationBlock() { - - validationBlock = { enteredValue in - return (enteredValue?.count ?? 0) > 0 - } - } - - /// Executes on UITextField.textDidChangeNotification - @objc func valueChanged() { - - if !showError { - feedback = "" - } + /// Validates the text of the entry field. + @discardableResult + @objc public func validateTextField() -> Bool { - // If validation not set, input will always be valid isValid = validationBlock?(text) ?? true if !isValid { @@ -227,22 +229,10 @@ import UIKit observingTextFieldDelegate?.isInvalid?(textfield: self) } else if isValid { - if showError == true { - showError = false - } - isSelected = true observingTextFieldDelegate?.isValid?(textfield: self) } - } - - /// Executes on UITextField.textDidEndEditingNotification - @objc func endInputing() { - if isValid { - showError = false - entryFieldContainer.bottomBar?.backgroundColor = UIColor.black.cgColor - - } + return isValid } /// Executes on UITextField.textDidBeginEditingNotification @@ -251,9 +241,33 @@ import UIKit isSelected = true textField.becomeFirstResponder() } + + /// Executes on UITextField.textDidChangeNotification (every character entry) + @objc func valueChanged() { + + guard validateEachCharacter else { return } + + validateTextField() + } + + /// Executes on UITextField.textDidEndEditingNotification + @objc func endInputing() { + + if isValid { + showError = false + entryFieldContainer.bottomBar?.backgroundColor = UIColor.black.cgColor + } + + resignFirstResponder() + } + + @objc func dismissFieldInput(_ sender: Any?) { + + resignFirstResponder() + } } -// MARK: - Molecular +// MARK: - MVMCoreUIMoleculeViewProtocol extension TextEntryField { @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/BaseClasses/View.swift b/MVMCoreUI/BaseClasses/View.swift index 8ae475b9..515dc0eb 100644 --- a/MVMCoreUI/BaseClasses/View.swift +++ b/MVMCoreUI/BaseClasses/View.swift @@ -44,7 +44,7 @@ import UIKit } } -/// MARK:- MVMCoreViewProtocol +// MARK:- MVMCoreViewProtocol extension View: MVMCoreViewProtocol { open func updateView(_ size: CGFloat) {} @@ -56,7 +56,7 @@ extension View: MVMCoreViewProtocol { } } -/// MARK:- MVMCoreUIMoleculeViewProtocol +// MARK:- MVMCoreUIMoleculeViewProtocol extension View: MVMCoreUIMoleculeViewProtocol { open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Containers/views/EntryFieldContainer.swift b/MVMCoreUI/Containers/views/EntryFieldContainer.swift index fc04b0bf..00c391a6 100644 --- a/MVMCoreUI/Containers/views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/views/EntryFieldContainer.swift @@ -33,8 +33,9 @@ import UIKit private(set) var fieldState: FieldState = .original { didSet (oldState) { // Will not update if new state is the same as old. - guard fieldState != oldState else { return } - fieldState.setStateUI(for: self) + if fieldState != oldState { + fieldState.setStateUI(for: self) + } } } @@ -100,8 +101,8 @@ import UIKit _isLocked = false _isEnabled = true - if selected && showError { - fieldState = .selectedError + if _showError { + fieldState = selected ? .selectedError : .error } else { fieldState = selected ? .selected : .original } @@ -174,6 +175,7 @@ import UIKit _showError = false subviews.forEach { $0.removeFromSuperview() } + borderPath.removeAllPoints() } //-------------------------------------------------- @@ -277,10 +279,10 @@ import UIKit layoutIfNeeded() } } - - //-------------------------------------------------- - // MARK: - Molecular - //-------------------------------------------------- +} + +// MARK:- MVMCoreUIMoleculeViewProtocol +extension EntryFieldContainer { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) From bcddc0b31145f6707ed5ac4be1f37aa2819c71b6 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 16 Dec 2019 08:53:45 -0500 Subject: [PATCH 080/112] Adjusted validation logic. --- .../TextFields/BaseDropdownEntryField.swift | 2 +- .../TextFields/DateDropdownEntryField.swift | 3 +- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 4 +- .../Atoms/TextFields/DigitEntryField.swift | 38 +++++++++---------- .../TextFields/ItemDropdownEntryField.swift | 8 ++-- .../Atoms/TextFields/MdnEntryField.swift | 3 +- .../Atoms/TextFields/TextEntryField.swift | 5 ++- 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift index d97d8943..e970be94 100644 --- a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -72,7 +72,7 @@ import UIKit } } -// MARK: - Molecular +// MARK: - MVMCoreUIMoleculeViewProtocol extension BaseDropdownEntryField { @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 4838e459..274626c1 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -96,6 +96,7 @@ import UIKit } @objc override func dismissFieldInput(_ sender: Any?) { + setTextWith(date: datePicker?.date) super.dismissFieldInput(sender) } @@ -106,7 +107,7 @@ import UIKit } } -// MARK: - Molecular +// MARK: - MVMCoreUIMoleculeViewProtocol extension DateDropdownEntryField { @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index ffc1fa01..3ec3d4f1 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -79,7 +79,6 @@ import UIKit @objc public override init(frame: CGRect) { super.init(frame: frame) - setup() } @objc public convenience init() { @@ -95,7 +94,8 @@ import UIKit // MARK: - Lifecycle //-------------------------------------------------- - @objc private func setup() { + open override func setupView() { + super.setupView() guard constraints.isEmpty else { return } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index fcd1d65c..876c2ed7 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -31,7 +31,6 @@ import UIKit public override var isEnabled: Bool { get { return super.isEnabled } set (enabled) { - titleLabel.textColor = enabled ? .mfBattleshipGrey() : .mfSilver() digitBoxes.forEach { $0.isEnabled = enabled } super.isEnabled = enabled } @@ -72,15 +71,11 @@ import UIKit // If there is already text in the textfield, set the placeholder label below. if let text = text, !text.isEmpty && !showError { feedback = placeholder + } else if !showError { feedback = "" } - /* - * 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") ?? "") } } @@ -101,8 +96,6 @@ import UIKit field.digitField.text = String(newValue[indexChar]) } } - - valueChanged() } } @@ -128,9 +121,6 @@ import UIKit @objc public override init(frame: CGRect) { super.init(frame: frame) - - isAccessibilityElement = false - entryFieldContainer.disableAllBorders = true } @objc public convenience init() { @@ -156,7 +146,11 @@ import UIKit // MARK: - Setup //-------------------------------------------------- - @objc public override func setupFieldContainerContent(_ container: UIView) {} + @objc public override func setupFieldContainerContent(_ container: UIView) { + + isAccessibilityElement = false + entryFieldContainer.disableAllBorders = true + } @objc private func createDigitField() -> DigitBox { @@ -304,7 +298,6 @@ import UIKit } else { selectNextField = true } - } else if selectNextField { if !clear { switchFieldsAutomatically = true @@ -325,8 +318,12 @@ import UIKit @objc override open func resignFirstResponder() -> Bool { - selectedDigitBox?.digitField.resignFirstResponder() + if validateWhenDoneEditing { + validateTextField() + } + selectedDigitBox?.isSelected = false + selectedDigitBox?.digitField.resignFirstResponder() return true } @@ -369,14 +366,12 @@ extension DigitEntryField { // One character, switch old value with new, select next textfield textField.text = string - valueChanged() selectNextDigitField(textField, clear: false) return false } else if replacementLength == 0 && oldLength == 1 { // Non empty cell, clear and stay. textField.text = "" - valueChanged() return false } @@ -388,7 +383,7 @@ extension DigitEntryField { @objc func digitFieldDidDelete(_ textField: TextField?) { - selectPreviousDigitField(textField, clear: true) + selectPreviousDigitField(textField, clear: false) } @objc public func textFieldDidBeginEditing(_ textField: UITextField) { @@ -403,7 +398,6 @@ extension DigitEntryField { if !switchFieldsAutomatically { textField.text = "" - valueChanged() } proprietorTextDelegate?.textFieldDidBeginEditing?(textField) @@ -415,12 +409,16 @@ extension DigitEntryField { selectedDigitBox?.isSelected = false selectedDigitBox = nil + if !switchFieldsAutomatically && validateWhenDoneEditing { + validateTextField() + } + proprietorTextDelegate?.textFieldDidEndEditing?(textField) } @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { - selectPreviousDigitField(textField, clear: false) + selectPreviousDigitField(textField, clear: true) return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true } @@ -436,7 +434,7 @@ extension DigitEntryField { } } -// MARK: - Molecular +// MARK: - MVMCoreUIMoleculeViewProtocol extension DigitEntryField { @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index bf083af2..b07d90d6 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -34,7 +34,6 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { @objc public override init(frame: CGRect) { super.init(frame: frame) - setup() } @objc public convenience init() { @@ -53,8 +52,9 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - - @objc private func setup() { + + @objc open override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) textField.hideBlinkingCaret = true @@ -115,7 +115,7 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { } } -// MARK: - Molecular +// MARK: - MVMCoreUIMoleculeViewProtocol extension ItemDropdownEntryField { @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 6911c8c7..3612f5a2 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -147,7 +147,7 @@ import MVMCore } text = unformattedMDN - _ = textFieldShouldReturn(textField) + textFieldShouldReturn(textField) textFieldDidEndEditing(textField) } } @@ -156,6 +156,7 @@ import MVMCore // MARK: - Implemented TextField Delegate //-------------------------------------------------- + @discardableResult @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 0f85c47f..16ff277a 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -228,7 +228,8 @@ import UIKit showError = true observingTextFieldDelegate?.isInvalid?(textfield: self) - } else if isValid { + } else { + showError = false observingTextFieldDelegate?.isValid?(textfield: self) } @@ -242,7 +243,7 @@ import UIKit textField.becomeFirstResponder() } - /// Executes on UITextField.textDidChangeNotification (every character entry) + /// Executes on UITextField.textDidChangeNotification (each character entry) @objc func valueChanged() { guard validateEachCharacter else { return } From 1fec5464affd7bf433fbfd16a152f0e8fe1fdbcc Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 16 Dec 2019 10:02:41 -0500 Subject: [PATCH 081/112] feedback independence. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 2 +- MVMCoreUI/Atoms/TextFields/EntryField.swift | 12 +++++++++++- MVMCoreUI/Atoms/TextFields/TextEntryField.swift | 10 +++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 876c2ed7..7e791551 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -22,7 +22,7 @@ import UIKit private var switchFieldsAutomatically = false public var digitBoxes: [DigitBox] = [] - var selectedDigitBox: DigitBox? + private var selectedDigitBox: DigitBox? //-------------------------------------------------- // MARK: - Computed Properties diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index c790483f..64c0a2c4 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -77,7 +77,7 @@ import UIKit guard let self = self else { return } self.entryFieldContainer.showError = error - self.feedback = error ? self.errorMessage : nil + self.feedback = error ? self.errorMessage : self.standardMessage } } } @@ -243,6 +243,11 @@ import UIKit feedbackLabel.reset() entryFieldContainer.reset() } + + open func feedbackStandardMessage(_ message: String? = nil) { + + feedback = message ?? standardMessage + } } // MARK: - MVMCoreUIMoleculeViewProtocol @@ -268,6 +273,11 @@ extension EntryField { errorMessage = errMessage } + if let feedback = dictionary["feedback"] as? String { + self.standardMessage = feedback + feedbackStandardMessage() + } + if let isLocked = dictionary["isLocked"] as? Bool { self.isLocked = isLocked } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 16ff277a..2c6df1ee 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -224,13 +224,13 @@ import UIKit isValid = validationBlock?(text) ?? true - if !isValid { - showError = true - observingTextFieldDelegate?.isInvalid?(textfield: self) - - } else { + if isValid { showError = false observingTextFieldDelegate?.isValid?(textfield: self) + + } else { + showError = true + observingTextFieldDelegate?.isInvalid?(textfield: self) } return isValid From d8030b178f5db3732815de70b3cc81db60101b7c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 17 Dec 2019 14:07:59 -0500 Subject: [PATCH 082/112] removed unneeded statement. --- MVMCoreUI/Atoms/TextFields/EntryField.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 64c0a2c4..2b30d300 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -126,7 +126,6 @@ import UIKit get { return feedbackLabel.text } set (newFeedback) { feedbackLabel.text = newFeedback - setAccessibilityString(newFeedback) entryFieldContainer.refreshUI() } } From 37ddf23278ca8e68ac67e7b719432a2a86f76be9 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 18 Dec 2019 16:12:09 -0500 Subject: [PATCH 083/112] Latest changes to make it better. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 26 ++-- .../Atoms/TextFields/DigitEntryField.swift | 130 ++++++++---------- MVMCoreUI/Atoms/TextFields/EntryField.swift | 66 ++++----- .../TextFields/ItemDropdownEntryField.swift | 2 +- .../Atoms/TextFields/TextEntryField.swift | 6 + .../views/EntryFieldContainer.swift | 18 +-- 6 files changed, 116 insertions(+), 132 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 3ec3d4f1..46f17ca6 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -24,6 +24,7 @@ import UIKit textField.setContentCompressionResistancePriority(.required, for: .vertical) textField.setContentCompressionResistancePriority(.required, for: .horizontal) textField.textAlignment = .center + textField.font = MFStyler.fontForTextField() textField.keyboardType = .numberPad return textField }() @@ -72,6 +73,7 @@ import UIKit private weak var widthConstraint: NSLayoutConstraint? private weak var heightConstraint: NSLayoutConstraint? + private weak var digitFieldHeight: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Initializers @@ -106,12 +108,14 @@ import UIKit digitField.delegate = self digitField.didDeleteDelegate = self + digitFieldHeight = digitField.heightAnchor.constraint(equalToConstant: 24) + digitFieldHeight?.isActive = true + NSLayoutConstraint.activate([ - digitField.heightAnchor.constraint(equalToConstant: 24), - digitField.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), - digitField.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), - bottomAnchor.constraint(greaterThanOrEqualTo: digitField.bottomAnchor), - trailingAnchor.constraint(greaterThanOrEqualTo: digitField.trailingAnchor), + digitField.topAnchor.constraint(equalTo: topAnchor, constant: PaddingOne), + digitField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: PaddingOne), + bottomAnchor.constraint(equalTo: digitField.bottomAnchor, constant: PaddingOne), + trailingAnchor.constraint(equalTo: digitField.trailingAnchor, constant: PaddingOne), digitField.centerYAnchor.constraint(equalTo: centerYAnchor), digitField.centerXAnchor.constraint(equalTo: centerXAnchor)]) @@ -145,7 +149,8 @@ import UIKit @objc open override func reset() { super.reset() - digitField.text = nil + backgroundColor = .clear + digitField.font = MFStyler.fontForTextField() } //-------------------------------------------------- @@ -161,28 +166,31 @@ import UIKit super.updateView(size) if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(previousSize)) { - MFStyler.styleTextField(digitField) - digitField.font = MFFonts.mfFont55Rg(28) - + var width: CGFloat = 0 var height: CGFloat = 0 + var pointSize: CGFloat = 13 let sizeObject = MFSizeObject(standardBlock: { width = 39 height = 44 + pointSize = 16 }, smalliPhone: { width = 35 height = 38 + pointSize = 13 }, standardiPadPortraitBlock: { width = 59 height = 74 + pointSize = 32 }) sizeObject?.performBlockBase(onSize: size) widthConstraint?.constant = width heightConstraint?.constant = height + digitField.font = MFFonts.mfFont55Rg(pointSize) previousSize = size } diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 7e791551..3dd0b014 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -16,7 +16,53 @@ import UIKit // MARK: - Stored Properties //-------------------------------------------------- - private(set) var numberOfDigits = 4 + private(set) var numberOfDigits = 4 { + didSet { + guard entryFieldContainer.subviews.isEmpty || oldValue != numberOfDigits else { return } + + var accessibleElements: [Any] = [titleLabel] + + if numberOfDigits > 0 { + var digitBoxes = [DigitBox]() + + for _ in 0.. 0 { - var digitBoxes = [DigitBox]() - - for _ in 0.. 0 && enteredValue.count == self.digitBoxes.count } } - + @objc public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { var selectPreviousField = false @@ -310,6 +295,10 @@ import UIKit } } + //-------------------------------------------------- + // MARK: - Observing TextField Changes + //-------------------------------------------------- + @objc override func startEditing() { selectedDigitBox?.isSelected = true @@ -442,12 +431,7 @@ extension DigitEntryField { guard let dictionary = json else { return } - let digits = dictionary["digits"] as? Int ?? 4 - if digits != numberOfDigits { - numberOfDigits = digits - } - - assembleDigitFieldsView(size: MVMCoreUIUtility.getWidth()) + numberOfDigits = dictionary["digits"] as? Int ?? 4 if let _ = dictionary["secureEntry"] as? Bool { setAsSecureTextEntry(true) diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 2b30d300..8756d4f5 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -28,6 +28,7 @@ import UIKit public private(set) var entryFieldContainer = EntryFieldContainer() + /// Provides contextual information on the TextField. public private(set) var feedbackLabel: Label = { let label = Label() label.font = MFStyler.fontForTextFieldUnderLabel() @@ -50,7 +51,13 @@ import UIKit public var fieldKey: String? public var errorMessage: String? - public var standardMessage: String? + public var standardMessage: String? { + didSet { + if !showError { + feedback = standardMessage + } + } + } //-------------------------------------------------- // MARK: - Computed Properties @@ -60,12 +67,8 @@ import UIKit public var isEnabled: Bool { get { return entryFieldContainer.isEnabled } set (enabled) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.entryFieldContainer.isEnabled = enabled - self.feedbackLabel.textColor = enabled ? .black : .mfSilver() - } + self.feedbackLabel.textColor = enabled ? .black : .mfSilver() + self.entryFieldContainer.isEnabled = enabled } } @@ -73,12 +76,8 @@ import UIKit public var showError: Bool { get { return entryFieldContainer.showError } set (error) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.entryFieldContainer.showError = error - self.feedback = error ? self.errorMessage : self.standardMessage - } + self.feedback = error ? self.errorMessage : self.standardMessage + self.entryFieldContainer.showError = error } } @@ -86,11 +85,7 @@ import UIKit public var isLocked: Bool { get { return entryFieldContainer.isLocked } set (locked) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.entryFieldContainer.isLocked = locked - } + self.entryFieldContainer.isLocked = locked } } @@ -98,11 +93,7 @@ import UIKit public var isSelected: Bool { get { return entryFieldContainer.isSelected } set (selected) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.entryFieldContainer.isSelected = selected - } + self.entryFieldContainer.isSelected = selected } } @@ -126,7 +117,12 @@ import UIKit get { return feedbackLabel.text } set (newFeedback) { feedbackLabel.text = newFeedback - entryFieldContainer.refreshUI() + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.entryFieldContainer.refreshUI() + } } } @@ -194,6 +190,7 @@ import UIKit titleLabelLeading?.isActive = true addSubview(entryFieldContainer) + entryFieldContainer.setContentCompressionResistancePriority(.required, for: .vertical) setupFieldContainerContent(entryFieldContainer) titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) @@ -238,15 +235,13 @@ import UIKit super.reset() backgroundColor = .clear - titleLabel.reset() - feedbackLabel.reset() + isAccessibilityElement = false + titleLabel.font = MFStyler.fontB3() + titleLabel.textColor = .mfBattleshipGrey() + feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() + feedbackLabel.textColor = .black entryFieldContainer.reset() } - - open func feedbackStandardMessage(_ message: String? = nil) { - - feedback = message ?? standardMessage - } } // MARK: - MVMCoreUIMoleculeViewProtocol @@ -268,13 +263,12 @@ extension EntryField { isEnabled = false } - if let errMessage = dictionary[KeyErrorMessage] as? String { - errorMessage = errMessage - } - if let feedback = dictionary["feedback"] as? String { self.standardMessage = feedback - feedbackStandardMessage() + } + + if let errMessage = dictionary[KeyErrorMessage] as? String { + errorMessage = errMessage } if let isLocked = dictionary["isLocked"] as? Bool { diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index b07d90d6..3e753ee1 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -52,7 +52,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - + @objc open override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 2c6df1ee..d82da76d 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -184,6 +184,12 @@ import UIKit layoutIfNeeded() } + open override func reset() { + super.reset() + + textField.font = MFStyler.fontForTextField() + } + @objc deinit { setBothTextDelegates(to: nil) } diff --git a/MVMCoreUI/Containers/views/EntryFieldContainer.swift b/MVMCoreUI/Containers/views/EntryFieldContainer.swift index 00c391a6..dd7c79f2 100644 --- a/MVMCoreUI/Containers/views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/views/EntryFieldContainer.swift @@ -34,7 +34,11 @@ import UIKit didSet (oldState) { // Will not update if new state is the same as old. if fieldState != oldState { - fieldState.setStateUI(for: self) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.fieldState.setStateUI(for: self) + } } } } @@ -166,18 +170,6 @@ import UIKit } } - open override func reset() { - super.reset() - - isEnabled = true - _isLocked = false - _isSelected = false - _showError = false - - subviews.forEach { $0.removeFromSuperview() } - borderPath.removeAllPoints() - } - //-------------------------------------------------- // MARK: - Draw States //-------------------------------------------------- From cf41609210a229a0e5d488bb758289554bd1b612 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 19 Dec 2019 16:09:41 -0500 Subject: [PATCH 084/112] correcting alignment func. --- MVMCoreUI/Atoms/Buttons/CaretButton.swift | 2 +- MVMCoreUI/Atoms/Views/CaretView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index 1bee754f..7ffd8749 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -135,7 +135,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI return true } - open func alignment() -> UIStackView.Alignment { + open func horizontalAlignment() -> UIStackView.Alignment { return .leading } diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index dbb9d1c0..2534c346 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -211,6 +211,6 @@ extension CaretView: MVMCoreUIViewConstrainingProtocol { } open func horizontalAlignment() -> UIStackView.Alignment { - return UIStackView.Alignment.leading; + return .leading } } From d2162cb9b6324585cb764f2bb15914b9b000f3d3 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 20 Dec 2019 08:43:13 -0500 Subject: [PATCH 085/112] removed override to conform. --- MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 6991adef..229efe2a 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -80,7 +80,7 @@ setupView() } - public convenience override init() { + public convenience init() { self.init(frame: .zero) } From dc201022ea8fe17cf67ffcd418a359ab6b07877f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 3 Jan 2020 14:15:22 -0500 Subject: [PATCH 086/112] changed comment. --- MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift | 3 +-- MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 274626c1..79f76df5 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -32,7 +32,7 @@ import UIKit }() //-------------------------------------------------- - // MARK: - Initializer + // MARK: - Initializers //-------------------------------------------------- @objc public override init(frame: CGRect) { @@ -79,7 +79,6 @@ import UIKit let pickedDate = datePicker?.date setTextWith(date: pickedDate) - resignFirstResponder() return pickedDate } diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 3e753ee1..4915f6cc 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -29,7 +29,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { public var observeDropdownSelection: ((String)->())? //-------------------------------------------------- - // MARK: - Initializer + // MARK: - Initializers //-------------------------------------------------- @objc public override init(frame: CGRect) { From 129a0e92411eef785faafa6639d907722d867094 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Tue, 7 Jan 2020 12:34:29 +0530 Subject: [PATCH 087/112] New molecule -> headLineBodyCaretLinkImage --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../HeadLineBodyCaretLinkImage.swift | 95 +++++++++++++++++++ .../MVMCoreUIMoleculeMappingObject.m | 3 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index e8c26f4c..775b8b0d 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; + C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479892314445E003FCCF9 /* LabelSwitch.swift */; }; @@ -233,6 +234,7 @@ 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; + C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadLineBodyCaretLinkImage.swift; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; D22479892314445E003FCCF9 /* LabelSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSwitch.swift; sourceTree = ""; }; @@ -486,6 +488,7 @@ D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */, + C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */, ); path = VerticalCombinationViews; sourceTree = ""; @@ -1175,6 +1178,7 @@ 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, D268C712238D6699007F2C1C /* DropDown.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, + C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift new file mode 100644 index 00000000..107ca07f --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -0,0 +1,95 @@ +// +// HeadLineBodyCaretLinkImage.swift +// MVMCoreUI +// +// Created by Arora, Prateek on 06/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers public class HeadLineBodyCaretLinkImage: ViewConstrainingView { + + let headlineBody = HeadlineBody(frame: .zero) + let caretButton = CaretButton(frame: .zero) + //let caretButton = MFTextButton(nil, constrainHeight: true, forWidth: MVMCoreUIUtility.getWidth()) + + let backgroundImageView = MFLoadImageView() + var spaceBetweenConstant: CGFloat = 104.0 + var spaceBetweenViews: NSLayoutConstraint? + var leftConstant: CGFloat = 32.0 + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + caretButton.updateView(size) + backgroundImageView.updateView(size) + setSpacing() + } + + open override func setupView() { + super.setupView() + guard subviews.count == 0 else { + return + } + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineBody) + view.addSubview(caretButton) + + headlineBody.translatesAutoresizingMaskIntoConstraints = false + headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: 24.0).isActive = true + headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true + var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) + constraint.priority = .defaultHigh + constraint.isActive = true + + spaceBetweenViews = caretButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) + spaceBetweenViews?.isActive = true + + caretButton.translatesAutoresizingMaskIntoConstraints = false + caretButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true + view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true + view.rightAnchor.constraint(greaterThanOrEqualTo: caretButton.rightAnchor).isActive = true + constraint = view.rightAnchor.constraint(equalTo: caretButton.rightAnchor) + constraint.priority = .defaultHigh + constraint.isActive = true + + //Background image view + backgroundImageView.translatesAutoresizingMaskIntoConstraints = false + backgroundImageView.imageView.contentMode = .scaleAspectFill + view.addSubview(backgroundImageView) + NSLayoutConstraint.constraintPinSubview(toSuperview: backgroundImageView) + view.sendSubviewToBack(backgroundImageView) + } + + // MARK: - Constraining + public func setSpacing() { + if headlineBody.hasText() && (caretButton.titleLabel?.text?.count ?? 0) > 0 { + spaceBetweenViews?.constant = spaceBetweenConstant + } else { + spaceBetweenViews?.constant = 0 + } + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + if let imageJSON = json?.optionalDictionaryForKey("image") { + backgroundImageView.setWithJSON(imageJSON, delegateObject: delegateObject, additionalData: additionalData) + } + headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) + caretButton.setWithJSON(json?.optionalDictionaryForKey("caretLink"), delegateObject: delegateObject, additionalData: additionalData) + + } + + open override func reset() { + super.reset() + headlineBody.reset() + } + + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 320 + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 9db7822d..ecad0fc7 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -66,7 +66,8 @@ @"tabsListItem": TabsTableViewCell.class, @"dropDownListItem": DropDownFilterTableViewCell.class, @"headlineBodyButton": HeadlineBodyButton.class, - @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class + @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class, + @"headLineBodyCaretLinkImage" : HeadLineBodyCaretLinkImage.class } mutableCopy]; }); return mapping; From d891d7ba1a88bfeb5b3ab88140bfbae7c1e255b5 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Tue, 7 Jan 2020 19:29:26 +0530 Subject: [PATCH 088/112] Code cleaning --- .../HeadLineBodyCaretLinkImage.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 107ca07f..5347b730 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -17,6 +17,8 @@ import Foundation var spaceBetweenConstant: CGFloat = 104.0 var spaceBetweenViews: NSLayoutConstraint? var leftConstant: CGFloat = 32.0 + var leftConstraint: NSLayoutConstraint? + // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) @@ -38,6 +40,7 @@ import Foundation view.addSubview(headlineBody) view.addSubview(caretButton) + //Headline view headlineBody.translatesAutoresizingMaskIntoConstraints = false headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: 24.0).isActive = true headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true @@ -48,6 +51,7 @@ import Foundation spaceBetweenViews = caretButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetweenViews?.isActive = true + //Caret view caretButton.translatesAutoresizingMaskIntoConstraints = false caretButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true @@ -76,17 +80,15 @@ import Foundation // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - if let imageJSON = json?.optionalDictionaryForKey("image") { - backgroundImageView.setWithJSON(imageJSON, delegateObject: delegateObject, additionalData: additionalData) - } + backgroundImageView.setWithJSON(json?.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) caretButton.setWithJSON(json?.optionalDictionaryForKey("caretLink"), delegateObject: delegateObject, additionalData: additionalData) - } open override func reset() { super.reset() headlineBody.reset() + backgroundImageView.reset() } public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { From 05b3f7885340d6a6d08edc09ea390a8cc87ac38c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 7 Jan 2020 11:36:14 -0500 Subject: [PATCH 089/112] dropping extraneous top and bottom constraints. --- MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift index e970be94..05e64f70 100644 --- a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -62,12 +62,10 @@ import UIKit container.addSubview(dropDownCaretView) textFieldTrailingConstraint?.isActive = false - dropDownCaretView.topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor, constant: 13).isActive = true textFieldTrailingConstraint = dropDownCaretView.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) textFieldTrailingConstraint?.isActive = true container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true - container.bottomAnchor.constraint(greaterThanOrEqualTo: dropDownCaretView.bottomAnchor, constant: 13).isActive = true dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true } } From 2ec963e2b63f25eb05a7180db262e1887f8e7b1f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 7 Jan 2020 16:08:53 -0500 Subject: [PATCH 090/112] latest changes of pr. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 9 +++++---- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 1 + MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift | 4 +--- MVMCoreUI/Atoms/TextFields/MdnEntryField.swift | 7 +++++-- MVMCoreUI/Atoms/TextFields/TextEntryField.swift | 4 +--- MVMCoreUI/Atoms/Views/DashLine.swift | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index 46f17ca6..dc6fc254 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -73,7 +73,7 @@ import UIKit private weak var widthConstraint: NSLayoutConstraint? private weak var heightConstraint: NSLayoutConstraint? - private weak var digitFieldHeight: NSLayoutConstraint? +// private weak var digitFieldHeight: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Initializers @@ -108,8 +108,8 @@ import UIKit digitField.delegate = self digitField.didDeleteDelegate = self - digitFieldHeight = digitField.heightAnchor.constraint(equalToConstant: 24) - digitFieldHeight?.isActive = true +// digitFieldHeight = digitField.heightAnchor.constraint(equalToConstant: 24) +// digitFieldHeight?.isActive = true NSLayoutConstraint.activate([ digitField.topAnchor.constraint(equalTo: topAnchor, constant: PaddingOne), @@ -131,7 +131,7 @@ import UIKit let tap = UITapGestureRecognizer(target: self, action: #selector(callTextField)) addGestureRecognizer(tap) - updateView(MVMCoreUISplitViewController.getDetailViewWidth()) + updateView(MVMCoreUIUtility.getWidth()) } @objc public func textFieldDidDelete() { @@ -188,6 +188,7 @@ import UIKit }) sizeObject?.performBlockBase(onSize: size) +// digitFieldHeight?.constant = floor(height * 0.55) widthConstraint?.constant = width heightConstraint?.constant = height digitField.font = MFFonts.mfFont55Rg(pointSize) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 3dd0b014..7ebac501 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -224,6 +224,7 @@ import UIKit self.layoutIfNeeded() } } + setNeedsLayout() } //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 4915f6cc..7eaf6c5c 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -17,8 +17,6 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { open var pickerData: [String] = [] open var pickerView: UIPickerView? - open var componentsCount = 1 - /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true @@ -98,7 +96,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { @objc public func numberOfComponents(in pickerView: UIPickerView) -> Int { - return componentsCount + return 1 } @objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 3612f5a2..1b687a41 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -88,7 +88,7 @@ import MVMCore @objc public func hasValidMDN() -> Bool { - guard let MDN = mdn, !MDN.isEmpty else { return true } + guard let MDN = mdn, MDN.isEmpty else { return false } if isNationalMDN { return MVMCoreUIUtility.validateMDNString(MDN) @@ -99,7 +99,10 @@ import MVMCore @objc public override func validateTextField() -> Bool { - guard !shouldValidateMDN else { return true } + guard !shouldValidateMDN, let MDN = mdn, !MDN.isEmpty else { + isValid = true + return true + } let isValid = hasValidMDN() diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index d82da76d..0c004509 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -79,9 +79,7 @@ import UIKit /// The text of this TextField. open override var text: String? { get { return textField.text } - set { - textField.text = newValue - } + set { textField.text = newValue } } /// Placeholder access for the TextField. diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 2b887c09..edd911e0 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -31,7 +31,7 @@ open class DashLine: View { required public init?(coder: NSCoder) { super.init(coder: coder) -// fatalError("DashLine xib not supported") + fatalError("DashLine xib not supported") } //------------------------------------------------------ From f32cc7d05c8e618703209fede23cecf308b5204f Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Wed, 8 Jan 2020 12:21:28 +0530 Subject: [PATCH 091/112] Code cleaning --- .../HeadLineBodyCaretLinkImage.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 5347b730..53bff16d 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -11,14 +11,11 @@ import Foundation let headlineBody = HeadlineBody(frame: .zero) let caretButton = CaretButton(frame: .zero) - //let caretButton = MFTextButton(nil, constrainHeight: true, forWidth: MVMCoreUIUtility.getWidth()) - let backgroundImageView = MFLoadImageView() var spaceBetweenConstant: CGFloat = 104.0 var spaceBetweenViews: NSLayoutConstraint? var leftConstant: CGFloat = 32.0 - var leftConstraint: NSLayoutConstraint? - + // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) @@ -36,7 +33,6 @@ import Foundation let view = MVMCoreUICommonViewsUtility.commonView() addSubview(view) pinView(toSuperView: view) - view.addSubview(headlineBody) view.addSubview(caretButton) @@ -80,9 +76,9 @@ import Foundation // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - backgroundImageView.setWithJSON(json?.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) - headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) - caretButton.setWithJSON(json?.optionalDictionaryForKey("caretLink"), delegateObject: delegateObject, additionalData: additionalData) + backgroundImageView.setWithJSON(json?.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) + headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) + caretButton.setWithJSON(json?.optionalDictionaryForKey("caretLink"), delegateObject: delegateObject, additionalData: additionalData) } open override func reset() { From c908606b79db06142375a0117644e7327a456e18 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Wed, 8 Jan 2020 13:08:09 +0530 Subject: [PATCH 092/112] Review Comment Changes --- .../VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 53bff16d..d1710070 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -44,7 +44,7 @@ import Foundation constraint.priority = .defaultHigh constraint.isActive = true - spaceBetweenViews = caretButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) + spaceBetweenViews = caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetweenViews?.isActive = true //Caret view From 4c60ea2f1f39ba5df18b23b8bfd6c7375b1f952d Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Wed, 8 Jan 2020 14:53:59 +0530 Subject: [PATCH 093/112] Review comment changes --- .../HeadLineBodyCaretLinkImage.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index d1710070..54f9b357 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -17,14 +17,6 @@ import Foundation var leftConstant: CGFloat = 32.0 // MARK: - MVMCoreViewProtocol - open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineBody.updateView(size) - caretButton.updateView(size) - backgroundImageView.updateView(size) - setSpacing() - } - open override func setupView() { super.setupView() guard subviews.count == 0 else { @@ -33,15 +25,15 @@ import Foundation let view = MVMCoreUICommonViewsUtility.commonView() addSubview(view) pinView(toSuperView: view) + setSpacing() view.addSubview(headlineBody) view.addSubview(caretButton) //Headline view - headlineBody.translatesAutoresizingMaskIntoConstraints = false headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: 24.0).isActive = true headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) - constraint.priority = .defaultHigh + constraint.priority = .defaultLow constraint.isActive = true spaceBetweenViews = caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) @@ -53,7 +45,7 @@ import Foundation view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true view.rightAnchor.constraint(greaterThanOrEqualTo: caretButton.rightAnchor).isActive = true constraint = view.rightAnchor.constraint(equalTo: caretButton.rightAnchor) - constraint.priority = .defaultHigh + constraint.priority = .defaultLow constraint.isActive = true //Background image view From 0226eb2cace3e5dac857045f92ed83870b742e76 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Wed, 8 Jan 2020 18:46:02 +0530 Subject: [PATCH 094/112] Review comments : added default padding --- .../VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 54f9b357..469a4615 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -30,7 +30,7 @@ import Foundation view.addSubview(caretButton) //Headline view - headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: 24.0).isActive = true + headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultLow From 203484b2883110bde3931dec39ee3ef6ffa23dd8 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Wed, 8 Jan 2020 18:49:36 +0530 Subject: [PATCH 095/112] Code clean --- .../HeadLineBodyCaretLinkImage.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 469a4615..e10a6196 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -17,6 +17,13 @@ import Foundation var leftConstant: CGFloat = 32.0 // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + caretButton.updateView(size) + backgroundImageView.updateView(size) + } + open override func setupView() { super.setupView() guard subviews.count == 0 else { From 55b38e48ef6eec46eb55d3317f645dc333b31732 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 8 Jan 2020 11:42:17 -0500 Subject: [PATCH 096/112] Remove asynchronous statement which was preventing proper redrawing. --- MVMCoreUI/Atoms/TextFields/DigitBox.swift | 5 ----- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 12 ++++-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift index dc6fc254..1134a80f 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -73,7 +73,6 @@ import UIKit private weak var widthConstraint: NSLayoutConstraint? private weak var heightConstraint: NSLayoutConstraint? -// private weak var digitFieldHeight: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Initializers @@ -108,9 +107,6 @@ import UIKit digitField.delegate = self digitField.didDeleteDelegate = self -// digitFieldHeight = digitField.heightAnchor.constraint(equalToConstant: 24) -// digitFieldHeight?.isActive = true - NSLayoutConstraint.activate([ digitField.topAnchor.constraint(equalTo: topAnchor, constant: PaddingOne), digitField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: PaddingOne), @@ -188,7 +184,6 @@ import UIKit }) sizeObject?.performBlockBase(onSize: size) -// digitFieldHeight?.constant = floor(height * 0.55) widthConstraint?.constant = width heightConstraint?.constant = height digitField.font = MFFonts.mfFont55Rg(pointSize) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 7ebac501..f5fc7259 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -216,15 +216,11 @@ import UIKit entryFieldContainer.disableAllBorders = true - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - if !self.digitBoxes.isEmpty { - self.digitBoxes.forEach { $0.updateView(size) } - self.layoutIfNeeded() - } + if !self.digitBoxes.isEmpty { + self.digitBoxes.forEach { $0.updateView(size) } +// self.layoutIfNeeded() } - setNeedsLayout() + layoutIfNeeded() } //-------------------------------------------------- From e8f52cd75ae7c0e867f5f1b9a4a8990355713b63 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 8 Jan 2020 13:13:17 -0500 Subject: [PATCH 097/112] execute if no text. --- MVMCoreUI/Atoms/TextFields/MdnEntryField.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift index 1b687a41..a0a893a6 100644 --- a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -88,7 +88,7 @@ import MVMCore @objc public func hasValidMDN() -> Bool { - guard let MDN = mdn, MDN.isEmpty else { return false } + guard let MDN = mdn, !MDN.isEmpty else { return false } if isNationalMDN { return MVMCoreUIUtility.validateMDNString(MDN) From 67304b7657d36f2fe6d526cc610e39f30c6f2531 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Thu, 9 Jan 2020 13:26:09 +0530 Subject: [PATCH 098/112] Changes as suggested by Ryan --- .../HeadLineBodyCaretLinkImage.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index e10a6196..4c8d2313 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -15,13 +15,17 @@ import Foundation var spaceBetweenConstant: CGFloat = 104.0 var spaceBetweenViews: NSLayoutConstraint? var leftConstant: CGFloat = 32.0 - + var leftConstraintHeadline : NSLayoutConstraint? + var leftConstraintCaretView : NSLayoutConstraint? + let padding = MFStyler.defaultHorizontalPaddingForApplicationWidth() // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) headlineBody.updateView(size) caretButton.updateView(size) backgroundImageView.updateView(size) + leftConstraintHeadline?.constant = MFStyler.defaultHorizontalPadding(forSize: size) + leftConstraintCaretView?.constant = MFStyler.defaultHorizontalPadding(forSize: size) } open override func setupView() { @@ -37,8 +41,9 @@ import Foundation view.addSubview(caretButton) //Headline view + leftConstraintHeadline = headlineBody.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) + leftConstraintHeadline?.isActive = true headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true - headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultLow constraint.isActive = true @@ -48,7 +53,9 @@ import Foundation //Caret view caretButton.translatesAutoresizingMaskIntoConstraints = false - caretButton.leftAnchor.constraint(equalTo: view.leftAnchor, constant: leftConstant).isActive = true + leftConstraintCaretView = caretButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) + leftConstraintCaretView?.isActive = true + caretButton.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor, constant: PaddingDefault).isActive = true view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true view.rightAnchor.constraint(greaterThanOrEqualTo: caretButton.rightAnchor).isActive = true constraint = view.rightAnchor.constraint(equalTo: caretButton.rightAnchor) From 03aeb1fc63958bd3891afebdb0fa84b494ad31a8 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Thu, 9 Jan 2020 16:35:56 +0530 Subject: [PATCH 099/112] headline body width 85% having max as 350px --- .../VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 4c8d2313..08ea4b5b 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -18,6 +18,7 @@ import Foundation var leftConstraintHeadline : NSLayoutConstraint? var leftConstraintCaretView : NSLayoutConstraint? let padding = MFStyler.defaultHorizontalPaddingForApplicationWidth() + let maxWidth : CGFloat = 350.0 // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) @@ -44,6 +45,9 @@ import Foundation leftConstraintHeadline = headlineBody.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) leftConstraintHeadline?.isActive = true headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true + let headLineBodyWidth = min((MVMCoreUISplitViewController.getDetailViewWidth() * 0.85), maxWidth) + headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: headLineBodyWidth).isActive = true + var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultLow constraint.isActive = true From 7b49224abdf948a7517e12e6922a161e05e2adb5 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Thu, 9 Jan 2020 18:19:52 +0530 Subject: [PATCH 100/112] Caret link bottom spacing --- .../VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 08ea4b5b..854c6a31 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -14,7 +14,6 @@ import Foundation let backgroundImageView = MFLoadImageView() var spaceBetweenConstant: CGFloat = 104.0 var spaceBetweenViews: NSLayoutConstraint? - var leftConstant: CGFloat = 32.0 var leftConstraintHeadline : NSLayoutConstraint? var leftConstraintCaretView : NSLayoutConstraint? let padding = MFStyler.defaultHorizontalPaddingForApplicationWidth() @@ -52,7 +51,7 @@ import Foundation constraint.priority = .defaultLow constraint.isActive = true - spaceBetweenViews = caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) + spaceBetweenViews = caretButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetweenViews?.isActive = true //Caret view From b8a162da54f71396f199a289f64accdde7cd4ddb Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Thu, 9 Jan 2020 20:10:17 +0530 Subject: [PATCH 101/112] Code cleaning removing duplicate constraint --- .../HeadLineBodyCaretLinkImage.swift | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 854c6a31..3ff09aa9 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -36,7 +36,7 @@ import Foundation let view = MVMCoreUICommonViewsUtility.commonView() addSubview(view) pinView(toSuperView: view) - setSpacing() + //setSpacing() view.addSubview(headlineBody) view.addSubview(caretButton) @@ -46,12 +46,8 @@ import Foundation headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true let headLineBodyWidth = min((MVMCoreUISplitViewController.getDetailViewWidth() * 0.85), maxWidth) headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: headLineBodyWidth).isActive = true - - var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) - constraint.priority = .defaultLow - constraint.isActive = true - spaceBetweenViews = caretButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) + spaceBetweenViews = caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetweenViews?.isActive = true //Caret view @@ -60,8 +56,8 @@ import Foundation leftConstraintCaretView?.isActive = true caretButton.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor, constant: PaddingDefault).isActive = true view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true - view.rightAnchor.constraint(greaterThanOrEqualTo: caretButton.rightAnchor).isActive = true - constraint = view.rightAnchor.constraint(equalTo: caretButton.rightAnchor) + + let constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultLow constraint.isActive = true From 20f62ce4c5f393a114e838ba33c408d264cf607e Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Thu, 9 Jan 2020 20:10:39 +0530 Subject: [PATCH 102/112] Code clean --- .../VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 3ff09aa9..f1695d02 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -36,7 +36,6 @@ import Foundation let view = MVMCoreUICommonViewsUtility.commonView() addSubview(view) pinView(toSuperView: view) - //setSpacing() view.addSubview(headlineBody) view.addSubview(caretButton) From b037d23adbaea3f428348bba75de88125e4c75a3 Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Thu, 9 Jan 2020 22:25:53 +0530 Subject: [PATCH 103/112] Code changes --- .../HeadLineBodyCaretLinkImage.swift | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index f1695d02..1b1c57e9 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -43,9 +43,12 @@ import Foundation leftConstraintHeadline = headlineBody.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) leftConstraintHeadline?.isActive = true headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true - let headLineBodyWidth = min((MVMCoreUISplitViewController.getDetailViewWidth() * 0.85), maxWidth) - headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: headLineBodyWidth).isActive = true - + + let headLineBodyWidth = headlineBody.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85) + headLineBodyWidth.priority = .defaultHigh + headLineBodyWidth.isActive = true + headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true + spaceBetweenViews = caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetweenViews?.isActive = true @@ -67,16 +70,7 @@ import Foundation NSLayoutConstraint.constraintPinSubview(toSuperview: backgroundImageView) view.sendSubviewToBack(backgroundImageView) } - - // MARK: - Constraining - public func setSpacing() { - if headlineBody.hasText() && (caretButton.titleLabel?.text?.count ?? 0) > 0 { - spaceBetweenViews?.constant = spaceBetweenConstant - } else { - spaceBetweenViews?.constant = 0 - } - } - + // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) From 371fb148d19203421a39a4054830687ad86a9590 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 9 Jan 2020 12:09:33 -0500 Subject: [PATCH 104/112] altered constraint. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index f5fc7259..8ef04814 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -48,7 +48,7 @@ import UIKit if let prevTrailingAnchor = prevBox?.trailingAnchor { box.leadingAnchor.constraint(equalTo: prevTrailingAnchor, constant: space).isActive = true } - entryFieldContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true + entryFieldContainer.trailingAnchor.constraint(equalTo: box.trailingAnchor).isActive = true } else { if let prevTrailingAnchor = prevBox?.trailingAnchor { From 4c078d607ca8424a2109a5ac526c125e200e8fc1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 9 Jan 2020 13:34:37 -0500 Subject: [PATCH 105/112] access didits. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 8ef04814..734dacea 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -35,7 +35,7 @@ import UIKit var prevBox: DigitBox? for (index, box) in digitBoxes.enumerated() { - accessibleElements.append(box) + accessibleElements.append(box.digitField) entryFieldContainer.addSubview(box) box.topAnchor.constraint(equalTo: entryFieldContainer.topAnchor).isActive = true From 47b82f7e87e5996c5b4dd2c267a39b8974374ed1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 9 Jan 2020 15:32:35 -0500 Subject: [PATCH 106/112] date formatting exposure. --- .../TextFields/DateDropdownEntryField.swift | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index 79f76df5..feddfd6b 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -22,14 +22,7 @@ import UIKit return calendar }() - public var formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeZone = NSTimeZone.system - formatter.locale = .current - formatter.formatterBehavior = .default - return formatter - }() + public var dateFormat: String? //-------------------------------------------------- // MARK: - Initializers @@ -90,7 +83,7 @@ import UIKit if calendar.isDate(date, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - text = formatter.string(from: date) + text = dateFormatter().string(from: date) } } @@ -104,6 +97,21 @@ import UIKit setTextWith(date: datePicker?.date) } + + public func dateFormatter() -> DateFormatter { + + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = NSTimeZone.system + formatter.locale = .current + formatter.formatterBehavior = .default + + if let dateFormat = dateFormat { + formatter.dateFormat = dateFormat + } + + return formatter + } } // MARK: - MVMCoreUIMoleculeViewProtocol @@ -113,5 +121,9 @@ extension DateDropdownEntryField { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json, !dictionary.isEmpty else { return } + + if let dateFormat = dictionary["dateFormat"] as? String { + self.dateFormat = dateFormat + } } } From 61d36b25a79cf2f1c095d9dba0e409ff376f716d Mon Sep 17 00:00:00 2001 From: "Arora, Prateek" Date: Fri, 10 Jan 2020 11:04:07 +0530 Subject: [PATCH 107/112] Code changes --- .../VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 1b1c57e9..33492541 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -56,10 +56,9 @@ import Foundation caretButton.translatesAutoresizingMaskIntoConstraints = false leftConstraintCaretView = caretButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) leftConstraintCaretView?.isActive = true - caretButton.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor, constant: PaddingDefault).isActive = true view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true - let constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) + let constraint = view.rightAnchor.constraint(greaterThanOrEqualTo: headlineBody.rightAnchor) constraint.priority = .defaultLow constraint.isActive = true From 7a48633cce0e2f1f3879cb0477607d8738cc11b8 Mon Sep 17 00:00:00 2001 From: "Khan, Arshad" Date: Fri, 10 Jan 2020 10:59:13 -0500 Subject: [PATCH 108/112] Implemented Ryan's feedback, as Prateek is out of office for next week. --- .../HeadLineBodyCaretLinkImage.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 33492541..80eaf69e 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -13,7 +13,6 @@ import Foundation let caretButton = CaretButton(frame: .zero) let backgroundImageView = MFLoadImageView() var spaceBetweenConstant: CGFloat = 104.0 - var spaceBetweenViews: NSLayoutConstraint? var leftConstraintHeadline : NSLayoutConstraint? var leftConstraintCaretView : NSLayoutConstraint? let padding = MFStyler.defaultHorizontalPaddingForApplicationWidth() @@ -48,9 +47,6 @@ import Foundation headLineBodyWidth.priority = .defaultHigh headLineBodyWidth.isActive = true headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true - - spaceBetweenViews = caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) - spaceBetweenViews?.isActive = true //Caret view caretButton.translatesAutoresizingMaskIntoConstraints = false @@ -58,9 +54,7 @@ import Foundation leftConstraintCaretView?.isActive = true view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true - let constraint = view.rightAnchor.constraint(greaterThanOrEqualTo: headlineBody.rightAnchor) - constraint.priority = .defaultLow - constraint.isActive = true + caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant).isActive = true //Background image view backgroundImageView.translatesAutoresizingMaskIntoConstraints = false From c4112665d9ba4b4ab58686ee5ea52fdd086eabdf Mon Sep 17 00:00:00 2001 From: panxi Date: Fri, 10 Jan 2020 11:35:29 -0500 Subject: [PATCH 109/112] add bottom padding --- .../VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 80eaf69e..9c3dba8a 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -52,7 +52,7 @@ import Foundation caretButton.translatesAutoresizingMaskIntoConstraints = false leftConstraintCaretView = caretButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) leftConstraintCaretView?.isActive = true - view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor).isActive = true + view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor, constant: PaddingDefault).isActive = true caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant).isActive = true From 85f210abe8397b69585ffab06eb4be87ce3d0646 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 13 Jan 2020 08:49:51 -0500 Subject: [PATCH 110/112] Accessibility fixes. --- MVMCoreUI/Atoms/Views/Label.swift | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 56a0c38b..30101710 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -91,6 +91,7 @@ public typealias ActionBlock = () -> () translatesAutoresizingMaskIntoConstraints = false clauses = [] accessibilityCustomActions = [] + accessibilityTraits = .staticText let tapGesture = UITapGestureRecognizer(target: self, action: #selector(textLinkTapped(_:))) tapGesture.numberOfTapsRequired = 1 @@ -522,6 +523,7 @@ public typealias ActionBlock = () -> () func appendActionableClause(range: NSRange, actionBlock: @escaping ActionBlock) { + accessibilityTraits = .button let accessibleAction = customAccessibilityAction(range: range) clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } @@ -582,6 +584,7 @@ extension Label { styleB2(true) accessibilityCustomActions = [] clauses = [] + accessibilityTraits = .staticText } @objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { @@ -721,7 +724,6 @@ extension UITapGestureRecognizer { if label.makeWholeViewClickable { return true } - guard let abstractContainer = label.abstractTextContainer() else { return false } let textContainer = abstractContainer.0 let layoutManager = abstractContainer.1 @@ -775,7 +777,7 @@ extension Label { } @objc public func accessibilityCustomAction(_ action: UIAccessibilityCustomAction) { - + for clause in clauses { if action.hash == clause.accessibilityID { clause.performAction() @@ -783,4 +785,19 @@ extension Label { } } } + + open override func accessibilityActivate() -> Bool { + + guard let accessibleActions = accessibilityCustomActions else { return false } + + for clause in clauses { + for action in accessibleActions { + if action.hash == clause.accessibilityID { + clause.performAction() + return true + } + } + } + return false + } } From 788145332f579c9d44b6b1df3bc0b181049ad46e Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 13 Jan 2020 12:44:05 -0500 Subject: [PATCH 111/112] Two button view temporary model fix primary button model --- MVMCoreUI.xcodeproj/project.pbxproj | 12 +++++ MVMCoreUI/Atoms/Buttons/ButtonModel.swift | 50 +++++++++++++++++++ ...maryButton+MoleculeProtocolExtension.swift | 30 +++++++++++ .../TwoButtonView.swift | 22 ++++++-- .../TwoButtonViewModel.swift | 16 ++++++ .../OtherHandlers/MoleculeObjectMapping.swift | 5 +- 6 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 MVMCoreUI/Atoms/Buttons/ButtonModel.swift create mode 100644 MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift create mode 100644 MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index dedc9cf9..d9e31e36 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -124,6 +124,9 @@ D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */; }; D28A838723CCCF6500DFE4FC /* MFTextButton+ModelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838623CCCF6500DFE4FC /* MFTextButton+ModelExtension.swift */; }; D28A838923CCCFCB00DFE4FC /* LinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838823CCCFCB00DFE4FC /* LinkModel.swift */; }; + D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */; }; + D28A838D23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */; }; + D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */; }; D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */; }; D29770C921F7C4AE00B2F0D0 /* TopLabelsView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -387,6 +390,9 @@ D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollerModel.swift; sourceTree = ""; }; D28A838623CCCF6500DFE4FC /* MFTextButton+ModelExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFTextButton+ModelExtension.swift"; sourceTree = ""; }; D28A838823CCCFCB00DFE4FC /* LinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkModel.swift; sourceTree = ""; }; + D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonModel.swift; sourceTree = ""; }; + D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PrimaryButton+MoleculeProtocolExtension.swift"; sourceTree = ""; }; + D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonViewModel.swift; sourceTree = ""; }; D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewConstrainingProtocol.h; sourceTree = ""; }; D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopLabelsView.m; sourceTree = ""; }; D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TopLabelsView.h; sourceTree = ""; }; @@ -729,6 +735,7 @@ isa = PBXGroup; children = ( D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */, + D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */, D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */, D28A837E23CCA96400DFE4FC /* TabsModel.swift */, ); @@ -995,6 +1002,8 @@ D29DF16A21E69E1F003B2FB9 /* MFCustomButton.h */, D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */, D28A837623C79FC600DFE4FC /* MFCustomButton+ActionModel.swift */, + D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */, + D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */, D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */, D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */, D282AACA2243C61700C46919 /* ButtonView.swift */, @@ -1383,6 +1392,8 @@ D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */, 014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, + D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */, + D28A838D23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, D28A838723CCCF6500DFE4FC /* MFTextButton+ModelExtension.swift in Sources */, @@ -1448,6 +1459,7 @@ 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, + D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, 012A88B1238C880100FE3DA1 /* PagingMoleculeProtocol.swift in Sources */, D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */, 9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift new file mode 100644 index 00000000..9d9ad9c3 --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift @@ -0,0 +1,50 @@ +// +// ButtonModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public enum ButtonStyle: String, Codable { + case primary + case secondary +} + +public class ButtonModel: MoleculeProtocol { + public static var identifier: String = "button" + public var backgroundColor: Color? + public var title: String + public var action: ActionProtocol + public var style: ButtonStyle? + + init(with title: String, action: ActionProtocol) { + self.title = title + self.action = action + } + + enum CodingKeys: String, CodingKey { + case backgroundColor + case title + case action + case style + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + title = try typeContainer.decode(String.self, forKey: .title) + action = try typeContainer.decodeModel(codingKey: .action, typeCodingKey: ActionCodingKey.type) + style = try typeContainer.decode(ButtonStyle.self, forKey: .style) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(title, forKey: .title) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeModel(action, forKey: .action) + try container.encodeIfPresent(style, forKey: .style) + } +} diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift b/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift new file mode 100644 index 00000000..f7192fc9 --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift @@ -0,0 +1,30 @@ +// +// PrimaryButton+MoleculeProtocolExtension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +// temporary until link is finished +extension PrimaryButton: ModelMoleculeViewProtocol { + public func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let model = model as? ButtonModel else { return } + setTitle(model.title, for: .normal) + backgroundColor = model.backgroundColor?.uiColor + if let style = model.style { + switch style { + case .primary: + setAsStandardCustom() + case .secondary: + setAsSecondaryCustom() + } + } + //TODO: Use object when handleAction is rewrote to handle action model + if let actionMap = model.action.toJSON() { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } +} diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift index 2c5f0fe5..b2f43529 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -137,20 +137,25 @@ import UIKit } } + /// Legacy func setupUI(withPrimaryButtonMap primaryButtonMap: [AnyHashable: Any]?, secondaryButtonMap: [AnyHashable: Any]?) { - if primaryButtonMap != nil, secondaryButtonMap != nil { + setupUI(primaryButtonShowing: primaryButtonMap != nil, secondaryButtonShowing: secondaryButtonMap != nil) + } + + func setupUI(primaryButtonShowing: Bool, secondaryButtonShowing: Bool) { + if primaryButtonShowing, secondaryButtonShowing { heightConstraint?.isActive = false if primaryButton == nil || secondaryButton == nil { removeButtons() setupWithTwoButtons() } - } else if primaryButtonMap != nil { + } else if primaryButtonShowing { heightConstraint?.isActive = false if primaryButton == nil || secondaryButton != nil { removeButtons() setupWithPrimaryButton() } - } else if secondaryButtonMap != nil { + } else if secondaryButtonShowing { heightConstraint?.isActive = false if secondaryButton == nil || primaryButton != nil { removeButtons() @@ -291,3 +296,14 @@ extension TwoButtonView { setup(primaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap, actionDelegate: actionDelegate, additionalData: additionalData, buttonDelegate: buttonDelegate) } } + +extension TwoButtonView: MoleculeViewProtocol { + func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? TwoButtonViewModel else { return } + setupUI(primaryButtonShowing: model.primaryButton != nil, secondaryButtonShowing: model.secondaryButton != nil) + setDefaultCustom() + primaryButton?.setWithModel(model.primaryButton, delegateObject, additionalData) + secondaryButton?.setWithModel(model.secondaryButton, delegateObject, additionalData) + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } +} diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift new file mode 100644 index 00000000..50643e1c --- /dev/null +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -0,0 +1,16 @@ +// +// TwoButtonViewModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class TwoButtonViewModel: MoleculeProtocol { + public static var identifier: String = "twoButtonView" + public var backgroundColor: Color? + public var primaryButton: ButtonModel? + public var secondaryButton: ButtonModel? +} diff --git a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift index 01677585..21895f3d 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift +++ b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift @@ -16,7 +16,6 @@ import Foundation ModelRegistry.register(MoleculeStackModel.self) ModelRegistry.register(StackItemModel.self) ModelRegistry.register(TextFieldModel.self) - ModelRegistry.register(LineModel.self) ModelRegistry.register(ProgressBarModel.self) ModelRegistry.register(MultiProgressBarModel.self) ModelRegistry.register(CaretViewModel.self) @@ -24,6 +23,10 @@ import Foundation ModelRegistry.register(ImageViewModel.self) ModelRegistry.register(TabsModel.self) ModelRegistry.register(ScrollerModel.self) + // buttons + ModelRegistry.register(LineModel.self) + ModelRegistry.register(ButtonModel.self) + ModelRegistry.register(TwoButtonViewModel.self) // list items ModelRegistry.register(ListItemModel.self) ModelRegistry.register(DropDownListItemModel.self) From b79ddf734602ae4028591ff3b17575e22e34db86 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 13 Jan 2020 12:45:49 -0500 Subject: [PATCH 112/112] additions for accessibility. --- MVMCoreUI/Atoms/TextFields/DigitEntryField.swift | 11 ++++++++--- MVMCoreUI/Atoms/TextFields/EntryField.swift | 8 ++------ MVMCoreUI/Atoms/TextFields/TextEntryField.swift | 16 ++++++++-------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 734dacea..4cd9b67a 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -25,8 +25,14 @@ import UIKit if numberOfDigits > 0 { var digitBoxes = [DigitBox]() - for _ in 0.. Bool)? - - public override var errorMessage: String? { - didSet { - textField.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField.text ?? "", errorMessage ?? "") - } - } - + //-------------------------------------------------- // MARK: - Delegate Properties //--------------------------------------------------