diff --git a/README.md b/README.md index ff37d6b..f690c41 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,9 @@ class SSNVRule: RegexRule { } ``` +## Documentation +Checkout the docs here. + Credits ------- diff --git a/SwiftValidator.podspec b/SwiftValidator.podspec index 4e31e32..d5dff3e 100644 --- a/SwiftValidator.podspec +++ b/SwiftValidator.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SwiftValidator" - s.version = "3.0.1" + s.version = "3.0.3" s.summary = "A UITextField Validation library for Swift" s.homepage = "https://github.com/jpotts18/SwiftValidator" s.screenshots = "https://raw.githubusercontent.com/jpotts18/SwiftValidator/master/swift-validator-v2.gif" @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.social_media_url = "http://twitter.com/jpotts18" s.platform = :ios s.ios.deployment_target = '8.0' - s.source = { :git => "https://github.com/jpotts18/SwiftValidator.git", :tag => "3.0.1" } + s.source = { :git => "https://github.com/jpotts18/SwiftValidator.git", :tag => "3.0.3" } s.source_files = "SwiftValidator/**/*.swift" s.exclude_files = "Validator/AppDelegate.swift" s.frameworks = ['Foundation', 'UIKit'] diff --git a/SwiftValidator/Core/ValidationDelegate.swift b/SwiftValidator/Core/ValidationDelegate.swift index 7e70372..65185b2 100644 --- a/SwiftValidator/Core/ValidationDelegate.swift +++ b/SwiftValidator/Core/ValidationDelegate.swift @@ -16,10 +16,19 @@ import UIKit This method will be called on delegate object when validation is successful. - returns: No return value. */ - func validationSuccessful() + optional func validationSuccessful() /** This method will be called on delegate object when validation fails. - returns: No return value. */ - func validationFailed(errors: [UITextField:ValidationError]) + optional func validationFailed(errors: [UITextField:ValidationError]) + /// This method is called as soon a validation starts. Should be used to do things like disable buttons, textfields once validation is started. + func willValidate() + /// This method is called just before validator's fields are validated. Should return true if validation is to be continued. Should return + /// false if validation should not run. + func shouldValidate() -> Bool + /// This method is called after validator's fields have been validated. + func didValidate() + /// This method is called after when validation does not run because preconditions have not been met. + func failedBeforeValidation() } diff --git a/SwiftValidator/Core/Validator.swift b/SwiftValidator/Core/Validator.swift index a600b2d..5398ea9 100644 --- a/SwiftValidator/Core/Validator.swift +++ b/SwiftValidator/Core/Validator.swift @@ -18,6 +18,7 @@ public class Validator { /// Dictionary to hold fields (and accompanying errors) that were unsuccessfully validated. public var errors = [UITextField:ValidationError]() /// Variable that holds success closure to display positive status of field. + public var delegate: ValidationDelegate? private var successStyleTransform:((validationRule:ValidationRule)->Void)? /// Variable that holds error closure to display negative status of field. private var errorStyleTransform:((validationError:ValidationError)->Void)? @@ -62,6 +63,7 @@ public class Validator { - returns: No return value. */ public func validateField(textField: UITextField, callback: (error:ValidationError?) -> Void){ + // perhaps delegate should be set on validator object instead of being passed as a parameter if let fieldRule = validations[textField] { if let error = fieldRule.validateField() { errors[textField] = error @@ -111,10 +113,11 @@ public class Validator { - parameter textfield: field that is to be validated. - parameter errorLabel: A UILabel that holds error label data - parameter rules: A Rule array that holds different rules that apply to said textField. + - paramteter sanitizers: A Sanitizer array that allows for custom cleaning of textField text. - returns: No return value */ - public func registerField(textField:UITextField, errorLabel:UILabel, rules:[Rule]) { - validations[textField] = ValidationRule(textField: textField, rules:rules, errorLabel:errorLabel) + public func registerField(textField:UITextField, errorLabel:UILabel, rules:[Rule], sanitizers: [Sanitizer]? = nil) { + validations[textField] = ValidationRule(textField: textField, rules:rules, errorLabel:errorLabel, sanitizers: sanitizers) } /** @@ -133,18 +136,30 @@ public class Validator { - returns: No return value. */ - public func validate(delegate:ValidationDelegate) { + public func validate() { + // If preconditions are not met, then automatically fail validation + if delegate!.shouldValidate() == false { + delegate!.failedBeforeValidation() + return + } + + // Validation is a go so modify view controller accordingly (disable subviews) + delegate!.willValidate() + // We've made it this far, so preconditions must've been satisfied self.validateAllFields() if errors.isEmpty { - delegate.validationSuccessful() + // call success method if it's implemented + if delegate!.validationSuccessful?() != nil {} } else { - delegate.validationFailed(errors) + // call failure method if it's implemented + if delegate!.validationFailed?(errors) != nil {} } - + // validation did run so update view controller (re-enable buttons and such) + delegate!.didValidate() } - + /** This method validates all fields in validator and sets any errors to errors parameter of callback. diff --git a/SwiftValidator/Rules/Sanitizer.swift b/SwiftValidator/Rules/Sanitizer.swift new file mode 100644 index 0000000..b572260 --- /dev/null +++ b/SwiftValidator/Rules/Sanitizer.swift @@ -0,0 +1,13 @@ +// +// Preparator.swift +// Validator +// +// Created by David Patterson on 2/22/16. +// Copyright © 2016 jpotts18. All rights reserved. +// + +import Foundation + +public protocol Sanitizer { + func sanitize(textField: UITextField) +} \ No newline at end of file diff --git a/SwiftValidator/Rules/Sanitizers.swift b/SwiftValidator/Rules/Sanitizers.swift new file mode 100644 index 0000000..f08f8ad --- /dev/null +++ b/SwiftValidator/Rules/Sanitizers.swift @@ -0,0 +1,16 @@ +// +// Sanitizers.swift +// Validator +// +// Created by David Patterson on 2/22/16. +// Copyright © 2016 jpotts18. All rights reserved. +// + +import Foundation + +public class TrimLeadingAndTrailingSpacesSanitizer: Sanitizer { + public init() {} + public func sanitize(textField: UITextField) { + textField.text = textField.text?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) + } +} \ No newline at end of file diff --git a/SwiftValidator/Rules/ValidationRule.swift b/SwiftValidator/Rules/ValidationRule.swift index 4e5a316..feb8b8b 100644 --- a/SwiftValidator/Rules/ValidationRule.swift +++ b/SwiftValidator/Rules/ValidationRule.swift @@ -11,7 +11,7 @@ import UIKit /** `ValidationRule` is a class that creates an object which holds validation info of a text field. */ -public class ValidationRule { +public class ValidationRule : NSObject { /// the text field of the field public var textField:UITextField /// the errorLabel of the field @@ -19,6 +19,8 @@ public class ValidationRule { /// the rules of the field public var rules:[Rule] = [] + public var sanitizers: [Sanitizer]? + /** Initializes `ValidationRule` instance with text field, rules, and errorLabel. @@ -27,10 +29,11 @@ public class ValidationRule { - parameter rules: array of Rule objects, which text field will be validated against. - returns: An initialized `ValidationRule` object, or nil if an object could not be created for some reason that would not result in an exception. */ - public init(textField: UITextField, rules:[Rule], errorLabel:UILabel?){ + public init(textField: UITextField, rules:[Rule], errorLabel:UILabel?, sanitizers: [Sanitizer]? = nil){ self.textField = textField self.errorLabel = errorLabel self.rules = rules + self.sanitizers = sanitizers } /** @@ -38,6 +41,15 @@ public class ValidationRule { - returns: `ValidationError` object if at least one error is found. Nil is returned if there are no validation errors. */ public func validateField() -> ValidationError? { + // make any preparations if there are any + if let sanitizers = sanitizers { + print("called sanitizer") + for sanitizer in sanitizers { + sanitizer.sanitize(self.textField) + } + } + + // check against prepare rules before validation return rules.filter{ !$0.validate(self.textField.text ?? "") } .map{ rule -> ValidationError in return ValidationError(textField: self.textField, errorLabel:self.errorLabel, error: rule.errorMessage()) }.first } diff --git a/Validator.xcodeproj/project.pbxproj b/Validator.xcodeproj/project.pbxproj index ff3feb4..0e810ac 100644 --- a/Validator.xcodeproj/project.pbxproj +++ b/Validator.xcodeproj/project.pbxproj @@ -12,7 +12,9 @@ 62D1AE221A1E6D4400E4DFF8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 62D1AE201A1E6D4400E4DFF8 /* Main.storyboard */; }; 62D1AE241A1E6D4400E4DFF8 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 62D1AE231A1E6D4400E4DFF8 /* Images.xcassets */; }; 62D1AE271A1E6D4400E4DFF8 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62D1AE251A1E6D4400E4DFF8 /* LaunchScreen.xib */; }; + C19BA8941C7B97CB004743FF /* Sanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19BA8931C7B97CA004743FF /* Sanitizer.swift */; }; C1AB099F1C712025003C7155 /* ValidationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AB099D1C712025003C7155 /* ValidationDelegate.swift */; }; + C1B7C0FD1C7BF6A200BA7174 /* Sanitizers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B7C0FC1C7BF6A200BA7174 /* Sanitizers.swift */; }; FB465CB81B9884F400398388 /* SwiftValidator.h in Headers */ = {isa = PBXBuildFile; fileRef = FB465CB71B9884F400398388 /* SwiftValidator.h */; settings = {ATTRIBUTES = (Public, ); }; }; FB465CBE1B9884F400398388 /* SwiftValidator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB465CB31B9884F400398388 /* SwiftValidator.framework */; }; FB465CC71B9884F400398388 /* SwiftValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB465CC61B9884F400398388 /* SwiftValidatorTests.swift */; }; @@ -92,7 +94,9 @@ 62D1AE261A1E6D4400E4DFF8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 62D1AE2C1A1E6D4500E4DFF8 /* ValidatorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ValidatorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 62D1AE311A1E6D4500E4DFF8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C19BA8931C7B97CA004743FF /* Sanitizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sanitizer.swift; sourceTree = ""; }; C1AB099D1C712025003C7155 /* ValidationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationDelegate.swift; sourceTree = ""; }; + C1B7C0FC1C7BF6A200BA7174 /* Sanitizers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sanitizers.swift; sourceTree = ""; }; FB465CB31B9884F400398388 /* SwiftValidator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftValidator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FB465CB61B9884F400398388 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FB465CB71B9884F400398388 /* SwiftValidator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftValidator.h; sourceTree = ""; }; @@ -262,6 +266,8 @@ FB465CEE1B9889EA00398388 /* ValidationRule.swift */, FB465CEF1B9889EA00398388 /* ZipCodeRule.swift */, 62C1821C1C6312F5003788E7 /* ExactLengthRule.swift */, + C19BA8931C7B97CA004743FF /* Sanitizer.swift */, + C1B7C0FC1C7BF6A200BA7174 /* Sanitizers.swift */, ); path = Rules; sourceTree = ""; @@ -473,6 +479,7 @@ FB465CF91B9889EA00398388 /* PasswordRule.swift in Sources */, FB465CFD1B9889EA00398388 /* Rule.swift in Sources */, FB465CFA1B9889EA00398388 /* PhoneNumberRule.swift in Sources */, + C1B7C0FD1C7BF6A200BA7174 /* Sanitizers.swift in Sources */, FB465CF51B9889EA00398388 /* FloatRule.swift in Sources */, FB465D011B9889EA00398388 /* Validator.swift in Sources */, FB465CFE1B9889EA00398388 /* ValidationRule.swift in Sources */, @@ -481,6 +488,7 @@ FB465CFC1B9889EA00398388 /* RequiredRule.swift in Sources */, FB465CFB1B9889EA00398388 /* RegexRule.swift in Sources */, FB465CF81B9889EA00398388 /* MinLengthRule.swift in Sources */, + C19BA8941C7B97CB004743FF /* Sanitizer.swift in Sources */, C1AB099F1C712025003C7155 /* ValidationDelegate.swift in Sources */, FB465CF71B9889EA00398388 /* MaxLengthRule.swift in Sources */, 62C1821D1C6312F5003788E7 /* ExactLengthRule.swift in Sources */, diff --git a/Validator/Base.lproj/Main.storyboard b/Validator/Base.lproj/Main.storyboard index 8efcb7e..6b4ba52 100644 --- a/Validator/Base.lproj/Main.storyboard +++ b/Validator/Base.lproj/Main.storyboard @@ -19,6 +19,12 @@ + @@ -402,6 +411,7 @@ + diff --git a/Validator/ViewController.swift b/Validator/ViewController.swift index f273e1a..a78a982 100644 --- a/Validator/ViewController.swift +++ b/Validator/ViewController.swift @@ -25,6 +25,7 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate @IBOutlet weak var phoneNumberErrorLabel: UILabel! @IBOutlet weak var zipcodeErrorLabel: UILabel! @IBOutlet weak var emailConfirmErrorLabel: UILabel! + @IBOutlet weak var agreementStatus: UISwitch! let validator = Validator() @@ -34,7 +35,6 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "hideKeyboard")) validator.styleTransformers(success:{ (validationRule) -> Void in - print("here") // clear error label validationRule.errorLabel?.hidden = true validationRule.errorLabel?.text = "" @@ -42,27 +42,32 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate validationRule.textField.layer.borderWidth = 0.5 }, error:{ (validationError) -> Void in - print("error") validationError.errorLabel?.hidden = false validationError.errorLabel?.text = validationError.errorMessage validationError.textField.layer.borderColor = UIColor.redColor().CGColor validationError.textField.layer.borderWidth = 1.0 }) - validator.registerField(fullNameTextField, errorLabel: fullNameErrorLabel , rules: [RequiredRule(), FullNameRule()]) + validator.registerField(fullNameTextField, errorLabel: fullNameErrorLabel , rules: [RequiredRule(), FullNameRule()], sanitizers: [TrimLeadingAndTrailingSpacesSanitizer()]) validator.registerField(emailTextField, errorLabel: emailErrorLabel, rules: [RequiredRule(), EmailRule()]) validator.registerField(emailConfirmTextField, errorLabel: emailConfirmErrorLabel, rules: [RequiredRule(), ConfirmationRule(confirmField: emailTextField)]) validator.registerField(phoneNumberTextField, errorLabel: phoneNumberErrorLabel, rules: [RequiredRule(), MinLengthRule(length: 9)]) validator.registerField(zipcodeTextField, errorLabel: zipcodeErrorLabel, rules: [RequiredRule(), ZipCodeRule()]) + // Set validator delegate + validator.delegate = self } @IBAction func submitTapped(sender: AnyObject) { print("Validating...") - validator.validate(self) + validator.validate() } // MARK: ValidationDelegate Methods + func willValidate() { + print("Prepare validator for validation") + } + func validationSuccessful() { print("Validation Success!") let alert = UIAlertController(title: "Success", message: "You are validated!", preferredStyle: UIAlertControllerStyle.Alert) @@ -71,10 +76,30 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate self.presentViewController(alert, animated: true, completion: nil) } + func validationFailed(errors:[UITextField:ValidationError]) { print("Validation FAILED!") } + func shouldValidate() -> Bool { + // Allow user to check that any preconditions are met before validation + // Good place to validate things other than UITextField + if !agreementStatus.on { + return false + } + return true + } + + func failedBeforeValidation() { + // perform any style transformations + print("validation failed before running") + } + + func didValidate() { + // perform custom post validation + print("validationDidRun called") + } + func hideKeyboard(){ self.view.endEditing(true) } @@ -91,5 +116,4 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate } return true } - } \ No newline at end of file