From 14a7725784c0fdc01d3bc143dac3ed3494db1210 Mon Sep 17 00:00:00 2001 From: Georgiy Malyukov <> Date: Fri, 31 Aug 2018 23:13:52 +0300 Subject: [PATCH 1/4] - Fixed issue with delegate call. --- Source/FormTextField.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/FormTextField.swift b/Source/FormTextField.swift index 1f614e8..474d369 100644 --- a/Source/FormTextField.swift +++ b/Source/FormTextField.swift @@ -243,6 +243,8 @@ extension FormTextField { } updateActive(true) + + textFieldDelegate?.formTextFieldDidBeginEditing?(self) } public func textFieldDidEndEditing(_: UITextField) { From 9f6d601efecf614c7f7e19185168901c0a3c9bde Mon Sep 17 00:00:00 2001 From: Georgiy Malyukov <> Date: Sun, 2 Sep 2018 02:14:00 +0300 Subject: [PATCH 2/4] - Added ability to set custom errors text for each error. --- Source/FormTextField.swift | 2 + Source/Validation/Validation.swift | 91 ++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/Source/FormTextField.swift b/Source/FormTextField.swift index 474d369..eee14e5 100644 --- a/Source/FormTextField.swift +++ b/Source/FormTextField.swift @@ -118,6 +118,7 @@ open class FormTextField: UITextField, UITextFieldDelegate { // Sets the textfields at the initial state, clear text and resets appearance too open func reset() { + inputValidator?.validation?.errors.reset() updateText(nil) updateEnabled(isEnabled) } @@ -194,6 +195,7 @@ open class FormTextField: UITextField, UITextFieldDelegate { open func validate(updatingUI: Bool = true) -> Bool { var isValid = true + inputValidator?.validation?.errors.reset() if let inputValidator = self.inputValidator { isValid = inputValidator.validateString(text ?? "") } diff --git a/Source/Validation/Validation.swift b/Source/Validation/Validation.swift index 6dcbb61..5022131 100755 --- a/Source/Validation/Validation.swift +++ b/Source/Validation/Validation.swift @@ -1,59 +1,114 @@ import Foundation -public struct Validation { +public class ValidationErrors: CustomStringConvertible { + + public typealias ValidationError = (text: String?, occured: Bool) + + public var minimumLength = ValidationError(nil, false) + public var maximumLength = ValidationError(nil, false) + public var minimumValue = ValidationError(nil, false) + public var maximumValue = ValidationError(nil, false) + public var characterSet = ValidationError(nil, false) + public var format = ValidationError(nil, false) + + public func reset() { + minimumLength.occured = false + maximumLength.occured = false + minimumValue.occured = false + maximumValue.occured = false + characterSet.occured = false + format.occured = false + } + + // MARK: - CustomStringConvertible + + public var description: String { + var string = "" + let list: [ValidationError] = [minimumLength, maximumLength, minimumValue, maximumValue, characterSet, format] + for item in list { + if let text = item.text, item.occured { + string.append(" \(text).") + } + } + return string.trimmingCharacters(in: .whitespaces) + } + +} + +public class Validation { + public var minimumLength = 0 public var maximumLength: Int? public var maximumValue: Double? public var minimumValue: Double? public var characterSet: CharacterSet? public var format: String? - - public init() {} + public var errors = ValidationErrors() + + public init(minLength: Int = 0, maxLength: Int? = nil, minValue: Double? = nil, maxValue: Double? = nil, charSet: CharacterSet? = nil, format: String? = nil) { + minimumLength = minLength + maximumLength = maxLength + minimumValue = minValue + maximumValue = maxValue + characterSet = charSet + self.format = format + } // Making complete false will cause minimumLength, minimumValue and format to be ignored // this is useful for partial validations, or validations where the final string is // in process of been completed. For example when entering characters into an UITextField public func validateString(_ string: String, complete: Bool = true) -> Bool { var valid = true - + errors.reset() + if complete { valid = (string.count >= minimumLength) + errors.minimumLength.occured = !valid } - if valid { - if let maximumLength = self.maximumLength { + if let maximumLength = self.maximumLength { + if valid { valid = (string.count <= maximumLength) } + errors.maximumLength.occured = !valid } - if valid { - let formatter = NumberFormatter() - let number = formatter.number(from: string) - if let number = number { - if let maximumValue = self.maximumValue { + let formatter = NumberFormatter() + let number = formatter.number(from: string) + if let number = number { + if let maximumValue = self.maximumValue { + if valid { valid = (number.doubleValue <= maximumValue) } + errors.maximumValue.occured = !valid + } - if valid && complete { - if let minimumValue = self.minimumValue { + if complete { + if let minimumValue = self.minimumValue { + if valid { valid = (number.doubleValue >= minimumValue) } + errors.minimumValue.occured = !valid } } } - if valid { - if let characterSet = self.characterSet { - let stringCharacterSet = CharacterSet(charactersIn: string) + if let characterSet = self.characterSet { + let stringCharacterSet = CharacterSet(charactersIn: string) + if valid { valid = characterSet.superSetOf(other: stringCharacterSet) } + errors.characterSet.occured = !valid } - if valid && complete { + if complete { if let format = self.format { let regex = try! NSRegularExpression(pattern: format, options: .caseInsensitive) let range = regex.rangeOfFirstMatch(in: string, options: .reportProgress, range: NSRange(location: 0, length: string.count)) - valid = (range.location == 0 && range.length == string.count) + if valid { + valid = (range.location == 0 && range.length == string.count) + } + errors.format.occured = !valid } } From e766c0bd95d096b32d3df8df9665f9da5f80827e Mon Sep 17 00:00:00 2001 From: Georgiy Malyukov <> Date: Sun, 2 Sep 2018 19:48:13 +0300 Subject: [PATCH 3/4] - Added default error texts setup. --- .../CardExpirationDateInputValidator.swift | 2 +- Source/Validation/Validation.swift | 56 ++++++++++++++++--- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/Source/InputValidator/CardExpirationDateInputValidator.swift b/Source/InputValidator/CardExpirationDateInputValidator.swift index 099a1bd..2f3057d 100755 --- a/Source/InputValidator/CardExpirationDateInputValidator.swift +++ b/Source/InputValidator/CardExpirationDateInputValidator.swift @@ -10,7 +10,7 @@ public struct CardExpirationDateInputValidator: InputValidatable { public var validation: Validation? public init(validation _: Validation? = nil) { - var predefinedValidation = Validation() + var predefinedValidation = Validation(name: "Card expiration date") predefinedValidation.minimumLength = "MM/YY".count predefinedValidation.maximumLength = "MM/YY".count // predefinedValidation.required = validation?.required ?? false diff --git a/Source/Validation/Validation.swift b/Source/Validation/Validation.swift index 5022131..fbde7c9 100755 --- a/Source/Validation/Validation.swift +++ b/Source/Validation/Validation.swift @@ -30,28 +30,68 @@ public class ValidationErrors: CustomStringConvertible { string.append(" \(text).") } } - return string.trimmingCharacters(in: .whitespaces) + // remove whitespaces and possible repeating dots + return string.trimmingCharacters(in: .whitespaces).replacingOccurrences(of: "..", with: ".") } } public class Validation { - public var minimumLength = 0 - public var maximumLength: Int? - public var maximumValue: Double? - public var minimumValue: Double? - public var characterSet: CharacterSet? - public var format: String? + public var name: String? { didSet { reloadErrorTexts() } } + public var minimumLength = 0 { didSet { reloadErrorTexts() } } + public var maximumLength: Int? { didSet { reloadErrorTexts() } } + public var maximumValue: Double? { didSet { reloadErrorTexts() } } + public var minimumValue: Double? { didSet { reloadErrorTexts() } } + public var characterSet: CharacterSet? { didSet { reloadErrorTexts() } } + public var format: String? { didSet { reloadErrorTexts() } } public var errors = ValidationErrors() - public init(minLength: Int = 0, maxLength: Int? = nil, minValue: Double? = nil, maxValue: Double? = nil, charSet: CharacterSet? = nil, format: String? = nil) { + public init(name: String? = nil, minLength: Int = 0, maxLength: Int? = nil, minValue: Double? = nil, maxValue: Double? = nil, charSet: CharacterSet? = nil, format: String? = nil) { + self.name = name minimumLength = minLength maximumLength = maxLength + if maximumLength != nil { + assert(minimumLength <= maximumLength!, "Invalid min/max length bounds.") + } minimumValue = minValue maximumValue = maxValue + if let min = minimumValue, let max = maximumValue { + assert(min <= max, "Invalid min/max value bounds.") + } characterSet = charSet self.format = format + // configure default errors text + reloadErrorTexts() + } + + /// Sets default texts for different error types. Depends on `name` field, it must be non-nil to proceed. + public func reloadErrorTexts() { + guard let name = name else { + return + } + if errors.minimumLength.text == nil { + errors.minimumLength.text = ( + minimumLength == 1 + ? "\(name) is required." + : "\(name) must contain \(minimumLength) characters or more." + ) + } + if errors.maximumLength.text == nil && maximumLength != nil { + errors.maximumLength.text = "\(name) must contain \(maximumLength!) characters or less." + } + if errors.minimumValue.text == nil && minimumValue != nil { + errors.minimumValue.text = "\(name) must be greater than or equal to \(minimumValue!)." + } + if errors.maximumValue.text == nil && maximumValue != nil { + errors.maximumValue.text = "\(name) must be less than or equal to \(maximumValue!)." + } + if errors.characterSet.text == nil && characterSet != nil { + errors.characterSet.text = "\(name) must contain only characters in set \(characterSet!)." + } + if errors.format.text == nil && self.format != nil { + errors.format.text = "\(name) has invalid format." + } } // Making complete false will cause minimumLength, minimumValue and format to be ignored From 90141c9cc7cea8d949c92ab89c1c03f24d61f9ef Mon Sep 17 00:00:00 2001 From: Georgiy Malyukov <> Date: Sun, 2 Sep 2018 20:07:38 +0300 Subject: [PATCH 4/4] - Fixed min length error text. --- Source/Validation/Validation.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Validation/Validation.swift b/Source/Validation/Validation.swift index fbde7c9..18a2a06 100755 --- a/Source/Validation/Validation.swift +++ b/Source/Validation/Validation.swift @@ -65,16 +65,16 @@ public class Validation { reloadErrorTexts() } - /// Sets default texts for different error types. Depends on `name` field, it must be non-nil to proceed. + /// Sets default texts for different error types. Depends on the `name` field, it must be non-nil to proceed. public func reloadErrorTexts() { guard let name = name else { return } - if errors.minimumLength.text == nil { + if errors.minimumLength.text == nil && minimumLength > 0 { errors.minimumLength.text = ( minimumLength == 1 - ? "\(name) is required." - : "\(name) must contain \(minimumLength) characters or more." + ? "\(name) is required." + : "\(name) must contain \(minimumLength) characters or more." ) } if errors.maximumLength.text == nil && maximumLength != nil {