Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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