diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 0be691c8..c60f125a 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -146,6 +146,10 @@ 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 */; }; + EAC58C122BED0DDD00BA39FA /* Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C112BED0DDD00BA39FA /* Text.swift */; }; + EAC58C142BED0DEC00BA39FA /* Number.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C132BED0DEC00BA39FA /* Number.swift */; }; + EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C152BED0E0300BA39FA /* InlineAction.swift */; }; + EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.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 */; }; @@ -347,6 +351,10 @@ EAC58C092BED004E00BA39FA /* FieldType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldType.swift; sourceTree = ""; }; EAC58C0B2BED01D500BA39FA /* Telephone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telephone.swift; sourceTree = ""; }; EAC58C0D2BED021600BA39FA /* Password.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Password.swift; sourceTree = ""; }; + EAC58C112BED0DDD00BA39FA /* Text.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Text.swift; sourceTree = ""; }; + EAC58C132BED0DEC00BA39FA /* Number.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Number.swift; sourceTree = ""; }; + EAC58C152BED0E0300BA39FA /* InlineAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAction.swift; sourceTree = ""; }; + EAC58C172BED0E2300BA39FA /* SecurityCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCode.swift; sourceTree = ""; }; EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = ""; }; @@ -891,8 +899,12 @@ EAC58C092BED004E00BA39FA /* FieldType.swift */, EAC58C052BED000200BA39FA /* CreditCard.swift */, EAC58C072BED002D00BA39FA /* Date.swift */, - EAC58C0B2BED01D500BA39FA /* Telephone.swift */, + EAC58C152BED0E0300BA39FA /* InlineAction.swift */, + EAC58C132BED0DEC00BA39FA /* Number.swift */, EAC58C0D2BED021600BA39FA /* Password.swift */, + EAC58C172BED0E2300BA39FA /* SecurityCode.swift */, + EAC58C0B2BED01D500BA39FA /* Telephone.swift */, + EAC58C112BED0DDD00BA39FA /* Text.swift */, ); path = FieldTypes; sourceTree = ""; @@ -1224,6 +1236,7 @@ EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */, EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */, EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */, + EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */, EA0D1C3D2A6AD57600E5C127 /* Typography+Enums.swift in Sources */, EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */, EAC58C0C2BED01D500BA39FA /* Telephone.swift in Sources */, @@ -1263,6 +1276,7 @@ EA985BF5296C60C000F2FF2E /* Icon.swift in Sources */, EA3361AA288B25E40071C351 /* Disabling.swift in Sources */, EA3361B6288B2A410071C351 /* Control.swift in Sources */, + EAC58C122BED0DDD00BA39FA /* Text.swift in Sources */, 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */, EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */, EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */, @@ -1279,7 +1293,9 @@ EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */, EA3361A8288B23300071C351 /* UIColor.swift in Sources */, EA2DC9B42BE2C6FE004F58C5 /* TextField.swift in Sources */, + EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */, EAC9257D29119B5400091998 /* TextLink.swift in Sources */, + EAC58C142BED0DEC00BA39FA /* Number.swift in Sources */, EA596ABF2A16B4F500300C4B /* Tabs.swift in Sources */, EAD062A72A3B67770015965D /* UIView+CALayer.swift in Sources */, EAD068942A560C13002E3A2D /* LoaderLaunchable.swift in Sources */, diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index dc4aeaf2..10dd99c4 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -75,6 +75,29 @@ extension InputField { } } + class CreditCardHandler: NSObject, FieldTypeHandler { + static let shared = CreditCardHandler() + + var keyboardType: UIKeyboardType { .numberPad } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) { + if let text = inputField.textField.text, text.count > 0 { + let rule = CharacterCountRule().copyWith { + $0.maxLength = inputField.creditCardType.maxLength + $0.compareType = .equals + $0.errorMessage = "Enter a valid credit card." + } + 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: " ") diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Date.swift b/VDS/Components/TextFields/InputField/FieldTypes/Date.swift index a8ef3d78..44dba3ac 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Date.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Date.swift @@ -6,8 +6,33 @@ // import Foundation +import UIKit extension InputField { + + class DateHandler: NSObject, FieldTypeHandler { + static let shared = DateHandler() + + var keyboardType: UIKeyboardType { .numberPad } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) { + if let text = inputField.textField.text, text.count > 0 { + let rule = CharacterCountRule().copyWith { + $0.maxLength = inputField.dateFormat.maxLength + $0.compareType = .equals + $0.errorMessage = "Enter a valid date." + } + inputField.rules.append(.init(rule)) + } + } + } + public enum DateFormat: String, CaseIterable { case mmyy case mmddyy diff --git a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift index db09b3ca..f0a613ad 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift @@ -9,7 +9,7 @@ import Foundation import UIKit extension InputField { - protocol FieldHandler: UITextFieldDelegate { + protocol FieldTypeHandler: UITextFieldDelegate { var keyboardType: UIKeyboardType { get } func configure(for inputField: InputField) func appendRules(for inputField: InputField) @@ -18,7 +18,7 @@ extension InputField { public enum FieldType: String, CaseIterable { case text, number, inlineAction, password, creditCard, telephone, date, securityCode - func handler() -> FieldHandler { + func handler() -> FieldTypeHandler { switch self { case .text: return TextHandler.shared @@ -43,150 +43,5 @@ extension InputField { handler().keyboardType } - internal func appendRules(for textField: InputField) { - handler().appendRules(for: textField) - } } } - -extension InputField { - class TextHandler: NSObject, FieldHandler { - static let shared = TextHandler() - - var keyboardType: UIKeyboardType { .default } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) {} - } - - class NumberHandler: NSObject, FieldHandler { - static let shared = NumberHandler() - - var keyboardType: UIKeyboardType { .numberPad } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) {} - } - - class InlineActionHandler: NSObject, FieldHandler { - static let shared = InlineActionHandler() - - var keyboardType: UIKeyboardType { .default } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) {} - } - - class PasswordHandler: NSObject, FieldHandler { - static let shared = PasswordHandler() - - var keyboardType: UIKeyboardType { .default } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) {} - } - - class CreditCardHandler: NSObject, FieldHandler { - static let shared = CreditCardHandler() - - var keyboardType: UIKeyboardType { .numberPad } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) { - if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = inputField.creditCardType.maxLength - $0.compareType = .equals - $0.errorMessage = "Enter a valid credit card." - } - inputField.rules.append(.init(rule)) - } - } - } - - class TelephoneHandler: NSObject, FieldHandler { - static let shared = TelephoneHandler() - - var keyboardType: UIKeyboardType { .phonePad } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) { - if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = "XXX-XXX-XXXX".count - $0.compareType = .equals - $0.errorMessage = "Enter a valid telephone." - } - inputField.rules.append(.init(rule)) - } - } - } - - class DateHandler: NSObject, FieldHandler { - static let shared = DateHandler() - - var keyboardType: UIKeyboardType { .numberPad } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) { - if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = inputField.dateFormat.maxLength - $0.compareType = .equals - $0.errorMessage = "Enter a valid date." - } - inputField.rules.append(.init(rule)) - } - } - } - - class SecurityCodeHandler: NSObject, FieldHandler { - static let shared = SecurityCodeHandler() - - var keyboardType: UIKeyboardType { .numberPad } - - private override init() { - super.init() - } - - func configure(for inputField: InputField) {} - - func appendRules(for inputField: InputField) {} - } - -} diff --git a/VDS/Components/TextFields/InputField/FieldTypes/InlineAction.swift b/VDS/Components/TextFields/InputField/FieldTypes/InlineAction.swift new file mode 100644 index 00000000..11845e70 --- /dev/null +++ b/VDS/Components/TextFields/InputField/FieldTypes/InlineAction.swift @@ -0,0 +1,27 @@ +// +// InlineAction.swift +// VDS +// +// Created by Matt Bruce on 5/9/24. +// + +import Foundation +import UIKit + +extension InputField { + + class InlineActionHandler: NSObject, FieldTypeHandler { + static let shared = InlineActionHandler() + + var keyboardType: UIKeyboardType { .default } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) {} + } + +} diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Number.swift b/VDS/Components/TextFields/InputField/FieldTypes/Number.swift new file mode 100644 index 00000000..0f274681 --- /dev/null +++ b/VDS/Components/TextFields/InputField/FieldTypes/Number.swift @@ -0,0 +1,27 @@ +// +// Number.swift +// VDS +// +// Created by Matt Bruce on 5/9/24. +// + +import Foundation +import UIKit + +extension InputField { + + class NumberHandler: NSObject, FieldTypeHandler { + static let shared = NumberHandler() + + var keyboardType: UIKeyboardType { .numberPad } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) {} + } + +} diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift index 5a81d59b..00f7a2c4 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit extension InputField { @@ -17,4 +18,18 @@ extension InputField { } } + class PasswordHandler: NSObject, FieldTypeHandler { + static let shared = PasswordHandler() + + var keyboardType: UIKeyboardType { .default } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) {} + } + } diff --git a/VDS/Components/TextFields/InputField/FieldTypes/SecurityCode.swift b/VDS/Components/TextFields/InputField/FieldTypes/SecurityCode.swift new file mode 100644 index 00000000..3ad802bd --- /dev/null +++ b/VDS/Components/TextFields/InputField/FieldTypes/SecurityCode.swift @@ -0,0 +1,27 @@ +// +// SecurityCode.swift +// VDS +// +// Created by Matt Bruce on 5/9/24. +// + +import Foundation +import UIKit + +extension InputField { + + class SecurityCodeHandler: NSObject, FieldTypeHandler { + static let shared = SecurityCodeHandler() + + var keyboardType: UIKeyboardType { .numberPad } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) {} + } + +} diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift index 3a13bc84..bcbc882c 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift @@ -6,6 +6,7 @@ // import Foundation +import UIKit extension InputField { @@ -40,4 +41,27 @@ extension InputField { return formattedNumber } + class TelephoneHandler: NSObject, FieldTypeHandler { + static let shared = TelephoneHandler() + + var keyboardType: UIKeyboardType { .phonePad } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) { + if let text = inputField.textField.text, text.count > 0 { + let rule = CharacterCountRule().copyWith { + $0.maxLength = "XXX-XXX-XXXX".count + $0.compareType = .equals + $0.errorMessage = "Enter a valid telephone." + } + inputField.rules.append(.init(rule)) + } + } + } + } diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Text.swift b/VDS/Components/TextFields/InputField/FieldTypes/Text.swift new file mode 100644 index 00000000..25715c9f --- /dev/null +++ b/VDS/Components/TextFields/InputField/FieldTypes/Text.swift @@ -0,0 +1,25 @@ +// +// Text.swift +// VDS +// +// Created by Matt Bruce on 5/9/24. +// + +import Foundation +import UIKit + +extension InputField { + class TextHandler: NSObject, FieldTypeHandler { + static let shared = TextHandler() + + var keyboardType: UIKeyboardType { .default } + + private override init() { + super.init() + } + + func configure(for inputField: InputField) {} + + func appendRules(for inputField: InputField) {} + } +} diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index ccb7820c..23b2bffa 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -349,7 +349,7 @@ open class InputField: EntryFieldBase { override func updateRules() { super.updateRules() - fieldType.appendRules(for: self) + fieldType.handler().appendRules(for: self) } /// Used to update any Accessibility properties. @@ -389,31 +389,6 @@ open class InputField: EntryFieldBase { } return super.resignFirstResponder() } - - //-------------------------------------------------- - // MARK: - Private Methods - //-------------------------------------------------- - internal func cursorPosition(textField: UITextField, range: NSRange, replacementString string: String, rawNumber: String, formattedNumber: String) -> UITextPosition? { - let start = range.location - let length = string.count - - let newCursorLocation = start + length - - // Adjust the cursor position to skip over formatting characters - var formattedCharacterCount = 0 - for (index, character) in formattedNumber.enumerated() { - if index >= newCursorLocation + formattedCharacterCount { - break - } - if !character.isNumber { - formattedCharacterCount += 1 - } - } - - let finalCursorLocation = min(newCursorLocation + formattedCharacterCount, formattedNumber.count) - return textField.position(from: textField.beginningOfDocument, offset: finalCursorLocation) - } - //-------------------------------------------------- // MARK: - Password //-------------------------------------------------- @@ -491,8 +466,7 @@ extension InputField: UITextFieldDelegate { textField.text = formattedNumber // Calculate the new cursor position - if let newPosition = cursorPosition(textField: textField, - range: range, + if let newPosition = textField.cursorPosition(range: range, replacementString: string, rawNumber: rawNumber, formattedNumber: formattedNumber) { @@ -551,8 +525,7 @@ extension InputField: UITextFieldDelegate { textField.text = formattedNumber // Calculate the new cursor position - if let newPosition = cursorPosition(textField: textField, - range: range, + if let newPosition = textField.cursorPosition(range: range, replacementString: string, rawNumber: rawNumber, formattedNumber: formattedNumber) { @@ -592,3 +565,26 @@ extension String { } } + +extension UITextField { + internal func cursorPosition(range: NSRange, replacementString string: String, rawNumber: String, formattedNumber: String) -> UITextPosition? { + let start = range.location + let length = string.count + + let newCursorLocation = start + length + + // Adjust the cursor position to skip over formatting characters + var formattedCharacterCount = 0 + for (index, character) in formattedNumber.enumerated() { + if index >= newCursorLocation + formattedCharacterCount { + break + } + if !character.isNumber { + formattedCharacterCount += 1 + } + } + + let finalCursorLocation = min(newCursorLocation + formattedCharacterCount, formattedNumber.count) + return position(from: beginningOfDocument, offset: finalCursorLocation) + } +}