Skip to content
Binary file modified .DS_Store
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>ValidationsDemo.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
34 changes: 30 additions & 4 deletions ValidationsDemo/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
Expand Down Expand Up @@ -84,8 +84,17 @@
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Word Filter" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="rIB-10-ubY">
<rect key="frame" x="16" y="311" width="343" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="D0X-Y5-hrq"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fCI-ud-T3u">
<rect key="frame" x="16" y="332" width="343" height="30"/>
<rect key="frame" x="16" y="429" width="343" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="oZn-rN-b2I"/>
</constraints>
Expand All @@ -94,29 +103,44 @@
<action selector="submitTapped:" destination="BYZ-38-t0r" eventType="touchUpInside" id="fMf-3o-AgM"/>
</connections>
</button>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Word Filter Thorough" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="eqn-04-WMC">
<rect key="frame" x="16" y="349" width="343" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="wNM-IK-GSA"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="rIB-10-ubY" firstAttribute="top" secondItem="OyT-3A-Ry7" secondAttribute="bottom" constant="8" id="2CS-lS-CgA"/>
<constraint firstAttribute="trailing" secondItem="CVs-tP-ud4" secondAttribute="trailing" constant="16" id="5yS-wa-Jv0"/>
<constraint firstItem="eqn-04-WMC" firstAttribute="leading" secondItem="rIB-10-ubY" secondAttribute="leading" id="ABP-nP-zGm"/>
<constraint firstItem="E45-Ox-giI" firstAttribute="top" secondItem="SvQ-RZ-QbB" secondAttribute="bottom" constant="8" id="B5T-3D-Hm2"/>
<constraint firstAttribute="trailing" secondItem="aR2-bE-xtS" secondAttribute="trailing" constant="16" id="B7S-hj-LJN"/>
<constraint firstItem="uAy-dw-xLf" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="BWT-PH-TM2"/>
<constraint firstAttribute="trailing" secondItem="OyT-3A-Ry7" secondAttribute="trailing" constant="16" id="GVd-98-T5t"/>
<constraint firstItem="fCI-ud-T3u" firstAttribute="top" secondItem="OyT-3A-Ry7" secondAttribute="bottom" constant="29" id="IJs-xs-Ki9"/>
<constraint firstAttribute="trailing" secondItem="fCI-ud-T3u" secondAttribute="trailing" constant="16" id="JCI-j1-Q9X"/>
<constraint firstAttribute="trailing" secondItem="RmN-XZ-MHu" secondAttribute="trailing" constant="16" id="JiG-JG-edO"/>
<constraint firstItem="uAy-dw-xLf" firstAttribute="top" secondItem="CVs-tP-ud4" secondAttribute="bottom" constant="8" id="K6K-nV-YLO"/>
<constraint firstItem="CVs-tP-ud4" firstAttribute="top" secondItem="E45-Ox-giI" secondAttribute="bottom" constant="8" id="M8n-7c-IS8"/>
<constraint firstItem="RmN-XZ-MHu" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="Npn-G4-QeI"/>
<constraint firstItem="eqn-04-WMC" firstAttribute="trailing" secondItem="rIB-10-ubY" secondAttribute="trailing" id="Y5w-gL-c8O"/>
<constraint firstItem="eqn-04-WMC" firstAttribute="top" secondItem="rIB-10-ubY" secondAttribute="bottom" constant="8" id="YNR-V4-aYl"/>
<constraint firstAttribute="trailing" secondItem="uAy-dw-xLf" secondAttribute="trailing" constant="16" id="b8R-ff-wKH"/>
<constraint firstItem="aR2-bE-xtS" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="25" id="bJe-gz-VmN"/>
<constraint firstItem="rIB-10-ubY" firstAttribute="trailing" secondItem="OyT-3A-Ry7" secondAttribute="trailing" id="fac-o2-LxO"/>
<constraint firstItem="OyT-3A-Ry7" firstAttribute="top" secondItem="RmN-XZ-MHu" secondAttribute="bottom" constant="8" id="frR-Gw-fyt"/>
<constraint firstItem="fCI-ud-T3u" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="gCn-zT-1Gt"/>
<constraint firstItem="RmN-XZ-MHu" firstAttribute="top" secondItem="uAy-dw-xLf" secondAttribute="bottom" constant="8" id="ja7-55-swP"/>
<constraint firstItem="fCI-ud-T3u" firstAttribute="top" secondItem="eqn-04-WMC" secondAttribute="bottom" constant="50" id="m4r-Ta-oiK"/>
<constraint firstItem="aR2-bE-xtS" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="nqR-3I-0wK"/>
<constraint firstItem="CVs-tP-ud4" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="oF8-xp-dzK"/>
<constraint firstItem="E45-Ox-giI" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="og5-6Q-80g"/>
<constraint firstItem="OyT-3A-Ry7" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="q41-gB-Czq"/>
<constraint firstItem="rIB-10-ubY" firstAttribute="leading" secondItem="OyT-3A-Ry7" secondAttribute="leading" id="rR6-i4-fJI"/>
<constraint firstAttribute="trailing" secondItem="E45-Ox-giI" secondAttribute="trailing" constant="16" id="vDK-Jk-Y9d"/>
<constraint firstItem="SvQ-RZ-QbB" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" constant="16" id="vQ2-r7-hYP"/>
<constraint firstItem="SvQ-RZ-QbB" firstAttribute="top" secondItem="aR2-bE-xtS" secondAttribute="bottom" constant="8" id="vnN-qI-D3Y"/>
Expand All @@ -131,6 +155,8 @@
<outlet property="mobileTextField" destination="E45-Ox-giI" id="KK5-fX-DBc"/>
<outlet property="passwordTextField" destination="CVs-tP-ud4" id="TAC-N3-dEG"/>
<outlet property="requiredTextField" destination="uAy-dw-xLf" id="aJz-eB-Q9n"/>
<outlet property="wordFilterTextField" destination="rIB-10-ubY" id="QgQ-rF-ate"/>
<outlet property="wordFilterThoroughTextField" destination="eqn-04-WMC" id="HRl-mP-MUl"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
Expand Down
37 changes: 37 additions & 0 deletions ValidationsDemo/TextFieldValidation/TextFieldValidation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ internal extension UITextField {
case let .range(min, max, message):
try rangeValidation(min: min, max: max, message: message)

case let .filterMessageBase(message):
try wordFilteredValidation(message: message)

case let .filterMessageExhaustive(message):
try wordFullFilterValidation(message: message)
}

}
}
catch {
Expand Down Expand Up @@ -166,6 +172,37 @@ internal extension UITextField {
}
}

