Skip to content

Commit d51e904

Browse files
committed
Merge pull request #28 from cameronmccord2/master
FloatRule, style transform callback on success/error
2 parents cc5e16a + a685e16 commit d51e904

File tree

7 files changed

+172
-59
lines changed

7 files changed

+172
-59
lines changed

Validator.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
62DC8D6E1AAA42CE0095DFA7 /* PasswordRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DC8D6B1AAA42CE0095DFA7 /* PasswordRule.swift */; };
2727
62DC8D711AAA43110095DFA7 /* ZipCodeRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DC8D701AAA43110095DFA7 /* ZipCodeRule.swift */; };
2828
62E9E2AD1ACFB336000A939C /* RegexRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9E2AC1ACFB336000A939C /* RegexRule.swift */; };
29+
DC5A35EC1AF99BA60013FE6B /* FloatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5A35EB1AF99BA60013FE6B /* FloatRule.swift */; };
2930
/* End PBXBuildFile section */
3031

3132
/* Begin PBXContainerItemProxy section */
@@ -62,6 +63,7 @@
6263
62DC8D6B1AAA42CE0095DFA7 /* PasswordRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordRule.swift; sourceTree = "<group>"; };
6364
62DC8D701AAA43110095DFA7 /* ZipCodeRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZipCodeRule.swift; sourceTree = "<group>"; };
6465
62E9E2AC1ACFB336000A939C /* RegexRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegexRule.swift; sourceTree = "<group>"; };
66+
DC5A35EB1AF99BA60013FE6B /* FloatRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatRule.swift; sourceTree = "<group>"; };
6567
/* End PBXFileReference section */
6668

6769
/* Begin PBXFrameworksBuildPhase section */
@@ -171,6 +173,7 @@
171173
62DC8D701AAA43110095DFA7 /* ZipCodeRule.swift */,
172174
628637271AAA49E300BC8FCF /* ConfirmRule.swift */,
173175
62E9E2AC1ACFB336000A939C /* RegexRule.swift */,
176+
DC5A35EB1AF99BA60013FE6B /* FloatRule.swift */,
174177
);
175178
name = Rules;
176179
sourceTree = "<group>";
@@ -283,6 +286,7 @@
283286
628637281AAA49E300BC8FCF /* ConfirmRule.swift in Sources */,
284287
62DC8D651AAA42520095DFA7 /* Rule.swift in Sources */,
285288
62D1AE1F1A1E6D4400E4DFF8 /* ViewController.swift in Sources */,
289+
DC5A35EC1AF99BA60013FE6B /* FloatRule.swift in Sources */,
286290
62DC8D6D1AAA42CE0095DFA7 /* RequiredRule.swift in Sources */,
287291
62D1AE1D1A1E6D4400E4DFF8 /* AppDelegate.swift in Sources */,
288292
62D1AE581A1E700200E4DFF8 /* Validator.swift in Sources */,

Validator/FloatRule.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// FloatRule.swift
3+
// Validator
4+
//
5+
// Created by Cameron McCord on 5/5/15.
6+
// Copyright (c) 2015 jpotts18. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public class FloatRule:Rule {
12+
13+
public init(){
14+
15+
}
16+
17+
public func validate(value: String) -> Bool {
18+
let regex = NSRegularExpression(pattern: "[-+]?(\\d*[.])?\\d+", options: nil, error: nil)
19+
if let regex = regex {
20+
let match = regex.numberOfMatchesInString(value, options: nil, range: NSRange(location: 0, length: count(value)))
21+
return match == 1
22+
}
23+
return false
24+
}
25+
26+
public func errorMessage() -> String {
27+
return "This must be a number with or without a decimal"
28+
}
29+
}

Validator/ValidationError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ public class ValidationError {
1818
self.textField = textField
1919
self.errorMessage = error
2020
}
21+
22+
public init(textField:UITextField, errorLabel:UILabel?, error:String){
23+
self.textField = textField
24+
self.errorLabel = errorLabel
25+
self.errorMessage = error
26+
}
2127
}

