diff --git a/MyPlayground.playground/contents.xcplayground b/MyPlayground.playground/contents.xcplayground new file mode 100644 index 0000000..8e39341 --- /dev/null +++ b/MyPlayground.playground/contents.xcplayground @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/MyPlayground.playground/section-1.swift b/MyPlayground.playground/section-1.swift new file mode 100644 index 0000000..b31d259 --- /dev/null +++ b/MyPlayground.playground/section-1.swift @@ -0,0 +1,10 @@ +// Playground - noun: a place where people can play + +import UIKit + +var str = "Hello, playground" + +var errors[String:Int] = ["This":1, "Is": 2, "A": 3, "Test": 4] + +println(errors.values) + diff --git a/MyPlayground.playground/timeline.xctimeline b/MyPlayground.playground/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/MyPlayground.playground/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + + diff --git a/README.md b/README.md index ebc6753..525a59b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,202 @@ -swift-validator +Swift-validator =============== -A rule-based validation library for Swift +Swift Validator is a rule-based validation library for Swift. + +Core Concepts + +* ```UITextField``` and ```ValidationRule``` go into the ```Validator```, ```UITextFields``` and ```ValidationErrors``` come out of ```Validator``` +* ```UITextField``` is registered to ```Validator``` +* ```Validator``` evaluates ```ValidationRules``` sequentially and stops evaluating when a ```ValidationRule``` fails. + +## Quick Start + +Initialize the ```Validator``` by setting a delegate to a View Controller or other object. + +```swift + +// ViewController.swift + +override func viewDidLoad() { + super.viewDidLoad() + + var validator = Validator(delegate: self) +} + +``` + +Register the fields that you want to validate + +```swift + +var fields:[String] = ["FullName", "Email", "Phone"] + +// Validation Rules are evaluated from left to right. The first rule is ValidationRuleType.Required the second is ValidationRuleType.FullName. +validator.registerField(fields[0], textField:nameTextField, rules: [.Required, .FullName]) +validator.registerField(fields[1], textField:emailTextField, rules: [.Required, .Email]) +validator.registerField(fields[2], textField:phoneTextField, rules: [.Required, .PhoneNumber]) + +``` + +Validate Individual Field + +```swift + +func validator.validateFieldBy(fields[0], delegate:self) + +// ValidationFieldDelegate methods +func validationFieldSuccess(key:String, validField:UITextField){ + validField.backgroundColor = UIColor.greenColor() +} + +func validationFieldFailure(key:String, error:ValidationError){ + println(error.error.description) +} + +``` + +Validate All Fields + +```swift + +validator.validateAllBy(fields, delegate:self) + +// ValidationDelegate methods + +func validationWasSuccessful(){ + // submit the form +} + +func validationFailed(key:String, errors[String:ValidationError]){ + // turn the fields to red + for error in errors.values { + error.textField.backgroundColor = UIColor.redColor() + println("error -> \(error.error.description)") + } +} + +``` + +## Custom Validation + +We will create a ```SSNValidation``` class to show how to create your own Validation. A United States Social Security Number (or SSN) is a field that consists of XXX-XX-XXXX. + +Create a class that implements the Validation protocol + +```swift + +class SSNValidation: Validation { + let SSN_REGEX = "^\\d{3}-\\d{2}-\\d{4}$" + + func validate(value: String) -> (Bool, ValidationErrorType) { + if let ssnTest = NSPredicate(format: "SELF MATCHES %@", SSN_REGEX) { + if ssnTest.evaluateWithObject(value) { + return (true, .NoError) + } + return (false, .SocialSecurity) // We will create this later ValidationErrorTYpe + } + return (false, .SocialSecurity) + } + +} + +``` + +Add the ```.SocialSecurity``` ValidationRuleType + +```swift + +enum ValidationRuleType { + case Required, + Email, + Password, + MinLength, + MaxLength, + ZipCode, + PhoneNumber, + FullName, + SocialSecurity // Added to the Rule Types +} + +``` + +Add the ```.SocialSecurity``` ValidationErrorType and description() + +```swift + +enum ValidationErrorType { + case Required, + Email, + Password, + MinLength, + MaxLength, + ZipCode, + PhoneNumber, + FullName, + SocialSecurity, // Added to the Error Types + NoError + + func description() -> String { + switch self { + case .Required: + return "Required field" + case .Email: + return "Must be a valid email" + case .MaxLength: + return "This field should be less than" + case .ZipCode: + return "5 digit zipcode" + case .PhoneNumber: + return "10 digit phone number" + case .Password: + return "Must be at least 8 characters" + case .FullName: + return "Provide a first & last name" + // Adding the desired error message + case .SocialSecurity: + return "SSN is XXX-XX-XXXX" + default: + return "" + } + } + +} + +``` +Register the Validation with the ValidationFactory + +```swift + +class ValidationFactory { + class func validationForRule(rule:ValidationRuleType) -> Validation { + switch rule { + case .Required: + return RequiredValidation() + case .Email: + return EmailValidation() + case .MinLength: + return MinLengthValidation() + case .MaxLength: + return MaxLengthValidation() + case .PhoneNumber: + return PhoneNumberValidation() + case .ZipCode: + return ZipCodeValidation() + case .FullName: + return FullNameValidation() + // Add Validation to allow Factory to create one on the fly for you + case .SocialSecurity: + return SSNValidation() + default: + return RequiredValidation() + } + } +} + +``` + + + + + + diff --git a/Validator.xcodeproj/project.pbxproj b/Validator.xcodeproj/project.pbxproj index 2625ab2..45990de 100644 --- a/Validator.xcodeproj/project.pbxproj +++ b/Validator.xcodeproj/project.pbxproj @@ -13,6 +13,21 @@ 62D1AE241A1E6D4400E4DFF8 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 62D1AE231A1E6D4400E4DFF8 /* Images.xcassets */; }; 62D1AE271A1E6D4400E4DFF8 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62D1AE251A1E6D4400E4DFF8 /* LaunchScreen.xib */; }; 62D1AE331A1E6D4500E4DFF8 /* ValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE321A1E6D4500E4DFF8 /* ValidatorTests.swift */; }; + 62D1AE3E1A1E6FEF00E4DFF8 /* FullNameValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE3D1A1E6FEF00E4DFF8 /* FullNameValidation.swift */; }; + 62D1AE491A1E6FF800E4DFF8 /* EmailValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE3F1A1E6FF800E4DFF8 /* EmailValidation.swift */; }; + 62D1AE4A1A1E6FF800E4DFF8 /* MaxLengthValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE401A1E6FF800E4DFF8 /* MaxLengthValidation.swift */; }; + 62D1AE4B1A1E6FF800E4DFF8 /* MinLengthValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE411A1E6FF800E4DFF8 /* MinLengthValidation.swift */; }; + 62D1AE4C1A1E6FF800E4DFF8 /* PhoneNumberValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE421A1E6FF800E4DFF8 /* PhoneNumberValidation.swift */; }; + 62D1AE4D1A1E6FF800E4DFF8 /* RequiredValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE431A1E6FF800E4DFF8 /* RequiredValidation.swift */; }; + 62D1AE4E1A1E6FF800E4DFF8 /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE441A1E6FF800E4DFF8 /* Validation.swift */; }; + 62D1AE4F1A1E6FF800E4DFF8 /* ValidationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE451A1E6FF800E4DFF8 /* ValidationError.swift */; }; + 62D1AE501A1E6FF800E4DFF8 /* ValidationErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE461A1E6FF800E4DFF8 /* ValidationErrorType.swift */; }; + 62D1AE511A1E6FF800E4DFF8 /* ValidationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE471A1E6FF800E4DFF8 /* ValidationFactory.swift */; }; + 62D1AE521A1E6FF800E4DFF8 /* ValidationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE481A1E6FF800E4DFF8 /* ValidationRule.swift */; }; + 62D1AE571A1E700200E4DFF8 /* ValidationRuleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE531A1E700200E4DFF8 /* ValidationRuleType.swift */; }; + 62D1AE581A1E700200E4DFF8 /* Validator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE541A1E700200E4DFF8 /* Validator.swift */; }; + 62D1AE591A1E700200E4DFF8 /* ZipCodeValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE551A1E700200E4DFF8 /* ZipCodeValidation.swift */; }; + 62D1AE5A1A1E700200E4DFF8 /* PasswordValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62D1AE561A1E700200E4DFF8 /* PasswordValidation.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -36,6 +51,22 @@ 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 = ""; }; 62D1AE321A1E6D4500E4DFF8 /* ValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatorTests.swift; sourceTree = ""; }; + 62D1AE3D1A1E6FEF00E4DFF8 /* FullNameValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullNameValidation.swift; sourceTree = ""; }; + 62D1AE3F1A1E6FF800E4DFF8 /* EmailValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailValidation.swift; sourceTree = ""; }; + 62D1AE401A1E6FF800E4DFF8 /* MaxLengthValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaxLengthValidation.swift; sourceTree = ""; }; + 62D1AE411A1E6FF800E4DFF8 /* MinLengthValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinLengthValidation.swift; sourceTree = ""; }; + 62D1AE421A1E6FF800E4DFF8 /* PhoneNumberValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidation.swift; sourceTree = ""; }; + 62D1AE431A1E6FF800E4DFF8 /* RequiredValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequiredValidation.swift; sourceTree = ""; }; + 62D1AE441A1E6FF800E4DFF8 /* Validation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Validation.swift; sourceTree = ""; }; + 62D1AE451A1E6FF800E4DFF8 /* ValidationError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationError.swift; sourceTree = ""; }; + 62D1AE461A1E6FF800E4DFF8 /* ValidationErrorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationErrorType.swift; sourceTree = ""; }; + 62D1AE471A1E6FF800E4DFF8 /* ValidationFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationFactory.swift; sourceTree = ""; }; + 62D1AE481A1E6FF800E4DFF8 /* ValidationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationRule.swift; sourceTree = ""; }; + 62D1AE531A1E700200E4DFF8 /* ValidationRuleType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationRuleType.swift; sourceTree = ""; }; + 62D1AE541A1E700200E4DFF8 /* Validator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Validator.swift; sourceTree = ""; }; + 62D1AE551A1E700200E4DFF8 /* ZipCodeValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZipCodeValidation.swift; sourceTree = ""; }; + 62D1AE561A1E700200E4DFF8 /* PasswordValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordValidation.swift; sourceTree = ""; }; + 62D1AE5C1A1E78EE00E4DFF8 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -59,6 +90,7 @@ 62D1AE0E1A1E6D4400E4DFF8 = { isa = PBXGroup; children = ( + 62D1AE5C1A1E78EE00E4DFF8 /* MyPlayground.playground */, 62D1AE191A1E6D4400E4DFF8 /* Validator */, 62D1AE2F1A1E6D4500E4DFF8 /* ValidatorTests */, 62D1AE181A1E6D4400E4DFF8 /* Products */, @@ -77,6 +109,7 @@ 62D1AE191A1E6D4400E4DFF8 /* Validator */ = { isa = PBXGroup; children = ( + 62D1AE3C1A1E6FAF00E4DFF8 /* lib */, 62D1AE1C1A1E6D4400E4DFF8 /* AppDelegate.swift */, 62D1AE1E1A1E6D4400E4DFF8 /* ViewController.swift */, 62D1AE201A1E6D4400E4DFF8 /* Main.storyboard */, @@ -112,6 +145,36 @@ name = "Supporting Files"; sourceTree = ""; }; + 62D1AE3C1A1E6FAF00E4DFF8 /* lib */ = { + isa = PBXGroup; + children = ( + 62D1AE5B1A1E701B00E4DFF8 /* Validations */, + 62D1AE531A1E700200E4DFF8 /* ValidationRuleType.swift */, + 62D1AE541A1E700200E4DFF8 /* Validator.swift */, + 62D1AE451A1E6FF800E4DFF8 /* ValidationError.swift */, + 62D1AE461A1E6FF800E4DFF8 /* ValidationErrorType.swift */, + 62D1AE471A1E6FF800E4DFF8 /* ValidationFactory.swift */, + 62D1AE481A1E6FF800E4DFF8 /* ValidationRule.swift */, + ); + name = lib; + sourceTree = ""; + }; + 62D1AE5B1A1E701B00E4DFF8 /* Validations */ = { + isa = PBXGroup; + children = ( + 62D1AE441A1E6FF800E4DFF8 /* Validation.swift */, + 62D1AE3D1A1E6FEF00E4DFF8 /* FullNameValidation.swift */, + 62D1AE421A1E6FF800E4DFF8 /* PhoneNumberValidation.swift */, + 62D1AE431A1E6FF800E4DFF8 /* RequiredValidation.swift */, + 62D1AE3F1A1E6FF800E4DFF8 /* EmailValidation.swift */, + 62D1AE411A1E6FF800E4DFF8 /* MinLengthValidation.swift */, + 62D1AE401A1E6FF800E4DFF8 /* MaxLengthValidation.swift */, + 62D1AE561A1E700200E4DFF8 /* PasswordValidation.swift */, + 62D1AE551A1E700200E4DFF8 /* ZipCodeValidation.swift */, + ); + name = Validations; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -212,8 +275,23 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 62D1AE4C1A1E6FF800E4DFF8 /* PhoneNumberValidation.swift in Sources */, + 62D1AE5A1A1E700200E4DFF8 /* PasswordValidation.swift in Sources */, + 62D1AE4F1A1E6FF800E4DFF8 /* ValidationError.swift in Sources */, + 62D1AE3E1A1E6FEF00E4DFF8 /* FullNameValidation.swift in Sources */, + 62D1AE4B1A1E6FF800E4DFF8 /* MinLengthValidation.swift in Sources */, 62D1AE1F1A1E6D4400E4DFF8 /* ViewController.swift in Sources */, + 62D1AE4E1A1E6FF800E4DFF8 /* Validation.swift in Sources */, 62D1AE1D1A1E6D4400E4DFF8 /* AppDelegate.swift in Sources */, + 62D1AE581A1E700200E4DFF8 /* Validator.swift in Sources */, + 62D1AE501A1E6FF800E4DFF8 /* ValidationErrorType.swift in Sources */, + 62D1AE491A1E6FF800E4DFF8 /* EmailValidation.swift in Sources */, + 62D1AE511A1E6FF800E4DFF8 /* ValidationFactory.swift in Sources */, + 62D1AE591A1E700200E4DFF8 /* ZipCodeValidation.swift in Sources */, + 62D1AE571A1E700200E4DFF8 /* ValidationRuleType.swift in Sources */, + 62D1AE521A1E6FF800E4DFF8 /* ValidationRule.swift in Sources */, + 62D1AE4A1A1E6FF800E4DFF8 /* MaxLengthValidation.swift in Sources */, + 62D1AE4D1A1E6FF800E4DFF8 /* RequiredValidation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Validator.xcodeproj/project.xcworkspace/xcshareddata/Validator.xccheckout b/Validator.xcodeproj/project.xcworkspace/xcshareddata/Validator.xccheckout new file mode 100644 index 0000000..ed01e81 --- /dev/null +++ b/Validator.xcodeproj/project.xcworkspace/xcshareddata/Validator.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + 41CB4003-1551-4C4C-B53F-78A56D1DFBE5 + IDESourceControlProjectName + Validator + IDESourceControlProjectOriginsDictionary + + 44BA2A1DF8B8E3EE0CCBE8F37625F38B9979A032 + github.com:jpotts18/swift-validator.git + + IDESourceControlProjectPath + Validator.xcodeproj + IDESourceControlProjectRelativeInstallPathDictionary + + 44BA2A1DF8B8E3EE0CCBE8F37625F38B9979A032 + ../.. + + IDESourceControlProjectURL + github.com:jpotts18/swift-validator.git + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 44BA2A1DF8B8E3EE0CCBE8F37625F38B9979A032 + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 44BA2A1DF8B8E3EE0CCBE8F37625F38B9979A032 + IDESourceControlWCCName + Validator + + + + diff --git a/Validator.xcodeproj/project.xcworkspace/xcuserdata/jpotts18.xcuserdatad/UserInterfaceState.xcuserstate b/Validator.xcodeproj/project.xcworkspace/xcuserdata/jpotts18.xcuserdatad/UserInterfaceState.xcuserstate index 633be68..5b4eb6e 100644 Binary files a/Validator.xcodeproj/project.xcworkspace/xcuserdata/jpotts18.xcuserdatad/UserInterfaceState.xcuserstate and b/Validator.xcodeproj/project.xcworkspace/xcuserdata/jpotts18.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Validator/EmailValidation.swift b/Validator/EmailValidation.swift new file mode 100644 index 0000000..4711f4d --- /dev/null +++ b/Validator/EmailValidation.swift @@ -0,0 +1,26 @@ +// +// EmailValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class EmailValidation: Validation { + + let EMAIL_REGEX = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}" + + func validate(value:String) -> (Bool, ValidationErrorType) { + + if let emailTest = NSPredicate(format: "SELF MATCHES %@", EMAIL_REGEX) { + if emailTest.evaluateWithObject(value) { + return (true, .NoError) + } else { + return (false,.Email) + } + } + return (false, .Email) + } +} \ No newline at end of file diff --git a/Validator/FullNameValidation.swift b/Validator/FullNameValidation.swift new file mode 100644 index 0000000..9e78993 --- /dev/null +++ b/Validator/FullNameValidation.swift @@ -0,0 +1,22 @@ +// +// FullNameValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/19/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + + +class FullNameValidation : Validation { + + func validate(value:String) -> (Bool, ValidationErrorType) { + + var nameArray:[String] = split(value) { $0 == " " } + if nameArray.count == 2 { + return (true, .NoError) + } + return (false, .FullName) + } +} \ No newline at end of file diff --git a/Validator/MaxLengthValidation.swift b/Validator/MaxLengthValidation.swift new file mode 100644 index 0000000..bf9139f --- /dev/null +++ b/Validator/MaxLengthValidation.swift @@ -0,0 +1,20 @@ +// +// MaxLengthValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class MaxLengthValidation: Validation { + let DEFAULT_MAX_LENGTH = 25 + + func validate(value: String) -> (Bool, ValidationErrorType) { + if countElements(value) > DEFAULT_MAX_LENGTH { + return (false, .MaxLength) + } + return (true, .NoError) + } +} \ No newline at end of file diff --git a/Validator/MinLengthValidation.swift b/Validator/MinLengthValidation.swift new file mode 100644 index 0000000..ce4078e --- /dev/null +++ b/Validator/MinLengthValidation.swift @@ -0,0 +1,20 @@ +// +// MinLengthValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class MinLengthValidation: Validation { + let DEFAULT_MIN_LENGTH = 3 + + func validate(value: String) -> (Bool, ValidationErrorType) { + if countElements(value) < DEFAULT_MIN_LENGTH { + return (false, .MinLength) + } + return (true, .NoError) + } +} \ No newline at end of file diff --git a/Validator/PasswordValidation.swift b/Validator/PasswordValidation.swift new file mode 100644 index 0000000..1583dcc --- /dev/null +++ b/Validator/PasswordValidation.swift @@ -0,0 +1,25 @@ +// +// PasswordValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/13/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class PasswordValidation : Validation { + + // 8 characters. one uppercase + var PASSWORD_REGEX = "^(?=.*?[A-Z]).{8,}$" + + func validate(value: String) -> (Bool, ValidationErrorType) { + if let passwordTes = NSPredicate(format: "SELF MATCHES %@", PASSWORD_REGEX) { + if passwordTes.evaluateWithObject(value) { + return (true, .NoError) + } + return (false, .Password) + } + return (false, .Password) + } +} \ No newline at end of file diff --git a/Validator/PhoneNumberValidation.swift b/Validator/PhoneNumberValidation.swift new file mode 100644 index 0000000..7f56ca8 --- /dev/null +++ b/Validator/PhoneNumberValidation.swift @@ -0,0 +1,24 @@ +// +// PhoneValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class PhoneNumberValidation: Validation { + let PHONE_REGEX = "^\\d{3}-\\d{3}-\\d{4}$" + + func validate(value: String) -> (Bool, ValidationErrorType) { + if let phoneTest = NSPredicate(format: "SELF MATCHES %@", PHONE_REGEX) { + if phoneTest.evaluateWithObject(value) { + return (true, .NoError) + } + return (false, .PhoneNumber) + } + return (false, .PhoneNumber) + } + +} diff --git a/Validator/RequiredValidation.swift b/Validator/RequiredValidation.swift new file mode 100644 index 0000000..0570d69 --- /dev/null +++ b/Validator/RequiredValidation.swift @@ -0,0 +1,19 @@ +// +// RequiredValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class RequiredValidation: Validation { + + func validate(value:String) -> (Bool, ValidationErrorType) { + if value.isEmpty { + return (false, .Required) + } + return (true, .NoError) + } +} \ No newline at end of file diff --git a/Validator/Validation.swift b/Validator/Validation.swift new file mode 100644 index 0000000..c7b5c45 --- /dev/null +++ b/Validator/Validation.swift @@ -0,0 +1,13 @@ +// +// Validation.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +protocol Validation { + func validate(value:String) -> (Bool, ValidationErrorType) +} \ No newline at end of file diff --git a/Validator/ValidationError.swift b/Validator/ValidationError.swift new file mode 100644 index 0000000..1b19384 --- /dev/null +++ b/Validator/ValidationError.swift @@ -0,0 +1,20 @@ +// +// File.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class ValidationError { + let textField:UITextField + let error:ValidationErrorType + + init(textField:UITextField, error:ValidationErrorType){ + self.textField = textField + self.error = error + } + +} \ No newline at end of file diff --git a/Validator/ValidationErrorType.swift b/Validator/ValidationErrorType.swift new file mode 100644 index 0000000..f612776 --- /dev/null +++ b/Validator/ValidationErrorType.swift @@ -0,0 +1,43 @@ +// +// ValidationErrorType.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +enum ValidationErrorType { + case Required, + Email, + Password, + MinLength, + MaxLength, + ZipCode, + PhoneNumber, + FullName, + NoError + + func description() -> String { + switch self { + case .Required: + return "Required field" + case .Email: + return "Must be a valid email" + case .MaxLength: + return "This field should be less than" + case .ZipCode: + return "5 digit zipcode" + case .PhoneNumber: + return "10 digit phone number" + case .Password: + return "Must be at least 8 characters" + case .FullName: + return "Provide a first & last name" + default: + return "" + } + } + +} \ No newline at end of file diff --git a/Validator/ValidationFactory.swift b/Validator/ValidationFactory.swift new file mode 100644 index 0000000..fa73f4e --- /dev/null +++ b/Validator/ValidationFactory.swift @@ -0,0 +1,32 @@ +// +// Validations.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class ValidationFactory { + class func validationForRule(rule:ValidationRuleType) -> Validation { + switch rule { + case .Required: + return RequiredValidation() + case .Email: + return EmailValidation() + case .MinLength: + return MinLengthValidation() + case .MaxLength: + return MaxLengthValidation() + case .PhoneNumber: + return PhoneNumberValidation() + case .ZipCode: + return ZipCodeValidation() + case .FullName: + return FullNameValidation() + default: + return RequiredValidation() + } + } +} diff --git a/Validator/ValidationRule.swift b/Validator/ValidationRule.swift new file mode 100644 index 0000000..bf108cd --- /dev/null +++ b/Validator/ValidationRule.swift @@ -0,0 +1,31 @@ +// +// ValidationRule.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class ValidationRule { + let textField:UITextField + var rules:[ValidationRuleType] = [] + + init(textField:UITextField, rules:[ValidationRuleType]){ + self.textField = textField + self.rules = rules + } + + func validateField() -> ValidationError? { + for rule in rules { + var validation = ValidationFactory.validationForRule(rule) + var attempt:(isValid:Bool, error:ValidationErrorType) = validation.validate(textField.text) + if !attempt.isValid { + return ValidationError(textField: textField, error: attempt.error) + } + } + return nil + } + +} \ No newline at end of file diff --git a/Validator/ValidationRuleType.swift b/Validator/ValidationRuleType.swift new file mode 100644 index 0000000..d9dcd62 --- /dev/null +++ b/Validator/ValidationRuleType.swift @@ -0,0 +1,20 @@ +// +// ValidationRuleType.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +enum ValidationRuleType { + case Required, + Email, + Password, + MinLength, + MaxLength, + ZipCode, + PhoneNumber, + FullName +} \ No newline at end of file diff --git a/Validator/Validator.swift b/Validator/Validator.swift new file mode 100644 index 0000000..78f4ae3 --- /dev/null +++ b/Validator/Validator.swift @@ -0,0 +1,64 @@ +// +// Validator.swift +// Pingo +// +// Created by Jeff Potter on 11/10/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +protocol ValidationDelegate { + func validationWasSuccessful() + func validationFailed(errors:[String:ValidationError]) +} + +protocol ValidationFieldDelegate { + func validationFieldFailed(key:String, error:ValidationError) + func validationFieldSuccess(key:String, validField:UITextField) +} + +class Validator { + var validationRules:[String:ValidationRule] = [:] + var validationErrors:[String:ValidationError] = [:] + let delegate:ValidationDelegate + + init(delegate:ValidationDelegate){ + self.delegate = delegate + } + + func registerField(key:String, textField:UITextField, rules:[ValidationRuleType]) { + validationRules[key] = ValidationRule(textField: textField, rules: rules) + } + + func validateFieldByKey(key:String, delegate:ValidationFieldDelegate) { + if let currentRule:ValidationRule = validationRules[key] { + if var error:ValidationError = currentRule.validateField() { + delegate.validationFieldFailed(key, error:error) + } else { + delegate.validationFieldSuccess(key, validField:currentRule.textField) + } + } + } + + func validateAllBy(keys:[String], delegate:ValidationDelegate){ + + for key in keys { + if let currentRule:ValidationRule = validationRules[key] { + if var error:ValidationError = currentRule.validateField() { + validationErrors[key] = error + } else { + validationErrors.removeValueForKey(key) + } + } + } + + if validationErrors.isEmpty { + delegate.validationWasSuccessful() + } else { + delegate.validationFailed(validationErrors) + } + + } + +} \ No newline at end of file diff --git a/Validator/ViewController.swift b/Validator/ViewController.swift index 8e2d68d..a757aa0 100644 --- a/Validator/ViewController.swift +++ b/Validator/ViewController.swift @@ -13,11 +13,15 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. + + var validator = Validator(delegate: self) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. + + } diff --git a/Validator/ZipCodeValidation.swift b/Validator/ZipCodeValidation.swift new file mode 100644 index 0000000..af62b54 --- /dev/null +++ b/Validator/ZipCodeValidation.swift @@ -0,0 +1,24 @@ +// +// ZipCodeValidation.swift +// Pingo +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2014 Byron Mackay. All rights reserved. +// + +import Foundation + +class ZipCodeValidation: Validation { + let ZIP_REGEX = "\\d{5}" + + func validate(value: String) -> (Bool, ValidationErrorType) { + if let zipTest = NSPredicate(format: "SELF MATCHES %@", ZIP_REGEX) { + if zipTest.evaluateWithObject(value) { + return (true, .NoError) + } + return (false, .ZipCode) + } + return (false, .ZipCode) + } + +} \ No newline at end of file