private func wordFilteredValidation(message: String) throws {
let wordsInText = text?.split(separator:" ");
for word in wordsInText! {
if ValidationPreferences.wordsForFilter.contains(String(word)){
throw generateException(message);
}
}
}

private func wordFullFilterValidation(message: String) throws {
let regexPatternForSpecialChars = "\\W+"
let regexFilter = try! NSRegularExpression(pattern: regexPatternForSpecialChars, options: NSRegularExpression.Options.caseInsensitive)
let textRange = NSMakeRange(0, (text?.characters.count)!)
var textForFilter = regexFilter.stringByReplacingMatches(in: text!, options: [], range: textRange, withTemplate: "")
textForFilter = textForFilter.lowercased()
var wordsForFilterDictionary = [String : Bool]()
for word in ValidationPreferences.wordsForFilter {
wordsForFilterDictionary[String(word)] = true
}
for firstCharIndex in 0 ... textForFilter.characters.count {
for endCharIndex in firstCharIndex ... textForFilter.characters.count {
let startIndexForSubstring = textForFilter.index(textForFilter.startIndex, offsetBy: firstCharIndex)
let endIndexForSubstring = textForFilter.index(textForFilter.startIndex, offsetBy: endCharIndex)
let rangeForSubstring = startIndexForSubstring..<endIndexForSubstring
if wordsForFilterDictionary[textForFilter.substring(with: rangeForSubstring)] == true {
throw generateException(message)
}
}
}
}

// Generate error from validations
private func generateException(_ message: String) -> Error {
return NSError(domain: ValidationPreferences.domain, code: ValidationPreferences.errorCode, userInfo: [NSLocalizedDescriptionKey: message, "textField":self]) as Error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ enum Validation {
case characterRange(min:Int, max:Int, message: String)
case alphaNumeric(message: String) // Only allowed A-Z lower or upper case or blank space or numeric values.
case range(min:Int, max:Int, message: String) // Range only apply on numeric values
case filterMessageBase(message: String)
case filterMessageExhaustive(message: String)
}

struct ValidationPreferences {
Expand All @@ -27,4 +29,5 @@ struct ValidationPreferences {
static let passwordRegEx = "(.{6,12})"
static let domain = "VALIDATIONFAILED"
static let errorCode = 501
static let wordsForFilter = [String]()
}
8 changes: 7 additions & 1 deletion ValidationsDemo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class ViewController: UIViewController {
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var ageTextField: UITextField!
@IBOutlet weak var alphaNumericTextField: UITextField!

@IBOutlet weak var wordFilterTextField: UITextField!
@IBOutlet weak var wordFilterThoroughTextField: UITextField!

override func viewDidLoad() {
super.viewDidLoad()

Expand Down Expand Up @@ -49,6 +51,8 @@ extension ViewController {
requiredTextField.validations = [Validation.required(message: "Text field is required")]
ageTextField.validations = [Validation.range(min: 18, max: 70, message: "Invalid age value")]
alphaNumericTextField.validations = [Validation.alphaNumeric(message: "Invalid alphanumeric textfield value")]
wordFilterTextField.validations = [Validation.filterMessageBase(message: "Sorry these words are not allowed")]
wordFilterThoroughTextField.validations = [Validation.filterMessageExhaustive(message: "Sorry these words are not allowed")]
}

fileprivate func checkValidation() -> Bool{
Expand All @@ -60,6 +64,8 @@ extension ViewController {
try requiredTextField.validate()
try ageTextField.validate()
try alphaNumericTextField.validate()
try wordFilterTextField.validate()
try wordFilterThoroughTextField.validate()
return true
}
catch{
Expand Down