refactored the rest of the inputfield code into the fieldtype handlers
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
parent
a656561073
commit
4ed2b3c894
@ -75,51 +75,121 @@ extension InputField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreditCardHandler: BaseFieldType {
|
class CreditCardHandler: FieldTypeHandler {
|
||||||
static let shared = CreditCardHandler()
|
static let shared = CreditCardHandler()
|
||||||
|
|
||||||
|
var creditCardType: CreditCardType = .generic
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.init()
|
super.init()
|
||||||
self.keyboardType = .numberPad
|
self.keyboardType = .numberPad
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
override func updateView(_ inputField: InputField) {
|
||||||
|
minWidth = 288.0
|
||||||
|
leftImageName = creditCardType.imageName
|
||||||
|
|
||||||
|
super.updateView(inputField)
|
||||||
|
}
|
||||||
|
|
||||||
override func appendRules(for inputField: InputField) {
|
override func appendRules(_ inputField: InputField) {
|
||||||
if let text = inputField.textField.text, text.count > 0 {
|
if let text = inputField.textField.text, text.count > 0 {
|
||||||
let rule = CharacterCountRule().copyWith {
|
let rule = CharacterCountRule().copyWith {
|
||||||
$0.maxLength = inputField.creditCardType.maxLength
|
$0.maxLength = creditCardType.maxLength
|
||||||
$0.compareType = .equals
|
$0.compareType = .equals
|
||||||
$0.errorMessage = "Enter a valid credit card."
|
$0.errorMessage = "Enter a valid credit card."
|
||||||
}
|
}
|
||||||
inputField.rules.append(.init(rule))
|
inputField.rules.append(.init(rule))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal func formatCreditCardNumber(_ number: String) -> String {
|
|
||||||
let formattedInput = number.filter { $0.isNumber } // Remove any existing slashes
|
|
||||||
return String.format(formattedInput, indices: creditCardType.separatorIndices, with: " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal func updateCardTypeIcon(rawNumber: String) {
|
override func textFieldDidBeginEditing(_ inputField: InputField, textField: UITextField) {
|
||||||
guard rawNumber.count >= 4,
|
if let value {
|
||||||
let firstFourDigits = Int(String(rawNumber.prefix(4))),
|
textField.text = formatCreditCardNumber(value)
|
||||||
let creditCardType = CreditCardType.from(iin: firstFourDigits) else {
|
}
|
||||||
leftImageView.image = BundleManager.shared.image(for: CreditCardType.generic.imageName)
|
|
||||||
creditCardType = .generic
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
self.creditCardType = creditCardType
|
|
||||||
}
|
override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) {
|
||||||
|
if let value {
|
||||||
|
textField.text = maskCreditCardNumber(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
let allowedCharacters = CharacterSet.decimalDigits
|
||||||
|
if string.rangeOfCharacter(from: allowedCharacters.inverted) != nil && !string.isEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
internal func maskCreditCardNumber(_ number: String) -> String {
|
// Get the current text
|
||||||
// Mask the first 12 characters if the length is 16
|
let currentText = textField.text ?? ""
|
||||||
let rawNumber = number.filter { $0.isNumber }
|
|
||||||
guard rawNumber.count == creditCardType.maxLength else { return formatCreditCardNumber(number) }
|
// Calculate the new text
|
||||||
let lastFourDigits = rawNumber.suffix(4)
|
let newText = (currentText as NSString).replacingCharacters(in: range, with: string)
|
||||||
let maskedSection = String(repeating: "•", count: 12)
|
|
||||||
let formattedMaskSection = String.format(maskedSection, indices: creditCardType.separatorIndices, with: " ")
|
// Remove any existing formatting
|
||||||
return formattedMaskSection + " " + lastFourDigits
|
let rawNumber = newText.filter { $0.isNumber }
|
||||||
|
|
||||||
|
if rawNumber.count > creditCardType.maxLength {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the number with spaces
|
||||||
|
let formattedNumber = formatCreditCardNumber(rawNumber)
|
||||||
|
|
||||||
|
// Update the icon based on the first four digits
|
||||||
|
updateCardTypeIcon(inputField, rawNumber: rawNumber)
|
||||||
|
|
||||||
|
// Check again
|
||||||
|
if rawNumber.count > creditCardType.maxLength {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the formatted text
|
||||||
|
textField.text = formattedNumber
|
||||||
|
|
||||||
|
// Calculate the new cursor position
|
||||||
|
if let newPosition = textField.cursorPosition(range: range,
|
||||||
|
replacementString: string,
|
||||||
|
rawNumber: rawNumber,
|
||||||
|
formattedNumber: formattedNumber) {
|
||||||
|
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if all passes, then set the number1
|
||||||
|
value = rawNumber
|
||||||
|
|
||||||
|
// Prevent the default behavior
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Private
|
||||||
|
internal func formatCreditCardNumber(_ number: String) -> String {
|
||||||
|
let formattedInput = number.filter { $0.isNumber } // Remove any existing slashes
|
||||||
|
return String.format(formattedInput, indices: creditCardType.separatorIndices, with: " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func updateCardTypeIcon(_ inputField: InputField, rawNumber: String) {
|
||||||
|
defer { inputField.setNeedsUpdate() }
|
||||||
|
|
||||||
|
guard rawNumber.count >= 4,
|
||||||
|
let firstFourDigits = Int(String(rawNumber.prefix(4))),
|
||||||
|
let creditCardType = CreditCardType.from(iin: firstFourDigits) else {
|
||||||
|
creditCardType = .generic
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.creditCardType = creditCardType
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func maskCreditCardNumber(_ number: String) -> String {
|
||||||
|
// Mask the first 12 characters if the length is 16
|
||||||
|
let rawNumber = number.filter { $0.isNumber }
|
||||||
|
guard rawNumber.count == creditCardType.maxLength else { return formatCreditCardNumber(number) }
|
||||||
|
let lastFourDigits = rawNumber.suffix(4)
|
||||||
|
let maskedSection = String(repeating: "•", count: 12)
|
||||||
|
let formattedMaskSection = String.format(maskedSection, indices: creditCardType.separatorIndices, with: " ")
|
||||||
|
return formattedMaskSection + " " + lastFourDigits
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
extension InputField {
|
extension InputField {
|
||||||
|
|
||||||
class DateHandler: BaseFieldType {
|
class DateHandler: FieldTypeHandler {
|
||||||
static let shared = DateHandler()
|
static let shared = DateHandler()
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
@ -18,9 +18,14 @@ extension InputField {
|
|||||||
self.keyboardType = .numberPad
|
self.keyboardType = .numberPad
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
override func updateView(_ inputField: InputField) {
|
||||||
|
minWidth = 114.0
|
||||||
|
placeholderText = inputField.dateFormat.placeholderText
|
||||||
|
|
||||||
|
super.updateView(inputField)
|
||||||
|
}
|
||||||
|
|
||||||
override func appendRules(for inputField: InputField) {
|
override func appendRules(_ inputField: InputField) {
|
||||||
if let text = inputField.textField.text, text.count > 0 {
|
if let text = inputField.textField.text, text.count > 0 {
|
||||||
let rule = CharacterCountRule().copyWith {
|
let rule = CharacterCountRule().copyWith {
|
||||||
$0.maxLength = inputField.dateFormat.maxLength
|
$0.maxLength = inputField.dateFormat.maxLength
|
||||||
@ -30,6 +35,27 @@ extension InputField {
|
|||||||
inputField.rules.append(.init(rule))
|
inputField.rules.append(.init(rule))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
// Allow only numbers and limit the length of text.
|
||||||
|
guard let oldText = textField.text,
|
||||||
|
let textRange = Range(range, in: oldText),
|
||||||
|
string.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let newText = oldText.replacingCharacters(in: textRange, with: string)
|
||||||
|
if newText.count > inputField.dateFormat.maxLength {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if newText.count <= inputField.dateFormat.maxLength {
|
||||||
|
textField.text = String.format(newText, indices: inputField.dateFormat.separatorIndices, with: "/")
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DateFormat: String, CaseIterable {
|
public enum DateFormat: String, CaseIterable {
|
||||||
@ -69,21 +95,5 @@ extension InputField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func formatDate(_ input: String) -> String {
|
|
||||||
let formattedInput = input.filter { $0.isNumber } // Remove any existing slashes
|
|
||||||
var formattedString = ""
|
|
||||||
var currentIndex = formattedInput.startIndex
|
|
||||||
|
|
||||||
for index in 0..<formattedInput.count {
|
|
||||||
if dateFormat.separatorIndices.contains(index) {
|
|
||||||
formattedString.append("/")
|
|
||||||
}
|
|
||||||
formattedString.append(formattedInput[currentIndex])
|
|
||||||
currentIndex = formattedInput.index(after: currentIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedString
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,20 +10,7 @@ import UIKit
|
|||||||
import VDSTokens
|
import VDSTokens
|
||||||
|
|
||||||
extension InputField {
|
extension InputField {
|
||||||
protocol FieldTypeHandler: UITextFieldDelegate {
|
class FieldTypeHandler: NSObject {
|
||||||
var keyboardType: UIKeyboardType { get }
|
|
||||||
var minWidth: CGFloat { get set }
|
|
||||||
var leftImageName: String? { get set }
|
|
||||||
var actionModel: InputField.TextLinkModel? { get set }
|
|
||||||
var toolTipModel: Tooltip.TooltipModel? { get set }
|
|
||||||
var isSecureTextEntry: Bool { get set }
|
|
||||||
var placeholderText: String? { get set }
|
|
||||||
|
|
||||||
func configure(for inputField: InputField)
|
|
||||||
func appendRules(for inputField: InputField)
|
|
||||||
}
|
|
||||||
|
|
||||||
class BaseFieldType: NSObject, FieldTypeHandler {
|
|
||||||
var keyboardType: UIKeyboardType
|
var keyboardType: UIKeyboardType
|
||||||
var minWidth: CGFloat = 40.0
|
var minWidth: CGFloat = 40.0
|
||||||
var leftImageName: String?
|
var leftImageName: String?
|
||||||
@ -31,13 +18,14 @@ extension InputField {
|
|||||||
var toolTipModel: Tooltip.TooltipModel?
|
var toolTipModel: Tooltip.TooltipModel?
|
||||||
var isSecureTextEntry = false
|
var isSecureTextEntry = false
|
||||||
var placeholderText: String?
|
var placeholderText: String?
|
||||||
|
var value: String?
|
||||||
|
|
||||||
internal override init() {
|
internal override init() {
|
||||||
keyboardType = .default
|
keyboardType = .default
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure(for inputField: InputField) {
|
func updateView(_ inputField: InputField) {
|
||||||
|
|
||||||
//textField
|
//textField
|
||||||
inputField.textField.isSecureTextEntry = isSecureTextEntry
|
inputField.textField.isSecureTextEntry = isSecureTextEntry
|
||||||
@ -78,8 +66,18 @@ extension InputField {
|
|||||||
inputField.tooltipModel = toolTipModel
|
inputField.tooltipModel = toolTipModel
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendRules(for inputField: InputField) {}
|
func appendRules(_ inputField: InputField) {}
|
||||||
|
|
||||||
|
func textFieldDidBeginEditing(_ inputField: InputField, textField: UITextField) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FieldType: String, CaseIterable {
|
public enum FieldType: String, CaseIterable {
|
||||||
|
|||||||
@ -10,16 +10,18 @@ import UIKit
|
|||||||
|
|
||||||
extension InputField {
|
extension InputField {
|
||||||
|
|
||||||
class InlineActionHandler: BaseFieldType {
|
class InlineActionHandler: FieldTypeHandler {
|
||||||
static let shared = InlineActionHandler()
|
static let shared = InlineActionHandler()
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
override func updateView(_ inputField: InputField) {
|
||||||
|
minWidth = 102.0
|
||||||
override func appendRules(for inputField: InputField) {}
|
|
||||||
|
super.updateView(inputField)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
extension InputField {
|
extension InputField {
|
||||||
|
|
||||||
class NumberHandler: BaseFieldType {
|
class NumberHandler: FieldTypeHandler {
|
||||||
static let shared = NumberHandler()
|
static let shared = NumberHandler()
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
@ -18,10 +18,12 @@ extension InputField {
|
|||||||
self.keyboardType = .numberPad
|
self.keyboardType = .numberPad
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
// Allow only numbers
|
||||||
override func appendRules(for inputField: InputField) {}
|
let allowedCharacters = CharacterSet.decimalDigits
|
||||||
|
let characterSet = CharacterSet(charactersIn: string)
|
||||||
|
return allowedCharacters.isSuperset(of: characterSet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,16 +18,37 @@ extension InputField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasswordHandler: BaseFieldType {
|
class PasswordHandler: FieldTypeHandler {
|
||||||
static let shared = PasswordHandler()
|
static let shared = PasswordHandler()
|
||||||
|
|
||||||
|
internal var passwordActionType: PasswordAction = .hide
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
override func updateView(_ inputField: InputField) {
|
||||||
|
let isHide = passwordActionType == .hide
|
||||||
override func appendRules(for inputField: InputField) {}
|
let buttonText = isHide ?
|
||||||
|
inputField.hidePasswordButtonText.isEmpty ? "Hide" : inputField.hidePasswordButtonText :
|
||||||
|
inputField.showPasswordButtonText.isEmpty ? "Show" : inputField.showPasswordButtonText
|
||||||
|
|
||||||
|
isSecureTextEntry = !isHide
|
||||||
|
let nextPasswordActionType = passwordActionType.toggle()
|
||||||
|
if let text = inputField.text, !text.isEmpty {
|
||||||
|
actionModel = .init(text: buttonText,
|
||||||
|
onClick: { [weak self] _ in
|
||||||
|
guard let self else { return }
|
||||||
|
self.passwordActionType = nextPasswordActionType
|
||||||
|
inputField.setNeedsUpdate()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
passwordActionType = .show
|
||||||
|
}
|
||||||
|
minWidth = 62.0
|
||||||
|
|
||||||
|
super.updateView(inputField)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
extension InputField {
|
extension InputField {
|
||||||
|
|
||||||
class SecurityCodeHandler: BaseFieldType {
|
class SecurityCodeHandler: FieldTypeHandler {
|
||||||
static let shared = SecurityCodeHandler()
|
static let shared = SecurityCodeHandler()
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
@ -18,9 +18,19 @@ extension InputField {
|
|||||||
self.keyboardType = .numberPad
|
self.keyboardType = .numberPad
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
override func updateView(_ inputField: InputField) {
|
||||||
|
minWidth = 88.0
|
||||||
|
isSecureTextEntry = true
|
||||||
|
|
||||||
|
super.updateView(inputField)
|
||||||
|
}
|
||||||
|
|
||||||
override func appendRules(for inputField: InputField) {}
|
override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
// Allow only numbers and limit the length of text.
|
||||||
|
let allowedCharacters = CharacterSet.decimalDigits
|
||||||
|
let characterSet = CharacterSet(charactersIn: string)
|
||||||
|
return allowedCharacters.isSuperset(of: characterSet) && ((textField.text?.count ?? 0) + string.count - range.length) <= 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,38 +10,7 @@ import UIKit
|
|||||||
|
|
||||||
extension InputField {
|
extension InputField {
|
||||||
|
|
||||||
internal func formatUSNumber(_ number: String) -> String {
|
class TelephoneHandler: FieldTypeHandler {
|
||||||
// Format the number in the style XXX-XXX-XXXX
|
|
||||||
let areaCodeLength = 3
|
|
||||||
let centralOfficeCodeLength = 3
|
|
||||||
let lineNumberLength = 4
|
|
||||||
|
|
||||||
var formattedNumber = ""
|
|
||||||
|
|
||||||
if number.count > 0 {
|
|
||||||
formattedNumber.append(contentsOf: number.prefix(areaCodeLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
if number.count > areaCodeLength {
|
|
||||||
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength)
|
|
||||||
let endIndex = number.index(startIndex, offsetBy: min(centralOfficeCodeLength, number.count - areaCodeLength))
|
|
||||||
let centralOfficeCode = number[startIndex..<endIndex]
|
|
||||||
formattedNumber.append("-")
|
|
||||||
formattedNumber.append(contentsOf: centralOfficeCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if number.count > areaCodeLength + centralOfficeCodeLength {
|
|
||||||
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength + centralOfficeCodeLength)
|
|
||||||
let endIndex = number.index(startIndex, offsetBy: min(lineNumberLength, number.count - areaCodeLength - centralOfficeCodeLength))
|
|
||||||
let lineNumber = number[startIndex..<endIndex]
|
|
||||||
formattedNumber.append("-")
|
|
||||||
formattedNumber.append(contentsOf: lineNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
class TelephoneHandler: BaseFieldType {
|
|
||||||
static let shared = TelephoneHandler()
|
static let shared = TelephoneHandler()
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
@ -49,9 +18,13 @@ extension InputField {
|
|||||||
self.keyboardType = .phonePad
|
self.keyboardType = .phonePad
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
override func updateView(_ inputField: InputField) {
|
||||||
|
minWidth = 176.0
|
||||||
|
|
||||||
|
super.updateView(inputField)
|
||||||
|
}
|
||||||
|
|
||||||
override func appendRules(for inputField: InputField) {
|
override func appendRules(_ inputField: InputField) {
|
||||||
if let text = inputField.textField.text, text.count > 0 {
|
if let text = inputField.textField.text, text.count > 0 {
|
||||||
let rule = CharacterCountRule().copyWith {
|
let rule = CharacterCountRule().copyWith {
|
||||||
$0.maxLength = "XXX-XXX-XXXX".count
|
$0.maxLength = "XXX-XXX-XXXX".count
|
||||||
@ -61,6 +34,70 @@ extension InputField {
|
|||||||
inputField.rules.append(.init(rule))
|
inputField.rules.append(.init(rule))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
// Allow only numbers and limit the length of text.
|
||||||
|
let allowedCharacters = CharacterSet(charactersIn: "01233456789")
|
||||||
|
let characterSet = CharacterSet(charactersIn: string)
|
||||||
|
let currentText = textField.text ?? ""
|
||||||
|
if !allowedCharacters.isSuperset(of: characterSet) { return false }
|
||||||
|
|
||||||
|
// Calculate the new text
|
||||||
|
let newText = (currentText as NSString).replacingCharacters(in: range, with: string)
|
||||||
|
|
||||||
|
// Remove any existing formatting
|
||||||
|
let rawNumber = newText.filter { $0.isNumber }
|
||||||
|
|
||||||
|
// Format the number with dashes
|
||||||
|
let formattedNumber = formatUSNumber(rawNumber)
|
||||||
|
|
||||||
|
// Set the formatted text
|
||||||
|
textField.text = formattedNumber
|
||||||
|
|
||||||
|
// Calculate the new cursor position
|
||||||
|
if let newPosition = textField.cursorPosition(range: range,
|
||||||
|
replacementString: string,
|
||||||
|
rawNumber: rawNumber,
|
||||||
|
formattedNumber: formattedNumber) {
|
||||||
|
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent the default behavior
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func formatUSNumber(_ number: String) -> String {
|
||||||
|
// Format the number in the style XXX-XXX-XXXX
|
||||||
|
let areaCodeLength = 3
|
||||||
|
let centralOfficeCodeLength = 3
|
||||||
|
let lineNumberLength = 4
|
||||||
|
|
||||||
|
var formattedNumber = ""
|
||||||
|
|
||||||
|
if number.count > 0 {
|
||||||
|
formattedNumber.append(contentsOf: number.prefix(areaCodeLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
if number.count > areaCodeLength {
|
||||||
|
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength)
|
||||||
|
let endIndex = number.index(startIndex, offsetBy: min(centralOfficeCodeLength, number.count - areaCodeLength))
|
||||||
|
let centralOfficeCode = number[startIndex..<endIndex]
|
||||||
|
formattedNumber.append("-")
|
||||||
|
formattedNumber.append(contentsOf: centralOfficeCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if number.count > areaCodeLength + centralOfficeCodeLength {
|
||||||
|
let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength + centralOfficeCodeLength)
|
||||||
|
let endIndex = number.index(startIndex, offsetBy: min(lineNumberLength, number.count - areaCodeLength - centralOfficeCodeLength))
|
||||||
|
let lineNumber = number[startIndex..<endIndex]
|
||||||
|
formattedNumber.append("-")
|
||||||
|
formattedNumber.append(contentsOf: lineNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedNumber
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,15 +9,11 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension InputField {
|
extension InputField {
|
||||||
class TextHandler: BaseFieldType {
|
class TextHandler: FieldTypeHandler {
|
||||||
static let shared = TextHandler()
|
static let shared = TextHandler()
|
||||||
|
|
||||||
private override init() {
|
private override init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func configure(for inputField: InputField) {}
|
|
||||||
|
|
||||||
override func appendRules(for inputField: InputField) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,8 +86,8 @@ open class InputField: EntryFieldBase {
|
|||||||
|
|
||||||
/// Value for the textField
|
/// Value for the textField
|
||||||
open override var value: String? {
|
open override var value: String? {
|
||||||
if fieldType == .creditCard {
|
if let value = fieldType.handler().value {
|
||||||
return creditCardRawNumber
|
return value
|
||||||
} else {
|
} else {
|
||||||
return textField.text
|
return textField.text
|
||||||
}
|
}
|
||||||
@ -205,7 +205,7 @@ open class InputField: EntryFieldBase {
|
|||||||
open override func updateView() {
|
open override func updateView() {
|
||||||
|
|
||||||
//update fieldType first
|
//update fieldType first
|
||||||
updateFieldType()
|
fieldType.handler().updateView(self)
|
||||||
|
|
||||||
super.updateView()
|
super.updateView()
|
||||||
|
|
||||||
@ -254,103 +254,10 @@ open class InputField: EntryFieldBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func updateFieldType() {
|
|
||||||
fieldType.handler().configure(for: self)
|
|
||||||
|
|
||||||
var minWidth: CGFloat = 40.0
|
|
||||||
var leftImageName: String?
|
|
||||||
var actionModel: InputField.TextLinkModel?
|
|
||||||
var toolTipModel: Tooltip.TooltipModel? = tooltipModel
|
|
||||||
var isSecureTextEntry = false
|
|
||||||
var placeholderText: String?
|
|
||||||
|
|
||||||
switch fieldType {
|
|
||||||
case .text:
|
|
||||||
break
|
|
||||||
|
|
||||||
case .number:
|
|
||||||
break
|
|
||||||
|
|
||||||
case .inlineAction:
|
|
||||||
minWidth = 102.0
|
|
||||||
|
|
||||||
case .password:
|
|
||||||
let isHide = passwordActionType == .hide
|
|
||||||
let buttonText = isHide ?
|
|
||||||
hidePasswordButtonText.isEmpty ? "Hide" : hidePasswordButtonText :
|
|
||||||
showPasswordButtonText.isEmpty ? "Show" : showPasswordButtonText
|
|
||||||
|
|
||||||
isSecureTextEntry = !isHide
|
|
||||||
let nextPasswordActionType = passwordActionType.toggle()
|
|
||||||
if let text, !text.isEmpty {
|
|
||||||
actionModel = .init(text: buttonText,
|
|
||||||
onClick: { [weak self] _ in
|
|
||||||
guard let self else { return }
|
|
||||||
self.passwordActionType = nextPasswordActionType
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
passwordActionType = .show
|
|
||||||
}
|
|
||||||
minWidth = 62.0
|
|
||||||
|
|
||||||
case .creditCard:
|
|
||||||
minWidth = 288.0
|
|
||||||
leftImageName = creditCardType.imageName
|
|
||||||
case .telephone:
|
|
||||||
minWidth = 176.0
|
|
||||||
|
|
||||||
case .date:
|
|
||||||
minWidth = 114.0
|
|
||||||
placeholderText = dateFormat.placeholderText
|
|
||||||
case .securityCode:
|
|
||||||
minWidth = 88.0
|
|
||||||
isSecureTextEntry = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//textField
|
|
||||||
textField.isSecureTextEntry = isSecureTextEntry
|
|
||||||
|
|
||||||
//leftIcon
|
|
||||||
if let leftImageName {
|
|
||||||
leftImageView.image = BundleManager.shared.image(for: leftImageName)?.withTintColor(iconColorConfiguration.getColor(self))
|
|
||||||
}
|
|
||||||
leftImageView.isHidden = leftImageName == nil
|
|
||||||
|
|
||||||
//actionLink
|
|
||||||
actionTextLink.surface = surface
|
|
||||||
if let actionModel {
|
|
||||||
actionTextLink.text = actionModel.text
|
|
||||||
actionTextLink.onClick = actionModel.onClick
|
|
||||||
actionTextLink.isHidden = false
|
|
||||||
containerStackView.setCustomSpacing(VDSLayout.space2X, after: statusIcon)
|
|
||||||
} else {
|
|
||||||
actionTextLink.isHidden = true
|
|
||||||
containerStackView.setCustomSpacing(0, after: statusIcon)
|
|
||||||
}
|
|
||||||
|
|
||||||
//set the width constraints
|
|
||||||
if let width, width > minWidth {
|
|
||||||
widthConstraint?.constant = width
|
|
||||||
widthConstraint?.isActive = true
|
|
||||||
minWidthConstraint?.isActive = false
|
|
||||||
} else {
|
|
||||||
minWidthConstraint?.constant = minWidth
|
|
||||||
widthConstraint?.isActive = false
|
|
||||||
minWidthConstraint?.isActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//placeholder
|
|
||||||
textField.placeholder = placeholderText
|
|
||||||
|
|
||||||
//tooltip
|
|
||||||
tooltipModel = toolTipModel
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override func updateRules() {
|
override func updateRules() {
|
||||||
super.updateRules()
|
super.updateRules()
|
||||||
fieldType.handler().appendRules(for: self)
|
fieldType.handler().appendRules(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to update any Accessibility properties.
|
/// Used to update any Accessibility properties.
|
||||||
@ -390,10 +297,14 @@ open class InputField: EntryFieldBase {
|
|||||||
}
|
}
|
||||||
return super.resignFirstResponder()
|
return super.resignFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Public FieldType Properties
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Password
|
// MARK: - Password
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
internal var passwordActionType: PasswordAction = .show { didSet { setNeedsUpdate() } }
|
|
||||||
open var hidePasswordButtonText: String = "Hide" { didSet { setNeedsUpdate() } }
|
open var hidePasswordButtonText: String = "Hide" { didSet { setNeedsUpdate() } }
|
||||||
open var showPasswordButtonText: String = "Show" { didSet { setNeedsUpdate() } }
|
open var showPasswordButtonText: String = "Show" { didSet { setNeedsUpdate() } }
|
||||||
|
|
||||||
@ -402,149 +313,20 @@ open class InputField: EntryFieldBase {
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
open var dateFormat: DateFormat = .mmddyy { didSet { setNeedsUpdate() } }
|
open var dateFormat: DateFormat = .mmddyy { didSet { setNeedsUpdate() } }
|
||||||
|
|
||||||
//---------------------------------------------------
|
|
||||||
// MARK: - Credit Card
|
|
||||||
//---------------------------------------------------
|
|
||||||
internal var creditCardRawNumber: String = ""
|
|
||||||
internal var creditCardType: CreditCardType = .generic { didSet { setNeedsUpdate() } }
|
|
||||||
|
|
||||||
//---------------------------------------------------
|
|
||||||
// MARK: - Telephone
|
|
||||||
//---------------------------------------------------
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InputField: UITextFieldDelegate {
|
extension InputField: UITextFieldDelegate {
|
||||||
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
if fieldType == .creditCard {
|
fieldType.handler().textFieldDidBeginEditing(self, textField: textField)
|
||||||
textField.text = formatCreditCardNumber(creditCardRawNumber)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func textFieldDidEndEditing(_ textField: UITextField) {
|
public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
if self.fieldType == .creditCard {
|
fieldType.handler().textFieldDidEndEditing(self, textField: textField)
|
||||||
textField.text = maskCreditCardNumber(creditCardRawNumber)
|
|
||||||
}
|
|
||||||
validate()
|
validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
return fieldType.handler().textField(self, textField: textField, shouldChangeCharactersIn: range, replacementString: string)
|
||||||
// case text, number, inlineAction, password, creditCard, tel, date, securityCode
|
|
||||||
|
|
||||||
switch fieldType {
|
|
||||||
case .creditCard:
|
|
||||||
let allowedCharacters = CharacterSet.decimalDigits
|
|
||||||
if string.rangeOfCharacter(from: allowedCharacters.inverted) != nil && !string.isEmpty {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current text
|
|
||||||
let currentText = textField.text ?? ""
|
|
||||||
|
|
||||||
// Calculate the new text
|
|
||||||
let newText = (currentText as NSString).replacingCharacters(in: range, with: string)
|
|
||||||
|
|
||||||
// Remove any existing formatting
|
|
||||||
let rawNumber = newText.filter { $0.isNumber }
|
|
||||||
|
|
||||||
if rawNumber.count > creditCardType.maxLength {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the number with spaces
|
|
||||||
let formattedNumber = formatCreditCardNumber(rawNumber)
|
|
||||||
|
|
||||||
// Update the icon based on the first four digits
|
|
||||||
updateCardTypeIcon(rawNumber: rawNumber)
|
|
||||||
|
|
||||||
// Check again
|
|
||||||
if rawNumber.count > creditCardType.maxLength {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the formatted text
|
|
||||||
textField.text = formattedNumber
|
|
||||||
|
|
||||||
// Calculate the new cursor position
|
|
||||||
if let newPosition = textField.cursorPosition(range: range,
|
|
||||||
replacementString: string,
|
|
||||||
rawNumber: rawNumber,
|
|
||||||
formattedNumber: formattedNumber) {
|
|
||||||
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if all passes, then set the number1
|
|
||||||
creditCardRawNumber = rawNumber
|
|
||||||
|
|
||||||
// Prevent the default behavior
|
|
||||||
return false
|
|
||||||
|
|
||||||
case .date:
|
|
||||||
// Allow only numbers and limit the length of text.
|
|
||||||
guard let oldText = textField.text,
|
|
||||||
let textRange = Range(range, in: oldText),
|
|
||||||
string.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let newText = oldText.replacingCharacters(in: textRange, with: string)
|
|
||||||
if newText.count > dateFormat.maxLength {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if newText.count <= dateFormat.maxLength {
|
|
||||||
textField.text = formatDate(newText)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case .number:
|
|
||||||
// Allow only numbers
|
|
||||||
let allowedCharacters = CharacterSet.decimalDigits
|
|
||||||
let characterSet = CharacterSet(charactersIn: string)
|
|
||||||
return allowedCharacters.isSuperset(of: characterSet)
|
|
||||||
|
|
||||||
case .telephone:
|
|
||||||
// Allow only numbers and limit the length of text.
|
|
||||||
let allowedCharacters = CharacterSet(charactersIn: "01233456789")
|
|
||||||
let characterSet = CharacterSet(charactersIn: string)
|
|
||||||
let currentText = textField.text ?? ""
|
|
||||||
if !allowedCharacters.isSuperset(of: characterSet) { return false }
|
|
||||||
|
|
||||||
// Calculate the new text
|
|
||||||
let newText = (currentText as NSString).replacingCharacters(in: range, with: string)
|
|
||||||
|
|
||||||
// Remove any existing formatting
|
|
||||||
let rawNumber = newText.filter { $0.isNumber }
|
|
||||||
|
|
||||||
// Format the number with dashes
|
|
||||||
let formattedNumber = formatUSNumber(rawNumber)
|
|
||||||
|
|
||||||
// Set the formatted text
|
|
||||||
textField.text = formattedNumber
|
|
||||||
|
|
||||||
// Calculate the new cursor position
|
|
||||||
if let newPosition = textField.cursorPosition(range: range,
|
|
||||||
replacementString: string,
|
|
||||||
rawNumber: rawNumber,
|
|
||||||
formattedNumber: formattedNumber) {
|
|
||||||
textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent the default behavior
|
|
||||||
return false
|
|
||||||
|
|
||||||
case .securityCode:
|
|
||||||
// Allow only numbers and limit the length of text.
|
|
||||||
let allowedCharacters = CharacterSet.decimalDigits
|
|
||||||
let characterSet = CharacterSet(charactersIn: string)
|
|
||||||
return allowedCharacters.isSuperset(of: characterSet) && ((textField.text?.count ?? 0) + string.count - range.length) <= 4
|
|
||||||
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user