initial refactor to split out FieldTypes
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
parent
2d5f3d7652
commit
5672df7070
@ -141,6 +141,11 @@
|
||||
EAB5FF0129424ACB00998C17 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FF0029424ACB00998C17 /* UIControl.swift */; };
|
||||
EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFEB632A26473700C4C106 /* NSAttributedString.swift */; };
|
||||
EAC58BFD2BE935C300BA39FA /* TitleLockupTextColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58BFC2BE935C300BA39FA /* TitleLockupTextColor.swift */; };
|
||||
EAC58C062BED000200BA39FA /* CreditCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C052BED000200BA39FA /* CreditCard.swift */; };
|
||||
EAC58C082BED002D00BA39FA /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C072BED002D00BA39FA /* Date.swift */; };
|
||||
EAC58C0A2BED004E00BA39FA /* FieldType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C092BED004E00BA39FA /* FieldType.swift */; };
|
||||
EAC58C0C2BED01D500BA39FA /* Telephone.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C0B2BED01D500BA39FA /* Telephone.swift */; };
|
||||
EAC58C0E2BED021600BA39FA /* Password.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C0D2BED021600BA39FA /* Password.swift */; };
|
||||
EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; };
|
||||
EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; };
|
||||
EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; };
|
||||
@ -337,6 +342,11 @@
|
||||
EAB5FF0029424ACB00998C17 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
|
||||
EABFEB632A26473700C4C106 /* NSAttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedString.swift; sourceTree = "<group>"; };
|
||||
EAC58BFC2BE935C300BA39FA /* TitleLockupTextColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockupTextColor.swift; sourceTree = "<group>"; };
|
||||
EAC58C052BED000200BA39FA /* CreditCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCard.swift; sourceTree = "<group>"; };
|
||||
EAC58C072BED002D00BA39FA /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
||||
EAC58C092BED004E00BA39FA /* FieldType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldType.swift; sourceTree = "<group>"; };
|
||||
EAC58C0B2BED01D500BA39FA /* Telephone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telephone.swift; sourceTree = "<group>"; };
|
||||
EAC58C0D2BED021600BA39FA /* Password.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Password.swift; sourceTree = "<group>"; };
|
||||
EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
|
||||
EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
|
||||
EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
@ -875,6 +885,18 @@
|
||||
path = Tooltip;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EAC58C042BECFFEA00BA39FA /* FieldTypes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAC58C092BED004E00BA39FA /* FieldType.swift */,
|
||||
EAC58C052BED000200BA39FA /* CreditCard.swift */,
|
||||
EAC58C072BED002D00BA39FA /* Date.swift */,
|
||||
EAC58C0B2BED01D500BA39FA /* Telephone.swift */,
|
||||
EAC58C0D2BED021600BA39FA /* Password.swift */,
|
||||
);
|
||||
path = FieldTypes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EAC9257E29119B5D00091998 /* TextLink */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -907,6 +929,7 @@
|
||||
EAC925862911C9DE00091998 /* InputField */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAC58C042BECFFEA00BA39FA /* FieldTypes */,
|
||||
EAC925872911C9DE00091998 /* InputField.swift */,
|
||||
EA2DC9B32BE2C6FE004F58C5 /* TextField.swift */,
|
||||
EA6642942BCEBF9500D81DC4 /* TextLinkModel.swift */,
|
||||
@ -1142,6 +1165,7 @@
|
||||
EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */,
|
||||
EA4DB2FD28D3D0CA00103EE3 /* AnyEquatable.swift in Sources */,
|
||||
EA5E305A29510F8B0082B959 /* EnumSubset.swift in Sources */,
|
||||
EAC58C082BED002D00BA39FA /* Date.swift in Sources */,
|
||||
EA985BF7296C665E00F2FF2E /* IconName.swift in Sources */,
|
||||
EA8141102A127066004F60D2 /* UIColor+VDSColor.swift in Sources */,
|
||||
EAF7F0AF289B144C00B287F5 /* UnderlineLabelAttribute.swift in Sources */,
|
||||
@ -1195,12 +1219,14 @@
|
||||
EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */,
|
||||
EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */,
|
||||
EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */,
|
||||
EAC58C0A2BED004E00BA39FA /* FieldType.swift in Sources */,
|
||||
EA471F3A2A95587500CE9E58 /* LayoutConstraintable.swift in Sources */,
|
||||
EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */,
|
||||
EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */,
|
||||
EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */,
|
||||
EA0D1C3D2A6AD57600E5C127 /* Typography+Enums.swift in Sources */,
|
||||
EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */,
|
||||
EAC58C0C2BED01D500BA39FA /* Telephone.swift in Sources */,
|
||||
EAF7F0A2289AFB3900B287F5 /* Errorable.swift in Sources */,
|
||||
EA8E40912A7D3F6300934ED3 /* UIView+Accessibility.swift in Sources */,
|
||||
EA6F330E2B911E9000BACAB9 /* TextView.swift in Sources */,
|
||||
@ -1214,6 +1240,7 @@
|
||||
44604AD729CE196600E62B51 /* Line.swift in Sources */,
|
||||
1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */,
|
||||
EAF978212A99035B00C2FEA9 /* Enabling.swift in Sources */,
|
||||
EAC58C062BED000200BA39FA /* CreditCard.swift in Sources */,
|
||||
EA5E3058295105A40082B959 /* Tilelet.swift in Sources */,
|
||||
186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */,
|
||||
EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */,
|
||||
@ -1246,6 +1273,7 @@
|
||||
EA985C692971B90B00F2FF2E /* IconSize.swift in Sources */,
|
||||
71FC86E02B973AE500700965 /* DropShadowConfiguration.swift in Sources */,
|
||||
EA3362302891EB4A0071C351 /* Font.swift in Sources */,
|
||||
EAC58C0E2BED021600BA39FA /* Password.swift in Sources */,
|
||||
EAF7F0AD289B142900B287F5 /* StrikeThroughLabelAttribute.swift in Sources */,
|
||||
EAB5FEF12927F4AA00998C17 /* SelfSizingCollectionView.swift in Sources */,
|
||||
EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */,
|
||||
|
||||
103
VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift
Normal file
103
VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift
Normal file
@ -0,0 +1,103 @@
|
||||
//
|
||||
// CreditCard.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 5/9/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension InputField {
|
||||
enum CreditCardType: CaseIterable {
|
||||
case generic
|
||||
case visa
|
||||
case mastercard
|
||||
case amex
|
||||
case discover
|
||||
case dinersClub
|
||||
case jcb
|
||||
case chinaUnionPay
|
||||
|
||||
var image: UIImage {
|
||||
return BundleManager.shared.image(for: imageName)!
|
||||
}
|
||||
|
||||
var imageName: String {
|
||||
var imageName: String = "generic"
|
||||
switch self {
|
||||
case .visa: imageName = "visa"
|
||||
case .mastercard: imageName = "mastercard"
|
||||
case .amex: imageName = "amex"
|
||||
case .discover: imageName = "discover"
|
||||
case .dinersClub: imageName = "dinersClub"
|
||||
case .jcb: imageName = "jcb"
|
||||
default: imageName = "generic"
|
||||
}
|
||||
return imageName
|
||||
}
|
||||
|
||||
internal var separatorIndices: [Int] {
|
||||
switch self {
|
||||
case .dinersClub:
|
||||
return [4, 10]
|
||||
default:
|
||||
return [4, 8, 12]
|
||||
}
|
||||
}
|
||||
|
||||
internal var maxLength: Int {
|
||||
switch self {
|
||||
case .dinersClub: return 14
|
||||
default: return 16
|
||||
}
|
||||
}
|
||||
|
||||
static func from(iin: Int) -> CreditCardType? {
|
||||
switch iin {
|
||||
case 4000...4999:
|
||||
return .visa
|
||||
case 5100...5599, 2221...2720:
|
||||
return .mastercard
|
||||
case 3400...3499, 3700...3799:
|
||||
return .amex
|
||||
case 6011, 6221...6229, 6440...6499, 6500...6599:
|
||||
return .discover
|
||||
case 3600...3699, 3800...3999:
|
||||
return .dinersClub
|
||||
case 3528...3589:
|
||||
return .jcb
|
||||
case 6200...6299, 6000...6010, 8100...8199:
|
||||
return .chinaUnionPay
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
guard rawNumber.count >= 4,
|
||||
let firstFourDigits = Int(String(rawNumber.prefix(4))),
|
||||
let creditCardType = CreditCardType.from(iin: firstFourDigits) else {
|
||||
leftImageView.image = BundleManager.shared.image(for: CreditCardType.generic.imageName)
|
||||
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
|
||||
}
|
||||
}
|
||||
65
VDS/Components/TextFields/InputField/FieldTypes/Date.swift
Normal file
65
VDS/Components/TextFields/InputField/FieldTypes/Date.swift
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// Date.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 5/9/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension InputField {
|
||||
public enum DateFormat: String, CaseIterable {
|
||||
case mmyy
|
||||
case mmddyy
|
||||
case mmddyyyy
|
||||
|
||||
public var placeholderText: String {
|
||||
switch self {
|
||||
case .mmyy: "MM/YY"
|
||||
case .mmddyy: "MM/DD/YY"
|
||||
case .mmddyyyy: "MM/DD/YYYY"
|
||||
}
|
||||
}
|
||||
|
||||
public var formatString: String {
|
||||
switch self {
|
||||
case .mmyy: "MM/yy"
|
||||
case .mmddyy: "MM/dd/yy"
|
||||
case .mmddyyyy: "MM/dd/yyyy"
|
||||
}
|
||||
}
|
||||
|
||||
public var maxLength: Int {
|
||||
switch self {
|
||||
case .mmyy: 5
|
||||
case .mmddyy: 8
|
||||
case .mmddyyyy: 10
|
||||
}
|
||||
}
|
||||
|
||||
internal var separatorIndices: [Int] {
|
||||
switch self {
|
||||
case .mmyy: [2]
|
||||
case .mmddyy: [2,4]
|
||||
case .mmddyyyy: [2,4]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
//
|
||||
// FieldType.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 5/9/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension InputField {
|
||||
public enum FieldType: String, CaseIterable {
|
||||
case text, number, inlineAction, password, creditCard, tel, date, securityCode
|
||||
|
||||
public var keyboardType: UIKeyboardType {
|
||||
switch self {
|
||||
case .number:
|
||||
.numberPad
|
||||
case .tel:
|
||||
.phonePad
|
||||
case .creditCard:
|
||||
.numberPad
|
||||
case .date:
|
||||
.numberPad
|
||||
case .securityCode:
|
||||
.numberPad
|
||||
default:
|
||||
.default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
//
|
||||
// Password.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 5/9/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension InputField {
|
||||
|
||||
enum PasswordAction {
|
||||
case show, hide
|
||||
|
||||
func toggle() -> PasswordAction {
|
||||
self == .hide ? .show : .hide
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Tel.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 5/9/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension InputField {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@ -31,14 +31,6 @@ open class InputField: EntryFieldBase {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Enums
|
||||
//--------------------------------------------------
|
||||
/// Enum used to describe the input type.
|
||||
public enum FieldType: String, CaseIterable {
|
||||
case text, number, inlineAction, password, creditCard, tel, date, securityCode
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
@ -77,7 +69,7 @@ open class InputField: EntryFieldBase {
|
||||
/// Representing the type of input.
|
||||
open var fieldType: FieldType = .text { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var leftIcon: Icon = Icon().with { $0.size = .medium }
|
||||
open var leftImageView = UIImageView().with { $0.height(21); $0.width(32) }
|
||||
|
||||
open var actionTextLink = TextLink().with { $0.contentEdgeInsets = .top(-2) }
|
||||
|
||||
@ -165,7 +157,7 @@ open class InputField: EntryFieldBase {
|
||||
controlContainerView.addSubview(controlStackView)
|
||||
controlStackView.pinToSuperView()
|
||||
|
||||
controlStackView.addArrangedSubview(leftIcon)
|
||||
controlStackView.addArrangedSubview(leftImageView)
|
||||
controlStackView.addArrangedSubview(textField)
|
||||
|
||||
textField.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
@ -266,7 +258,7 @@ open class InputField: EntryFieldBase {
|
||||
open func updateFieldType() {
|
||||
|
||||
var minWidth: CGFloat = 40.0
|
||||
var leftIconName: Icon.Name?
|
||||
var leftImageName: String?
|
||||
var actionModel: InputField.TextLinkModel?
|
||||
var toolTipModel: Tooltip.TooltipModel? = tooltipModel
|
||||
var isSecureTextEntry = false
|
||||
@ -303,7 +295,7 @@ open class InputField: EntryFieldBase {
|
||||
|
||||
case .creditCard:
|
||||
minWidth = 288.0
|
||||
|
||||
leftImageName = creditCardType.imageName
|
||||
case .tel:
|
||||
minWidth = 176.0
|
||||
|
||||
@ -319,10 +311,10 @@ open class InputField: EntryFieldBase {
|
||||
textField.isSecureTextEntry = isSecureTextEntry
|
||||
|
||||
//leftIcon
|
||||
leftIcon.surface = surface
|
||||
leftIcon.color = iconColorConfiguration.getColor(self)
|
||||
leftIcon.name = leftIconName
|
||||
leftIcon.isHidden = leftIconName == nil
|
||||
if let leftImageName {
|
||||
leftImageView.image = BundleManager.shared.image(for: creditCardType.imageName)?.withTintColor(iconColorConfiguration.getColor(self))
|
||||
}
|
||||
leftImageView.isHidden = leftImageName == nil
|
||||
|
||||
//actionLink
|
||||
actionTextLink.surface = surface
|
||||
@ -362,7 +354,7 @@ open class InputField: EntryFieldBase {
|
||||
case .creditCard:
|
||||
if let text = textField.text, text.count > 0 {
|
||||
let rule = CharacterCountRule().copyWith {
|
||||
$0.maxLength = creditCardMaxLength
|
||||
$0.maxLength = creditCardType.maxLength
|
||||
$0.compareType = .equals
|
||||
$0.errorMessage = "Enter a valid credit card."
|
||||
}
|
||||
@ -431,115 +423,9 @@ open class InputField: EntryFieldBase {
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Password
|
||||
// MARK: - Private Methods
|
||||
//--------------------------------------------------
|
||||
enum PasswordAction {
|
||||
case show, hide
|
||||
|
||||
func toggle() -> PasswordAction {
|
||||
self == .hide ? .show : .hide
|
||||
}
|
||||
}
|
||||
|
||||
internal var passwordActionType: PasswordAction = .show { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var hidePasswordButtonText: String = "Hide" { didSet { setNeedsUpdate() } }
|
||||
open var showPasswordButtonText: String = "Show" { didSet { setNeedsUpdate() } }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Date
|
||||
//--------------------------------------------------
|
||||
open var dateFormat: DateFormat = .mmddyy { didSet { setNeedsUpdate() } }
|
||||
|
||||
private 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
|
||||
}
|
||||
|
||||
//---------------------------------------------------
|
||||
// MARK: - Credit Card
|
||||
//---------------------------------------------------
|
||||
private var creditCardRawNumber: String = ""
|
||||
private var creditCardMaxLength = 16
|
||||
|
||||
private func formatCreditCardNumber(_ number: String) -> String {
|
||||
// Format the number in the style XXXX XXXX XXXX XXXX
|
||||
var formattedNumber = ""
|
||||
for (index, char) in number.enumerated() {
|
||||
if index != 0 && index % 4 == 0 {
|
||||
formattedNumber.append(" ")
|
||||
}
|
||||
formattedNumber.append(char)
|
||||
}
|
||||
|
||||
return formattedNumber
|
||||
}
|
||||
|
||||
private func updateCardTypeIcon(rawNumber: String) {
|
||||
// let firstFourDigits = String(rawNumber.prefix(4))
|
||||
// if let icon = cardTypeIcons[firstFourDigits] {
|
||||
// cardTypeIconView.image = icon
|
||||
// } else {
|
||||
// cardTypeIconView.image = nil
|
||||
// }
|
||||
}
|
||||
|
||||
private func maskCreditCardNumber(_ number: String) -> String {
|
||||
// Mask the first 12 characters if the length is 16
|
||||
let rawNumber = number.filter { $0.isNumber }
|
||||
guard rawNumber.count == creditCardMaxLength else { return formatCreditCardNumber(number) }
|
||||
let lastFourDigits = rawNumber.suffix(4)
|
||||
let maskedSection = String(repeating: "•", count: 12)
|
||||
let formattedMaskSection = formatCreditCardNumber(maskedSection)
|
||||
return formattedMaskSection + " " + lastFourDigits
|
||||
}
|
||||
|
||||
//---------------------------------------------------
|
||||
// MARK: - Telephone
|
||||
//---------------------------------------------------
|
||||
private 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
|
||||
}
|
||||
|
||||
private func getTelCursorPosition(textField: UITextField, range: NSRange, replacementString string: String, rawNumber: String, formattedNumber: String) -> UITextPosition? {
|
||||
internal func cursorPosition(textField: UITextField, range: NSRange, replacementString string: String, rawNumber: String, formattedNumber: String) -> UITextPosition? {
|
||||
let start = range.location
|
||||
let length = string.count
|
||||
|
||||
@ -560,6 +446,28 @@ open class InputField: EntryFieldBase {
|
||||
return textField.position(from: textField.beginningOfDocument, offset: finalCursorLocation)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Password
|
||||
//--------------------------------------------------
|
||||
internal var passwordActionType: PasswordAction = .show { didSet { setNeedsUpdate() } }
|
||||
open var hidePasswordButtonText: String = "Hide" { didSet { setNeedsUpdate() } }
|
||||
open var showPasswordButtonText: String = "Show" { didSet { setNeedsUpdate() } }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Date
|
||||
//--------------------------------------------------
|
||||
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 {
|
||||
@ -596,7 +504,7 @@ extension InputField: UITextFieldDelegate {
|
||||
// Remove any existing formatting
|
||||
let rawNumber = newText.filter { $0.isNumber }
|
||||
|
||||
if rawNumber.count > creditCardMaxLength {
|
||||
if rawNumber.count > creditCardType.maxLength {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -606,11 +514,16 @@ extension InputField: UITextFieldDelegate {
|
||||
// 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 = getTelCursorPosition(textField: textField,
|
||||
if let newPosition = cursorPosition(textField: textField,
|
||||
range: range,
|
||||
replacementString: string,
|
||||
rawNumber: rawNumber,
|
||||
@ -670,7 +583,7 @@ extension InputField: UITextFieldDelegate {
|
||||
textField.text = formattedNumber
|
||||
|
||||
// Calculate the new cursor position
|
||||
if let newPosition = getTelCursorPosition(textField: textField,
|
||||
if let newPosition = cursorPosition(textField: textField,
|
||||
range: range,
|
||||
replacementString: string,
|
||||
rawNumber: rawNumber,
|
||||
@ -693,62 +606,21 @@ extension InputField: UITextFieldDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
extension InputField.FieldType {
|
||||
extension String {
|
||||
|
||||
public var keyboardType: UIKeyboardType {
|
||||
switch self {
|
||||
case .number:
|
||||
.numberPad
|
||||
case .tel:
|
||||
.phonePad
|
||||
case .creditCard:
|
||||
.numberPad
|
||||
case .date:
|
||||
.numberPad
|
||||
case .securityCode:
|
||||
.numberPad
|
||||
default:
|
||||
.default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension InputField {
|
||||
public enum DateFormat: String, CaseIterable {
|
||||
case mmyy
|
||||
case mmddyy
|
||||
case mmddyyyy
|
||||
|
||||
public var placeholderText: String {
|
||||
switch self {
|
||||
case .mmyy: "MM/YY"
|
||||
case .mmddyy: "MM/DD/YY"
|
||||
case .mmddyyyy: "MM/DD/YYYY"
|
||||
}
|
||||
}
|
||||
|
||||
public var formatString: String {
|
||||
switch self {
|
||||
case .mmyy: "MM/yy"
|
||||
case .mmddyy: "MM/dd/yy"
|
||||
case .mmddyyyy: "MM/dd/yyyy"
|
||||
}
|
||||
}
|
||||
|
||||
public var maxLength: Int {
|
||||
switch self {
|
||||
case .mmyy: 5
|
||||
case .mmddyy: 8
|
||||
case .mmddyyyy: 10
|
||||
}
|
||||
}
|
||||
|
||||
internal var separatorIndices: [Int] {
|
||||
switch self {
|
||||
case .mmyy: [2]
|
||||
case .mmddyy: [2,4]
|
||||
case .mmddyyyy: [2,4]
|
||||
}
|
||||
internal static func format(_ value: String, indices: [Int], with separator: String) -> String {
|
||||
var formattedString = ""
|
||||
var currentIndex = value.startIndex
|
||||
|
||||
for index in 0..<value.count {
|
||||
if indices.contains(index) {
|
||||
formattedString.append(separator)
|
||||
}
|
||||
formattedString.append(value[currentIndex])
|
||||
currentIndex = value.index(after: currentIndex)
|
||||
}
|
||||
|
||||
return formattedString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user