From 26c73070d93e316709d6c8a5caafe59a96ae084f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 20 Nov 2019 09:12:31 -0500 Subject: [PATCH] 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 } } }