Validator/ValidationRule.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class ValidationRule {
2323
public func validateField() -> ValidationError? {
2424
for rule in rules {
2525
if !rule.validate(textField.text) {
26-
return ValidationError(textField: self.textField, error: rule.errorMessage())
26+
return ValidationError(textField: self.textField, errorLabel:self.errorLabel, error: rule.errorMessage())
2727
}
2828
}
2929
return nil

Validator/Validator.swift

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,48 @@ public class Validator {
1818
// dictionary to handle complex view hierarchies like dynamic tableview cells
1919
public var errors:[UITextField:ValidationError] = [:]
2020
public var validations:[UITextField:ValidationRule] = [:]
21+
private var successStyleTransform:((validationRule:ValidationRule)->Void)?
22+
private var errorStyleTransform:((validationError:ValidationError)->Void)?
2123

2224
public init(){}
2325

26+
// MARK: Private functions
27+
28+
private func clearErrors() {
29+
self.errors = [:]
30+
}
31+
32+
private func validateAllFields() {
33+
34+
self.clearErrors()
35+
36+
for field in validations.keys {
37+
if let currentRule: ValidationRule = validations[field] {
38+
if var error: ValidationError = currentRule.validateField() {
39+
errors[field] = error
40+
41+
// let the user transform the field if they want
42+
if let transform = self.errorStyleTransform {
43+
transform(validationError: error)
44+
}
45+
} else {
46+
// No error
47+
// let the user transform the field if they want
48+
if let transform = self.successStyleTransform {
49+
transform(validationRule: currentRule)
50+
}
51+
}
52+
}
53+
}
54+
}
55+
2456
// MARK: Using Keys
2557

58+
public func styleTransformers(#success:((validationRule:ValidationRule)->Void)?, #error:((validationError:ValidationError)->Void)?) {
59+
self.successStyleTransform = success
60+
self.errorStyleTransform = error
61+
}
62+
2663
public func registerField(textField:UITextField, rules:[Rule]) {
2764
validations[textField] = ValidationRule(textField: textField, rules: rules, errorLabel: nil)
2865
}
@@ -38,18 +75,7 @@ public class Validator {
3875

3976
public func validate(delegate:ValidationDelegate) {
4077

41-
for field in validations.keys {
42-
if let currentRule: ValidationRule = validations[field] {
43-
if var error: ValidationError = currentRule.validateField() {
44-
if currentRule.errorLabel != nil {
45-
error.errorLabel = currentRule.errorLabel
46-
}
47-
errors[field] = error
48-
} else {
49-
errors.removeValueForKey(field)
50-
}
51-
}
52-
}
78+
self.validateAllFields()
5379

5480
if errors.isEmpty {
5581
delegate.validationSuccessful()
@@ -60,20 +86,8 @@ public class Validator {
6086

6187
public func validate(callback:(errors:[UITextField:ValidationError])->Void) -> Void {
6288

63-
for field in validations.keys {
64-
if let currentRule:ValidationRule = validations[field] {
65-
if var error:ValidationError = currentRule.validateField() {
66-
errors[field] = error
67-
} else {
68-
errors.removeValueForKey(field)
69-
}
70-
}
71-
}
89+
self.validateAllFields()
7290

7391
callback(errors: errors)
7492
}
75-
76-
func clearErrors() {
77-
self.errors = [:]
78-
}
7993
}

Validator/ViewController.swift

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,31 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate
3333

3434
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "hideKeyboard"))
3535

36+
validator.styleTransformers(success:{ (validationRule) -> Void in
37+
println("here")
38+
// clear error label
39+
validationRule.errorLabel?.hidden = true
40+
validationRule.errorLabel?.text = ""
41+
validationRule.textField.layer.borderColor = UIColor.greenColor().CGColor
42+
validationRule.textField.layer.borderWidth = 0.5
43+
44+
}, error:{ (validationError) -> Void in
45+
println("error")
46+
validationError.errorLabel?.hidden = false
47+
validationError.errorLabel?.text = validationError.errorMessage
48+
validationError.textField.layer.borderColor = UIColor.redColor().CGColor
49+
validationError.textField.layer.borderWidth = 1.0
50+
})
51+
3652
validator.registerField(fullNameTextField, errorLabel: fullNameErrorLabel , rules: [RequiredRule(), FullNameRule()])
3753
validator.registerField(emailTextField, errorLabel: emailErrorLabel, rules: [RequiredRule(), EmailRule()])
3854
validator.registerField(emailConfirmTextField, errorLabel: emailConfirmErrorLabel, rules: [RequiredRule(), ConfirmationRule(confirmField: emailTextField)])
3955
validator.registerField(phoneNumberTextField, errorLabel: phoneNumberErrorLabel, rules: [RequiredRule(), MinLengthRule(length: 9)])
4056
validator.registerField(zipcodeTextField, errorLabel: zipcodeErrorLabel, rules: [RequiredRule(), ZipCodeRule()])
41-
4257
}
4358

4459
@IBAction func submitTapped(sender: AnyObject) {
4560
println("Validating...")
46-
self.clearErrors()
4761
validator.validate(self)
4862
}
4963

@@ -59,37 +73,6 @@ class ViewController: UIViewController , ValidationDelegate, UITextFieldDelegate
5973
}
6074
func validationFailed(errors:[UITextField:ValidationError]) {
6175
println("Validation FAILED!")
62-
self.setErrors()
63-
}
64-
65-
// MARK: Error Styling
66-
67-
func removeError(label:UILabel, textField:UITextField) {
68-
label.hidden = true
69-
textField.layer.borderWidth = 0.0
70-
}
71-
72-
func removeAllErrors(){
73-
removeError(fullNameErrorLabel, textField: fullNameTextField)
74-
removeError(emailErrorLabel, textField: emailTextField)
75-
removeError(phoneNumberErrorLabel, textField: phoneNumberTextField)
76-
removeError(zipcodeErrorLabel, textField: zipcodeTextField)
77-
}
78-
79-
private func setErrors(){
80-
for (field, error) in validator.errors {
81-
field.layer.borderColor = UIColor.redColor().CGColor
82-
field.layer.borderWidth = 1.0
83-
error.errorLabel?.text = error.errorMessage
84-
error.errorLabel?.hidden = false
85-
}
86-
}
87-
88-
private func clearErrors(){
89-
for (field, error) in validator.errors {
90-
field.layer.borderWidth = 0.0
91-
error.errorLabel?.hidden = true
92-
}
9376
}
9477

9578
func hideKeyboard(){

ValidatorTests/ValidatorTests.swift

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class ValidatorTests: XCTestCase {
2727
let VALID_PASSWORD = "Super$ecret"
2828
let INVALID_PASSWORD = "abc"
2929

30+
let VALID_FLOAT = "1234.444"
31+
let INVALID_FLOAT = "1234.44.44"
32+
3033
let LEN_3 = "hey"
3134
let LEN_5 = "Howdy"
3235
let LEN_20 = "Paint the cat orange"
@@ -42,6 +45,8 @@ class ValidatorTests: XCTestCase {
4245
let UNREGISTER_ERRORS_TXT_FIELD = UITextField()
4346
let UNREGISTER_ERRORS_VALIDATOR = Validator()
4447

48+
let ERROR_LABEL = UILabel()
49+
4550
override func setUp() {
4651
super.setUp()
4752
// Put setup code here. This method is called before the invocation of each test method in the class.
@@ -92,6 +97,17 @@ class ValidatorTests: XCTestCase {
9297
XCTAssertFalse(EmailRule().validate(INVALID_EMAIL), "Email should be invalid")
9398
}
9499

100+
// MARK: Float
101+
102+
func testFloat() {
103+
XCTAssert(FloatRule().validate(VALID_FLOAT), "Float should be valid")
104+
}
105+
106+
func testFloatInvalid() {
107+
XCTAssert(!FloatRule().validate(INVALID_FLOAT), "Float should be invalid")
108+
XCTAssert(!FloatRule().validate(VALID_EMAIL), "Float should be invalid")
109+
}
110+
95111
// MARK: Confirm against field
96112

97113
func testConfirmSame(){
@@ -193,4 +209,65 @@ class ValidatorTests: XCTestCase {
193209
XCTAssert(errors.count == 1, "Should come back with 1 error")
194210
}
195211
}
212+
213+
// MARK: Validate error field gets it's text set to the error, if supplied
214+
215+
func testNoErrorMessageSet() {
216+
REGISTER_VALIDATOR.registerField(REGISTER_TXT_FIELD, errorLabel: ERROR_LABEL, rules: [EmailRule()])
217+
REGISTER_TXT_FIELD.text = VALID_EMAIL
218+
REGISTER_VALIDATOR.validate { (errors) -> Void in
219+
XCTAssert(errors.count == 0, "Should not come back with errors")
220+
}
221+
}
222+
223+
func testErrorMessageSet() {
224+
REGISTER_VALIDATOR.registerField(REGISTER_TXT_FIELD, errorLabel: ERROR_LABEL, rules: [EmailRule()])
225+
var successCount = 0
226+
var errorCount = 0
227+
REGISTER_VALIDATOR.styleTransformers(success: { (validationRule) -> Void in
228+
successCount++
229+
}) { (validationError) -> Void in
230+
errorCount++
231+
}
232+
REGISTER_TXT_FIELD.text = INVALID_EMAIL
233+
REGISTER_VALIDATOR.validate { (errors) -> Void in
234+
XCTAssert(errors.count == 1, "Should come back with errors")
235+
XCTAssert(errorCount == 1, "Should have called the error style transform once")
236+
XCTAssert(successCount == 0, "Should not have called the success style transform as there are no successful fields")
237+
}
238+
}
239+
240+
func testErrorMessageSetAndThenUnset() {
241+
REGISTER_VALIDATOR.registerField(REGISTER_TXT_FIELD, errorLabel: ERROR_LABEL, rules: [EmailRule()])
242+
243+
var successCount = 0
244+
var errorCount = 0
245+
REGISTER_VALIDATOR.styleTransformers(success: { (validationRule) -> Void in
246+
successCount++
247+
}) { (validationError) -> Void in
248+
errorCount++
249+
}
250+
251+
REGISTER_TXT_FIELD.text = INVALID_EMAIL
252+
REGISTER_VALIDATOR.validate { (errors) -> Void in
253+
XCTAssert(errors.count == 1, "Should come back with errors")
254+
XCTAssert(errorCount == 1, "Should have called the error style transform once")
255+
XCTAssert(successCount == 0, "Should not have called the success style transform as there are no successful fields")
256+
self.REGISTER_TXT_FIELD.text = self.VALID_EMAIL
257+
self.REGISTER_VALIDATOR.validate { (errors) -> Void in
258+
XCTAssert(errors.count == 0, "Should not come back with errors: \(errors)")
259+
XCTAssert(successCount == 1, "Should have called the success style transform once")
260+
XCTAssert(errorCount == 1, "Should not have called the error style transform again")
261+
}
262+
}
263+
}
264+
265+
func testTextFieldBorderColorNotSet() {
266+
REGISTER_VALIDATOR.registerField(REGISTER_TXT_FIELD, errorLabel: ERROR_LABEL, rules: [EmailRule()])
267+
REGISTER_TXT_FIELD.text = INVALID_EMAIL
268+
REGISTER_VALIDATOR.validate { (errors) -> Void in
269+
XCTAssert(errors.count == 1, "Should come back with errors")
270+
XCTAssert(!CGColorEqualToColor(self.REGISTER_TXT_FIELD.layer.borderColor, UIColor.redColor().CGColor), "Color shouldn't get set at all")
271+
}
272+
}
196273
}

0 commit comments

Comments
 (0)