latestest and greatest.

This commit is contained in:
Kevin G Christiano 2019-11-20 09:12:31 -05:00
parent 702dc10db2
commit 26c73070d9
7 changed files with 269 additions and 102 deletions

View File

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

View File

@ -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..<numberOfDigits {
digitFields.append(createDigitField())
digitBoxes.append(createDigitField())
}
self.digitFields = digitFields
self.digitBoxes = digitBoxes
guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize() else { return }
var prevBox: DigitBox?
for (index, box) in digitFields.enumerated() {
for (index, box) in digitBoxes.enumerated() {
accessibleElements.append(box)
entryContainer.addSubview(box)
@ -169,7 +197,7 @@ import UIKit
if index == 0 {
box.leadingAnchor.constraint(equalTo: entryContainer.leadingAnchor).isActive = true
} else if index == digitFields.count - 1 {
} else if index == digitBoxes.count - 1 {
box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true
entryContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true
@ -180,7 +208,7 @@ import UIKit
prevBox = box
}
} else {
digitFields = []
digitBoxes = []
}
accessibilityElements = accessibleElements + [feedbackLabel]
@ -193,19 +221,26 @@ import UIKit
open override func updateView(_ size: CGFloat) {
super.updateView(size)
entryContainer.hideBorders = true
entryContainer.disableBorders = true
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if !self.digitFields.isEmpty {
if !self.digitBoxes.isEmpty {
self.digitFields.forEach { $0.updateView(size) }
self.digitBoxes.forEach { $0.updateView(size) }
self.layoutIfNeeded()
}
}
}
open override func reset() {
super.reset()
entryContainer.disableBorders = false
digitBoxes.forEach { $0.reset() }
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
@ -215,11 +250,11 @@ import UIKit
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
for (index, field) in self.digitFields.enumerated() {
for (index, field) in self.digitBoxes.enumerated() {
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))
field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(self.digitBoxes.count))
}
}
}
@ -229,21 +264,21 @@ import UIKit
validationBlock = { enteredValue in
guard let enteredValue = enteredValue else { return false }
return enteredValue.count > 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)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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