diff --git a/README.md b/README.md index d86f007..c4525a1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,4 @@ SwiftValidator -=============== - -[![Build Status](https://travis-ci.org/jpotts18/SwiftValidator.svg?branch=travis-ci)](https://travis-ci.org/jpotts18/SwiftValidator) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) -[![Coverage Status](https://coveralls.io/repos/github/davepatterson/SwiftValidator/badge.svg?branch=master)](https://coveralls.io/github/davepatterson/SwiftValidator?branch=travis-edit) - ======= [![Build Status](https://travis-ci.org/jpotts18/SwiftValidator.svg?branch=travis-ci)](https://travis-ci.org/jpotts18/SwiftValidator) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![codecov.io](https://codecov.io/github/jpotts18/SwiftValidator/coverage.svg?branch=master)](https://codecov.io/github/jpotts18/SwiftValidator?branch=master) diff --git a/SwiftValidator.podspec b/SwiftValidator.podspec index 4e31e32..add6793 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.4" 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.4" } 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 79ffe7d..49471dc 100644 --- a/SwiftValidator/Core/ValidationDelegate.swift +++ b/SwiftValidator/Core/ValidationDelegate.swift @@ -13,15 +13,21 @@ import UIKit */ @objc public protocol ValidationDelegate { /** - This method will be called on delegate object when validation is successful. + This delegate method will be called on delegate object when validation is successful. - returns: No return value. */ func validationSuccessful() /** - This method will be called on delegate object when validation fails. + This delegae method will be called on delegate object when validation fails. - returns: No return value. */ func validationFailed(errors: [UITextField:ValidationError]) - - optional func remoteRequest(text: String, urlString: String, completion:(result: Bool) -> Void) + /** + This delegate method is called on fields that require remote validation. + - parameter text: String to is sent to server to be validated. + - parameter urlString: String of url endpoint that will be used to validate text. + - parameter completion: closure that holds the result of the server validation request. Should be set to true if server validation was a success. Should return false if server validation failed. + - returns: No return value. + */ + optional func remoteValidationRequest(text: String, urlString: String, completion:(result: Bool) -> Void) } diff --git a/SwiftValidator/Core/Validator.swift b/SwiftValidator/Core/Validator.swift index 271885e..afc7895 100644 --- a/SwiftValidator/Core/Validator.swift +++ b/SwiftValidator/Core/Validator.swift @@ -11,7 +11,7 @@ import UIKit /** Class that makes `Validator` objects. Should be added as a parameter to ViewController that will display validation fields. - */ +*/ public class Validator { /// Dictionary to hold all fields (and accompanying rules) that will undergo validation. public var validations = [UITextField:ValidationRule]() @@ -54,12 +54,19 @@ public class Validator { } } } - /// Validate all fields with completion handler + + /** + This method is used to validate all fields registered to Validator. If validation is unsuccessful, + field gets added to errors dictionary. Completion closure is used to validator know when all fields + have undergone validation attempt. + - parameter completion: Bool that is set to true when all fields have experienced validation attempt. + - returns: No return value. + */ private func validateAllFields(completion: (finished: Bool) -> Void) { errors = [:] for (textField, rule) in validations { - if rule.remoteURLString != nil { + if rule.remoteInfo != nil { validateRemoteField(textField, completion: { status -> Void in self.completedValidationsCount = self.completedValidationsCount + 1 if self.completedValidationsCount == self.validations.count { @@ -77,40 +84,67 @@ public class Validator { } } } - /// Validate remote field + + /** + This method is used to validate a field that will need to also be validated via remote request. + - parameter textField: TextField of field that is being validated. + - parameter completion: Closure that holds the status of textField's validation. Is set to true + after remote validation has ended, regardless of whether the validation was a success or failure. + - returns: No return value. + */ private func validateRemoteField(textField: UITextField, completion: (status: Bool) -> Void) { if let fieldRule = validations[textField] { - delegate!.remoteRequest!(textField.text!, urlString: fieldRule.remoteURLString!, completion: { result -> Void in - if result { - // Carry on with validation as remote validation passed - self.validateRegularField(fieldRule.textField) - } else { - // Stop validation because remote validation failed - // Validation Failed on remote call - let error = ValidationError(textField: fieldRule.textField, errorLabel: UILabel(), error: "Field already taken") - self.errors[fieldRule.textField] = error - if let transform = self.errorStyleTransform { - transform(validationError: error) + // Carry on with validation as regular validation passed + if self.validateRegularField(fieldRule.textField) { + delegate!.remoteValidationRequest!(textField.text!, urlString: fieldRule.remoteInfo!.urlString, completion: { result -> Void in + if result { + if let transform = self.successStyleTransform { + transform(validationRule: fieldRule) + } + } else { + // Stop validation because remote validation failed + // Validation Failed on remote call + let error = ValidationError(textField: fieldRule.textField, errorLabel: fieldRule.errorLabel, error: fieldRule.remoteInfo!.error) + self.errors[fieldRule.textField] = error + if let transform = self.errorStyleTransform { + transform(validationError: error) + } } - } + // Validation is over, so let validateAllFields(completion: (status: Bool)) know + completion(status: true) + }) + } else { + // Validation is over, so let validateAllFields(completion: (status: Bool)) know completion(status: true) - }) + } + } } - /// Validate regular field (non-remote) - private func validateRegularField(textField: UITextField) { + + /** + Method used to validate a regular field (non-remote). + - parameter: TextField of field that is undergoing validation + - returns: A Bool that represents whether the validation was a success or failure, returns true for the + former and false for the latter. + */ + private func validateRegularField(textField: UITextField) -> Bool { if let fieldRule = validations[textField] { if let error = fieldRule.validateField() { errors[textField] = error if let transform = self.errorStyleTransform { transform(validationError: error) + return false } } else { if let transform = self.successStyleTransform { - transform(validationRule: fieldRule) + if fieldRule.remoteInfo == nil { + transform(validationRule: fieldRule) + } + return true } } } + return false } // MARK: Public functions @@ -160,9 +194,9 @@ public class Validator { - parameter textField: field that is to be validated. - parameter Rule: An array which holds different rules to validate against textField. - returns: No return value - */ - public func registerField(textField:UITextField, rules:[Rule], remoteURLString: String? = nil) { - validations[textField] = ValidationRule(textField: textField, rules: rules, errorLabel: nil, remoteURLString: remoteURLString) + */ + public func registerField(textField:UITextField, rules:[Rule], remoteInfo: (String, String)? = nil) { + validations[textField] = ValidationRule(textField: textField, rules: rules, errorLabel: nil, remoteInfo: remoteInfo) } /** @@ -172,9 +206,9 @@ public class Validator { - parameter errorLabel: A UILabel that holds error label data - parameter rules: A Rule array that holds different rules that apply to said textField. - 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], remoteInfo: (String, String)? = nil) { + validations[textField] = ValidationRule(textField: textField, rules:rules, errorLabel:errorLabel, remoteInfo: remoteInfo) } /** @@ -182,7 +216,7 @@ public class Validator { - parameter textField: field used to locate and remove textField from validator. - returns: No return value - */ + */ public func unregisterField(textField:UITextField) { validations.removeValueForKey(textField) errors.removeValueForKey(textField) @@ -192,7 +226,7 @@ public class Validator { This method checks to see if all fields in validator are valid. - returns: No return value. - */ + */ public func validate(delegate:ValidationDelegate) { self.validateAllFields() @@ -206,12 +240,30 @@ public class Validator { } /** - This method validates all fields in validator and sets any errors to errors parameter of callback. + This method attempts to validate all fields registered to Validator(). + - returns: No return value. + */ + public func validate() { + self.validateAllFields { finished -> Void in + if self.errors.isEmpty { + // call success method if it's implemented + self.delegate!.validationSuccessful() + } else { + // call failure method if it's implemented + self.delegate!.validationFailed(self.errors) + } + // set number of completed validations back to 0 + self.completedValidationsCount = 0 + } + } + + /** + This method validates all fields in validator and sets any errors to errors parameter of closure. - parameter callback: A closure which is called with errors, a dictionary of type UITextField:ValidationError. - returns: No return value. - */ + */ public func validate(callback:(errors:[UITextField:ValidationError])->Void) -> Void { self.validateAllFields() diff --git a/SwiftValidator/Rules/ValidationRule.swift b/SwiftValidator/Rules/ValidationRule.swift index 2d49996..b9b5a24 100644 --- a/SwiftValidator/Rules/ValidationRule.swift +++ b/SwiftValidator/Rules/ValidationRule.swift @@ -18,7 +18,9 @@ public class ValidationRule { public var errorLabel:UILabel? /// the rules of the field public var rules:[Rule] = [] - public var remoteURLString: String? + /// tuple that holds remote validatin info + public var remoteInfo: (urlString: String, error: String)? + //public var remoteURLString: String? /** Initializes `ValidationRule` instance with text field, rules, and errorLabel. @@ -32,7 +34,14 @@ public class ValidationRule { self.textField = textField self.errorLabel = errorLabel self.rules = rules - self.remoteURLString = remoteURLString + //self.remoteURLString = remoteURLString + } + + public init(textField: UITextField, rules:[Rule], errorLabel:UILabel?, remoteInfo: (String, String)? = nil){ + self.textField = textField + self.errorLabel = errorLabel + self.rules = rules + self.remoteInfo = remoteInfo } /** diff --git a/Validator/ViewController.swift b/Validator/ViewController.swift index f273e1a..697ec73 100644 --- a/Validator/ViewController.swift +++ b/Validator/ViewController.swift @@ -50,33 +50,58 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate }) validator.registerField(fullNameTextField, errorLabel: fullNameErrorLabel , rules: [RequiredRule(), FullNameRule()]) - validator.registerField(emailTextField, errorLabel: emailErrorLabel, rules: [RequiredRule(), EmailRule()]) + validator.registerField(emailTextField, errorLabel: emailErrorLabel, rules: [RequiredRule(), EmailRule()], remoteInfo: (urlString: "http://localhost:8000/emails/", error: "Email already in use")) 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()]) + validator.delegate = self } @IBAction func submitTapped(sender: AnyObject) { print("Validating...") - validator.validate(self) + validator.validate() + } + + func simulateRemoteRequest(seconds: Int64, completion: (result: Bool) -> Void) { + print("Simulating \(seconds) second server request...") + // Set number of seconds before "request" is finished + let triggerTime = (Int64(NSEC_PER_SEC) * seconds) + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, triggerTime), dispatch_get_main_queue(), { () -> Void in + // For example purposes, if user does not enter David Patterson as full name, + // the email they tried will be already taken + if self.fullNameTextField.text! == "David Patterson" { + completion(result: true) + } else { + completion(result: false) + } + }) + } + + func hideKeyboard(){ + self.view.endEditing(true) } // MARK: ValidationDelegate Methods func validationSuccessful() { print("Validation Success!") - let alert = UIAlertController(title: "Success", message: "You are validated!", preferredStyle: UIAlertControllerStyle.Alert) + let alert = UIAlertController(title: "Success", message: "You are validated, \(fullNameTextField.text!)!", preferredStyle: UIAlertControllerStyle.Alert) let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil) alert.addAction(defaultAction) self.presentViewController(alert, animated: true, completion: nil) } + func validationFailed(errors:[UITextField:ValidationError]) { print("Validation FAILED!") } - func hideKeyboard(){ - self.view.endEditing(true) + func remoteValidationRequest(text: String, urlString: String, completion: (result: Bool) -> Void) { + simulateRemoteRequest(2) { result -> Void in + // Set result to true if field was validated server-side + // Set to false if field was not validated server-side + completion(result: result) + } } // MARK: Validate single field