├── .swift-version ├── .ruby-version ├── fastlane ├── travis.sh ├── codecov.sh ├── Pluginfile └── README.md ├── .codecov.yml ├── docs ├── img │ ├── gh.png │ ├── carat.png │ ├── dash.png │ └── spinner.gif ├── docsets │ ├── FormValidatorSwift.tgz │ └── FormValidatorSwift.docset │ │ └── Contents │ │ ├── Resources │ │ ├── docSet.dsidx │ │ └── Documents │ │ │ ├── img │ │ │ ├── gh.png │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ └── spinner.gif │ │ │ ├── badge.svg │ │ │ └── js │ │ │ ├── jazzy.js │ │ │ └── jazzy.search.js │ │ └── Info.plist ├── badge.svg └── js │ ├── jazzy.js │ └── jazzy.search.js ├── FormValidatorSwift.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── FormValidatorSwift tvOS.xcscheme │ └── FormValidatorSwift iOS.xcscheme ├── Scripts └── swiftlint_xcode.sh ├── Gemfile ├── .jazzy.yaml ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── CONTRIBUTING.md ├── FormValidatorSwift.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── Sources ├── Protocols │ ├── Validatable.swift │ ├── Configuration.swift │ ├── ConfigurableValidator.swift │ ├── ConfigurableCondition.swift │ ├── ValidatorControl.swift │ ├── Validator.swift │ ├── Condition.swift │ └── Form.swift ├── Models │ ├── ControlForm.swift │ └── FormEntry.swift ├── FormValidatorSwift.h ├── FormValidatorSwift-macOS.h ├── Validators │ ├── URLValidator.swift │ ├── EmailValidator.swift │ ├── PresentValidator.swift │ ├── URLShorthandValidator.swift │ ├── RangeValidator.swift │ ├── NumericValidator.swift │ ├── PostcodeValidator.swift │ ├── AlphabeticValidator.swift │ ├── AlphanumericValidator.swift │ ├── PasswordStrengthValidator.swift │ ├── CompositeValidator.swift │ ├── OptionalValidator.swift │ └── CreditCardValidator.swift ├── Configurations │ ├── RangeConfiguration.swift │ ├── NumericConfiguration.swift │ ├── AlphabeticConfiguration.swift │ ├── AlphanumericConfiguration.swift │ ├── PasswordStrengthConfiguration.swift │ └── PostcodeConfiguration.swift ├── Conditions │ ├── EmailCondition.swift │ ├── URLShorthandCondition.swift │ ├── URLCondition.swift │ ├── PostcodeCondition.swift │ ├── PresentCondition.swift │ ├── Logic │ │ ├── NotCondition.swift │ │ ├── AndCondition.swift │ │ └── OrCondition.swift │ ├── RangeCondition.swift │ ├── NumericCondition.swift │ ├── AlphabeticCondition.swift │ ├── AlphanumericCondition.swift │ ├── CreditCardCondition.swift │ └── PasswordStrengthCondition.swift ├── Info.plist ├── Resources │ ├── ja.lproj │ │ └── Localizable.strings │ ├── ko.lproj │ │ └── Localizable.strings │ ├── da.lproj │ │ └── Localizable.strings │ ├── en.lproj │ │ └── Localizable.strings │ ├── nb.lproj │ │ └── Localizable.strings │ ├── de.lproj │ │ └── Localizable.strings │ ├── ru.lproj │ │ └── Localizable.strings │ ├── tr.lproj │ │ └── Localizable.strings │ ├── pl.lproj │ │ └── Localizable.strings │ ├── es.lproj │ │ └── Localizable.strings │ ├── it.lproj │ │ └── Localizable.strings │ ├── ro.lproj │ │ └── Localizable.strings │ ├── fr.lproj │ │ └── Localizable.strings │ ├── sv.lproj │ │ └── Localizable.strings │ ├── pt-PT.lproj │ │ └── Localizable.strings │ └── Localization.swift └── Controls │ └── ValidatorTextView-AppKit.swift ├── .travis.yml ├── Tests ├── UI Tests │ ├── macOS │ │ └── Info.plist │ └── iOS │ │ └── Info.plist └── Unit Tests │ ├── Info.plist │ ├── Validators │ ├── PresentValidatorTests.swift │ ├── NumericValidatorTests.swift │ ├── PostcodeValidatorTests.swift │ ├── EmailValidatorTests.swift │ ├── URLValidatorTests.swift │ ├── URLShorthandValidatorTests.swift │ ├── RangeValidatorTests.swift │ ├── PasswordStrengthValidatorTests.swift │ ├── OptionalValidatorTests.swift │ ├── CreditCardValidatorTests.swift │ └── CompositeValidatorTests.swift │ ├── Controls │ ├── ValidatorTextViewTests.swift │ └── ValidatorTextFieldTests.swift │ ├── Conditions │ ├── PresentConditionTests.swift │ ├── Logic │ │ ├── NotConditionTests.swift │ │ ├── OrConditionTests.swift │ │ └── AndConditionTests.swift │ ├── CreditCardTypeTests.swift │ ├── URLConditionTests.swift │ ├── RangeConditionTests.swift │ ├── EmailConditionTests.swift │ ├── URLShorthandConditionTests.swift │ ├── PasswordStrengthConditionTests.swift │ ├── PostcodeConditionTests.swift │ └── CreditCardConditionTests.swift │ ├── Configurations │ └── ConfigurationSeeds.swift │ └── Extensions │ └── XCTestCase+Additions.swift ├── Example ├── iOS │ ├── FormAccessibility.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── FormViewController.swift │ ├── FormView.swift │ └── FormEntryView.swift └── macOS │ ├── FormAccessibility.swift │ ├── Info.plist │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── FormViewController.swift │ └── FormEntryView.swift ├── FormValidatorSwift.podspec ├── LICENSE.md ├── .gitignore ├── .swiftlint.yml ├── CODE_OF_CONDUCT.md ├── iOS Example.xcodeproj └── xcshareddata │ └── xcschemes │ └── iOS Example.xcscheme └── macOS Example.xcodeproj └── xcshareddata └── xcschemes └── macOS Example.xcscheme /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.4.3 -------------------------------------------------------------------------------- /fastlane/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | bundle exec fastlane test 4 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - Example/.* 4 | - Tests/.* 5 | -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/img/dash.png -------------------------------------------------------------------------------- /fastlane/codecov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash <(curl -s https://codecov.io/bash) 4 | sleep 5 5 | -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/docsets/FormValidatorSwift.tgz -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-versioning' 6 | -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/docsets/FormValidatorSwift.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustwo/formvalidator-swift/HEAD/docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /FormValidatorSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Scripts/swiftlint_xcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if which swiftlint >/dev/null; then 6 | swiftlint 7 | else 8 | echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" 9 | fi 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'fastlane' 4 | gem 'jazzy' 5 | gem 'cocoapods', '~> 1.6.1' 6 | 7 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 8 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 9 | -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | author: ustwo Fampany Ltd. 2 | author_url: https://ustwo.com/ 3 | github_url: https://github.com/ustwo/formvalidator-swift 4 | module: FormValidatorSwift 5 | output: docs 6 | theme: fullwidth 7 | xcodebuild_arguments: [-workspace,'FormValidatorSwift.xcworkspace',-scheme,'FormValidatorSwift iOS'] 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | > Provide a general description of what is this Pull Request about and any 4 | context that makes easier to review the changes. 5 | 6 | > If it addresses a Github Issue please mention the number (e.g. `#34`) to keep 7 | track of the different parts of the topic. 8 | -------------------------------------------------------------------------------- /FormValidatorSwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Protocols/Validatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Validatable.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public protocol Validatable { 13 | 14 | var validatableText: String? { get } 15 | var validator: Validator { get } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10.2 2 | language: objective-c 3 | 4 | env: 5 | global: 6 | - LC_CTYPE=en_US.UTF-8 7 | - LANG=en_US.UTF-8 8 | 9 | before_install: 10 | - rvm list 11 | - rvm install $(cat .ruby-version) 12 | - brew update 13 | - brew outdated swiftlint || brew upgrade swiftlint 14 | - gem install bundler -v 2.0.1 15 | 16 | # Test using Fastlane 17 | script: 18 | - ./fastlane/travis.sh 19 | -------------------------------------------------------------------------------- /FormValidatorSwift.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Sources/Protocols/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// A configuration for a `Condition`. 13 | public protocol Configuration { 14 | 15 | /// Initializes a `Configuration` with the default values. 16 | init() 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | > Describe the issue you are seeing. 4 | 5 | ### Steps to Reproduce 6 | 7 | 1. 8 | 2. 9 | 3. 10 | 11 | ### Expected Result 12 | 13 | ### Actual Result 14 | 15 | ### Version Information 16 | 17 | The current version I am using is: 18 | 19 | - Xcode: 20 | - iOS/macOS/tvOS: 21 | - FormValidator: 22 | 23 | ### Other Information 24 | 25 | > Add logs, screenshots, etc. that will help in debugging the issue. 26 | -------------------------------------------------------------------------------- /Sources/Models/ControlForm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldsForm.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * Convenience implementation of `Form` that is a form full of any type of `ValidatorControl`. 14 | */ 15 | public struct ControlForm: Form { 16 | 17 | public var entries: [FormEntry] 18 | 19 | public init() { 20 | entries = [FormEntry]() 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Sources/FormValidatorSwift.h: -------------------------------------------------------------------------------- 1 | // 2 | // FormValidatorSwift.h 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 12/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FormValidatorSwift. 12 | FOUNDATION_EXPORT double FormValidatorSwiftVersionNumber; 13 | 14 | //! Project version string for FormValidatorSwift. 15 | FOUNDATION_EXPORT const unsigned char FormValidatorSwiftVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/FormValidatorSwift-macOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // FormValidatorSwift.h 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 12/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FormValidatorSwift. 12 | FOUNDATION_EXPORT double FormValidatorSwiftVersionNumber; 13 | 14 | //! Project version string for FormValidatorSwift. 15 | FOUNDATION_EXPORT const unsigned char FormValidatorSwiftVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/Validators/URLValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `URLValidator` contains an `URLCondition`. A valid string is a full URL with scheme. 14 | * - seealso: `URLCondition` 15 | */ 16 | public struct URLValidator: Validator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | conditions = [URLCondition()] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Validators/EmailValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `EmailValidator` contains an `EmailCondition`. A valid string is an email address. 14 | * - seealso: `EmailCondition` 15 | */ 16 | public struct EmailValidator: Validator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | conditions = [EmailCondition()] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Validators/PresentValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `PresentValidator` contains an `PresentCondition`. A valid string is a non-empty string. 14 | * - seealso: `PresentCondition` 15 | */ 16 | public struct PresentValidator: Validator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | conditions = [PresentCondition()] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Validators/URLShorthandValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLShorthandValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `URLShorthandValidator` contains an `URLShorthandCondition`. A valid string is a URL, with or without scheme. 14 | * - seealso: `URLShorthandCondition` 15 | */ 16 | public struct URLShorthandValidator: Validator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | conditions = [URLShorthandCondition()] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.formvalidatorswift 7 | CFBundleName 8 | FormValidatorSwift 9 | DocSetPlatformFamily 10 | formvalidatorswift 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/UI Tests/macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 3.0.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/Configurations/RangeConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeConfiguration.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// Stores configuration for `RangeCondition`. 13 | public struct RangeConfiguration: Configuration { 14 | 15 | 16 | // MARK: - Properties 17 | 18 | public let range: CountableRange 19 | 20 | 21 | // MARK: - Initializers 22 | 23 | /// Initializes a `RangeConfiguration` with a `0..<1` range. 24 | public init() { 25 | self.init(range: 0..<1) 26 | } 27 | 28 | public init(range: CountableRange) { 29 | self.range = range 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Validators/RangeValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `RangeValidator` contains an `RangeCondition`. A valid string meets the required string length. 14 | * - seealso: `RangeCondition` 15 | */ 16 | public struct RangeValidator: ConfigurableValidator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init(configuration: RangeConfiguration) { 27 | conditions = [RangeCondition(configuration: configuration)] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Validators/NumericValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `NumericValidator` contains an `NumericCondition`. A valid string only contains numbers. 14 | * - seealso: `NumericCondition` 15 | */ 16 | public struct NumericValidator: ConfigurableValidator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init(configuration: NumericConfiguration) { 27 | conditions = [NumericCondition(configuration: configuration)] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Validators/PostcodeValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostcodeValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `PostcodeValidator` contains an `PostcodeCondition`. A valid string is a postcode. 14 | * - seealso: `PostcodeCondition` 15 | */ 16 | public struct PostcodeValidator: ConfigurableValidator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init(configuration: PostcodeConfiguration) { 27 | conditions = [PostcodeCondition(configuration: configuration)] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Validators/AlphabeticValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabeticValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `AlphabeticValidator` contains an `AlphabeticCondition`. A valid string only contains letters. 14 | * - seealso: `AlphabeticCondition` 15 | */ 16 | public struct AlphabeticValidator: ConfigurableValidator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init(configuration: AlphabeticConfiguration) { 27 | conditions = [AlphabeticCondition(configuration: configuration)] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Conditions/EmailCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `EmailCondition` checks a string for an email. 14 | */ 15 | public struct EmailCondition: Condition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationEmail", comment: "") 21 | 22 | public let regex = "^[+\\w\\.\\-']+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*(\\.[a-zA-Z]{2,})+$" 23 | 24 | public var shouldAllowViolation = true 25 | 26 | 27 | // MARK: - Initializers 28 | 29 | public init() { } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 3.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/UI Tests/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 3.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/Validators/AlphanumericValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphanumericValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `AlphanumericValidator` contains an `AlphanumericCondition`. A valid string only contains letters and/or numbers. 14 | * - seealso: `AlphanumericCondition` 15 | */ 16 | public struct AlphanumericValidator: ConfigurableValidator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init(configuration: AlphanumericConfiguration) { 27 | conditions = [AlphanumericCondition(configuration: configuration)] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Example/iOS/FormAccessibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormAccessibility.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | enum FormAccessibility { 13 | 14 | enum Identifiers { 15 | static let EmailLabel = "EMAIL_LABEL" 16 | static let EmailTextField = "EMAIL_TEXTFIELD" 17 | 18 | static let ErrorLabel = "ERROR_LABEL" 19 | 20 | static let NameLabel = "NAME_LABEL" 21 | static let NameTextField = "NAME_TEXTFIELD" 22 | 23 | static let TitleLabel = "TITLE_LABEL" 24 | static let TitleTextField = "TITLE_TEXTFIELD" 25 | 26 | static let SubmitButton = "SUBMIT_BUTTON" 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Example/macOS/FormAccessibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormAccessibility.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 06/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum FormAccessibility { 12 | 13 | enum Identifiers { 14 | static let EmailLabel = "EMAIL_LABEL" 15 | static let EmailTextField = "EMAIL_TEXTFIELD" 16 | 17 | static let ErrorLabel = "ERROR_LABEL" 18 | 19 | static let NameLabel = "NAME_LABEL" 20 | static let NameTextField = "NAME_TEXTFIELD" 21 | 22 | static let TitleLabel = "TITLE_LABEL" 23 | static let TitleTextField = "TITLE_TEXTFIELD" 24 | 25 | static let SubmitButton = "SUBMIT_BUTTON" 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Validators/PasswordStrengthValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasswordStrengthValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `PasswordStrengthValidator` contains an `PasswordStrengthCondition`. A valid string meets the required strength level. 14 | * - seealso: `PasswordStrengthCondition` 15 | */ 16 | public struct PasswordStrengthValidator: ConfigurableValidator { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init(configuration: PasswordStrengthConfiguration) { 27 | conditions = [PasswordStrengthCondition(configuration: configuration)] 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Protocols/ConfigurableValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurableValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// A special type of `Validator` that allows configuration. 13 | public protocol ConfigurableValidator: Validator { 14 | 15 | associatedtype ConfigurationType: Configuration 16 | 17 | /// Initializes a new `ConfigurableValidator` with a given configuration. 18 | /// 19 | /// - Parameter configuration: The configuration to use. 20 | init(configuration: ConfigurationType) 21 | 22 | } 23 | 24 | 25 | // Default implemenation of `Condition:init()`. Initializes with the default configuration. 26 | extension ConfigurableValidator { 27 | 28 | public init() { 29 | self.init(configuration: ConfigurationType()) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Conditions/URLShorthandCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLShorthandCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `URLShorthandCondition` checks a string for a valid URL. 14 | * - note: No scheme (protocol) is needed for a valid URL. If you want a check for more strict URLs see `URLCondition`. 15 | */ 16 | public struct URLShorthandCondition: Condition { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationShorthandURL", comment: "") 22 | 23 | public let regex = "^((https?)://)?[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$" 24 | 25 | public var shouldAllowViolation = true 26 | 27 | 28 | // MARK: - Initializers 29 | 30 | public init() { } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Conditions/URLCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `URLCondition` checks a string for a valid URL. 14 | * - note: The scheme (protocol) is needed for a valid URL. If you want a check for shorthand URLs see `URLShorthandCondition`. Only HTTP and HTTPS schemes are considered valid. 15 | */ 16 | public struct URLCondition: Condition { 17 | 18 | 19 | // MARK: - Properties 20 | 21 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationURL", comment: "") 22 | 23 | public let regex = "^((https?)://)[a-z0-9-]+(\\.[a-z0-9-]+)+([/?].*)?$" 24 | 25 | public var shouldAllowViolation = true 26 | 27 | 28 | // MARK: - Initializers 29 | 30 | public init() { } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Conditions/PostcodeCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostcodeCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `PostcodeCondition` checks a string for a post code. 14 | */ 15 | public struct PostcodeCondition: ConfigurableCondition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationPostcodeUK", comment: "") 21 | 22 | public var regex: String { 23 | return configuration.country.regex 24 | } 25 | 26 | public var shouldAllowViolation = true 27 | 28 | public var configuration: PostcodeConfiguration 29 | 30 | 31 | // MARK: - Initializer 32 | 33 | public init(configuration: PostcodeConfiguration) { 34 | self.configuration = configuration 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Conditions/PresentCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * The `PresentCondition` checks for the presence of a string. 13 | */ 14 | public struct PresentCondition: Condition { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationPresent", comment: "") 20 | 21 | public let regex = "" 22 | 23 | public var shouldAllowViolation = true 24 | 25 | 26 | // MARK: - Initializers 27 | 28 | public init() { } 29 | 30 | 31 | // MARK: - Check 32 | 33 | public func check(_ text: String?) -> Bool { 34 | guard let sourceText = text else { 35 | return false 36 | } 37 | 38 | return !sourceText.isEmpty 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Resources/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "%d文字以上、%d文字以下で入力して下さい。"; 10 | "US2KeyConditionViolationNumeric" = "数字のみで入力して下さい。"; 11 | "US2KeyConditionViolationAlphanumeric" = "数字か文字のみで入力して下さい。"; 12 | "US2KeyConditionViolationAlphabetic" = "文字のみで入力して下さい。"; 13 | "US2KeyConditionViolationEmail" = "example@example.comの形式でメールアドレスを入力して下さい。"; 14 | "US2KeyConditionViolationURL" = "http(s)://www.example.comの形式でURLを入力して下さい。"; 15 | "US2KeyConditionViolationShorthandURL" = "www.example.comの形式でURLを入力して下さい。"; 16 | "US2KeyConditionViolationPasswordStrength" = "もっと強いパスワードを入力して下さい。"; 17 | "US2KeyConditionViolationPostcodeUK" = "正式なUKの郵便番号を入力して下さい。"; 18 | "US2KeyConditionViolationPresent" = "テキストを入力して下さい。"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Configurations/NumericConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericConfiguration.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 06/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// Stores configuration for `NumericCondition`. 13 | public struct NumericConfiguration: Configuration { 14 | 15 | 16 | // MARK: - Properties 17 | 18 | /// Whether or not to allow Unicode numbers. If `false` then only ASCII numbers (0-9) are allowed. 19 | public let allowsUnicode: Bool 20 | /// Whether or not to allow whitespace. 21 | public let allowsWhitespace: Bool 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | self.init(allowsUnicode: true, allowsWhitespace: false) 28 | } 29 | 30 | public init(allowsUnicode: Bool = true, allowsWhitespace: Bool = false) { 31 | self.allowsWhitespace = allowsWhitespace 32 | self.allowsUnicode = allowsUnicode 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Protocols/ConfigurableCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurableCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// A special type of `Condition` that allows configuration. 13 | public protocol ConfigurableCondition: Condition { 14 | 15 | associatedtype ConfigurationType: Configuration 16 | 17 | /// Configuration for the `Condition`. 18 | var configuration: ConfigurationType { get } 19 | 20 | /// Initializes a new `ConfigurableCondition` with a given configuration. 21 | /// 22 | /// - Parameter configuration: The configuration to use. 23 | init(configuration: ConfigurationType) 24 | 25 | } 26 | 27 | 28 | // Default implemenation of `Condition:init()`. Initializes with the default configuration. 29 | extension ConfigurableCondition { 30 | 31 | public init() { 32 | self.init(configuration: ConfigurationType()) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Resources/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "최소%d자,최대%d자를 입력하세요"; 10 | "US2KeyConditionViolationNumeric" = "숫자만 입력하세요"; 11 | "US2KeyConditionViolationAlphanumeric" = "숫자와 영문자를 혼합하여 입력하세요"; 12 | "US2KeyConditionViolationAlphabetic" = "문자만 입력하세요"; 13 | "US2KeyConditionViolationEmail" = "유효한 이메일 주소를 입력하세요 example@example.com"; 14 | "US2KeyConditionViolationURL" = "유효한 URL주소를 http(s)를 포함하여 입력하세요 예) http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "유효한 URL주소를 입력하세요 예) www.example.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "좀 더 안전한 비밀번호를 입력하세요"; 17 | "US2KeyConditionViolationPostcodeUK" = "유효한 영국내 우편주소를 입력하세요"; 18 | "US2KeyConditionViolationPresent" = "글자를 입력하세요"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Example/iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | // MARK: - Properties 16 | 17 | var window: UIWindow? 18 | 19 | 20 | // MARK: - UIApplicationDelegate 21 | 22 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 23 | let viewController = FormViewController() 24 | let navigationController = UINavigationController(rootViewController: viewController) 25 | navigationController.navigationBar.isTranslucent = false 26 | 27 | window = UIWindow(frame: UIScreen.main.bounds) 28 | window?.rootViewController = navigationController 29 | window?.makeKeyAndVisible() 30 | 31 | return true 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Configurations/AlphabeticConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabeticConfiguration.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// Stores configuration for `AlphabeticCondition`. 13 | public struct AlphabeticConfiguration: Configuration { 14 | 15 | 16 | // MARK: - Properties 17 | 18 | /// Whether or not to allow Unicode letters. If `false` then only ASCII letters (A-Z, a-z) are allowed. 19 | public let allowsUnicode: Bool 20 | /// Whether or not to allow whitespace. 21 | public let allowsWhitespace: Bool 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | self.init(allowsUnicode: true, allowsWhitespace: false) 28 | } 29 | 30 | public init(allowsUnicode: Bool = true, allowsWhitespace: Bool = false) { 31 | self.allowsWhitespace = allowsWhitespace 32 | self.allowsUnicode = allowsUnicode 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Models/FormEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormEntry.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * A single entry within a `Form`. 14 | */ 15 | public struct FormEntry { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | /// A `Validatable` that contains text to be validated. 21 | public let validatable: ValidatorControl 22 | /// A `Validator` to use to validate text from `validatable`. 23 | public let validator: Validator 24 | 25 | 26 | // MARK: - Check 27 | 28 | /** 29 | Checks the text from `validatable` using `validator` from `FormEntry` (NOT the `validator` from `validatable`). 30 | - returns: An array of conditions that were violated. If no conditions were violated then `nil` is returned. 31 | */ 32 | public func checkConditions() -> [Condition]? { 33 | return validator.checkConditions(validatable.validatableText) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Conditions/Logic/NotCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * A condition that returns the opposite of the original condition. 14 | */ 15 | public struct NotCondition: Condition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = "" 21 | 22 | public let regex = "" 23 | 24 | public var shouldAllowViolation = true 25 | 26 | public let condition: Condition 27 | 28 | 29 | // MARK: - Initializers 30 | 31 | public init() { 32 | self.init(condition: AlphanumericCondition()) 33 | } 34 | 35 | public init(condition: Condition) { 36 | self.condition = condition 37 | } 38 | 39 | 40 | // MARK: - Check 41 | 42 | public func check(_ text: String?) -> Bool { 43 | return !condition.check(text) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Configurations/AlphanumericConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphanumericConfiguration.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// Stores configuration for `AlphanumericCondition`. 13 | public struct AlphanumericConfiguration: Configuration { 14 | 15 | 16 | // MARK: - Properties 17 | 18 | /// Whether or not to allow Unicode letters and numbers. If `false` then only ASCII letters (A-Z, a-z, 0-9) are allowed. 19 | public let allowsUnicode: Bool 20 | /// Whether or not to allow whitespace. 21 | public let allowsWhitespace: Bool 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | self.init(allowsUnicode: true, allowsWhitespace: false) 28 | } 29 | 30 | public init(allowsUnicode: Bool = true, allowsWhitespace: Bool = false) { 31 | self.allowsWhitespace = allowsWhitespace 32 | self.allowsUnicode = allowsUnicode 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Conditions/Logic/AndCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AndCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * A condition that returns the result of either operands. 14 | */ 15 | public struct AndCondition: Condition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = "" 21 | 22 | public let regex = "" 23 | 24 | public var shouldAllowViolation = true 25 | 26 | public let conditions: [Condition] 27 | 28 | 29 | // MARK: - Initializers 30 | 31 | public init() { 32 | self.init(conditions: [AlphanumericCondition()]) 33 | } 34 | 35 | public init(conditions: [Condition]) { 36 | self.conditions = conditions 37 | } 38 | 39 | 40 | // MARK: - Check 41 | 42 | public func check(_ text: String?) -> Bool { 43 | return conditions.reduce(true, { $0 && $1.check(text) }) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Conditions/Logic/OrCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OrCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * A condition that returns the result of either operands. 14 | */ 15 | public struct OrCondition: Condition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = "" 21 | 22 | public let regex = "" 23 | 24 | public var shouldAllowViolation = true 25 | 26 | public let conditions: [Condition] 27 | 28 | 29 | // MARK: - Initializers 30 | 31 | public init() { 32 | self.init(conditions: [AlphanumericCondition()]) 33 | } 34 | 35 | public init(conditions: [Condition]) { 36 | self.conditions = conditions 37 | } 38 | 39 | 40 | // MARK: - Check 41 | 42 | public func check(_ text: String?) -> Bool { 43 | return conditions.reduce(false, { $0 || $1.check(text) }) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /FormValidatorSwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'FormValidatorSwift' 3 | s.version = '4.0.0' 4 | s.license = { :type => "MIT", :file => "License.md" } 5 | s.summary = 'A framework to validate inputs of text fields and text views in a convenient way.' 6 | s.homepage = 'https://github.com/ustwo/formvalidator-swift' 7 | s.authors = { 'Shagun Madhikarmi' => 'shagun@ustwo.com', 8 | 'Aaron McTavish' => 'aamct@ustwo.com' } 9 | s.source = { :git => 'https://github.com/ustwo/formvalidator-swift.git', :tag => s.version } 10 | 11 | s.ios.deployment_target = '8.3' 12 | s.tvos.deployment_target = '9.0' 13 | s.osx.deployment_target = '10.11' 14 | 15 | s.source_files = 'Sources/**/*.swift' 16 | s.ios.exclude_files = '**/*AppKit.swift' 17 | s.tvos.exclude_files = '**/*AppKit.swift' 18 | s.osx.exclude_files = '**/*UIKit.swift' 19 | 20 | s.resource_bundles = { 'FormValidatorSwift' => 'Sources/Resources/**/*.strings' } 21 | 22 | s.frameworks = 'Foundation' 23 | s.ios.frameworks = 'UIKit' 24 | s.tvos.frameworks = 'UIKit' 25 | s.osx.frameworks = 'AppKit' 26 | 27 | s.requires_arc = true 28 | end 29 | -------------------------------------------------------------------------------- /Sources/Validators/CompositeValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositeValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `CompositeValidator` concatenates all of the conditions of the individual validators. All conditions must be satisfied for a string to be valid. 14 | */ 15 | public struct CompositeValidator: Validator { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var conditions: [Condition] 21 | 22 | public let validators: [Validator] 23 | 24 | 25 | // MARK: - Initializers 26 | 27 | public init() { 28 | self.init(validators: [PresentValidator()]) 29 | } 30 | 31 | /** 32 | Initializes a `CompositeValidator`. 33 | - parameter validators: Validators which are used for validation. 34 | */ 35 | public init(validators: [Validator]) { 36 | self.conditions = validators.map { $0.conditions }.flatMap { $0 } 37 | self.validators = validators 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 ustwo™ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/Resources/da.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Brug mindst %d, max %d tegn"; 10 | "US2KeyConditionViolationNumeric" = "Brug kun tal"; 11 | "US2KeyConditionViolationAlphanumeric" = "Brug kun tal og bogstaver"; 12 | "US2KeyConditionViolationAlphabetic" = "Brug kun bogstaver"; 13 | "US2KeyConditionViolationEmail" = "Skriv en gyldig email-adresse i dette format: example@example.com"; 14 | "US2KeyConditionViolationURL" = "Skriv en gyldig URL i dette format: http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Skriv en gyldig URL i dette format: www.example.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "Brug et sikrere password"; 17 | "US2KeyConditionViolationPostcodeUK" = "Skriv et gyldigt UK postnummer"; 18 | "US2KeyConditionViolationPresent" = "Skriv tekst"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Resources/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | 10 | "US2KeyConditionViolationRange" = "Enter minimum %d, maximum %d characters"; 11 | "US2KeyConditionViolationNumeric" = "Enter numbers only"; 12 | "US2KeyConditionViolationAlphanumeric" = "Enter numbers and letters only"; 13 | "US2KeyConditionViolationAlphabetic" = "Enter letters only"; 14 | "US2KeyConditionViolationEmail" = "Enter valid email address in the format example@example.com"; 15 | "US2KeyConditionViolationURL" = "Enter a valid URL in the format http(s)://www.example.com"; 16 | "US2KeyConditionViolationShorthandURL" = "Enter a valid URL in the format www.example.com"; 17 | "US2KeyConditionViolationPasswordStrength" = "Enter a stronger password"; 18 | "US2KeyConditionViolationPostcodeUK" = "Enter a valid UK post code"; 19 | "US2KeyConditionViolationPresent" = "Enter some text"; 20 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 21 | -------------------------------------------------------------------------------- /Example/macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 4.0.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Ustwo Fampany Ltd. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Sources/Resources/nb.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Skriv inn minimum %d, maksimum %d tegn"; 10 | "US2KeyConditionViolationNumeric" = "Bruk kun nummer"; 11 | "US2KeyConditionViolationAlphanumeric" = "Bruk kun nummer og bokstaver"; 12 | "US2KeyConditionViolationAlphabetic" = "Bruk kun bokstaver"; 13 | "US2KeyConditionViolationEmail" = "Skriv inn en gyldig e-postadresse i formatet example@example.com"; 14 | "US2KeyConditionViolationURL" = "Skriv inn en gyldig lenke i formatet http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Skriv inn en gyldig lenke i formatet www.example.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "Skriv inn et mer sikkert passord"; 17 | "US2KeyConditionViolationPostcodeUK" = "Skriv inn et gyldig UK postnummer"; 18 | "US2KeyConditionViolationPresent" = "Skriv inn tekst"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Resources/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Minimum %d, maximal %d Zeichen eingeben"; 10 | "US2KeyConditionViolationNumeric" = "Nur Zahlen eingeben"; 11 | "US2KeyConditionViolationAlphanumeric" = "Nur Zahlen und Buchstaben eingeben"; 12 | "US2KeyConditionViolationAlphabetic" = "Nur Buchstaben eingeben"; 13 | "US2KeyConditionViolationEmail" = "Gültige E-Mail-Adresse im Format beispiel@domain.com eingeben"; 14 | "US2KeyConditionViolationURL" = "Gültige URL im Format http(s)://www.example.com eingeben"; 15 | "US2KeyConditionViolationShorthandURL" = "Gültige URL im Format www.example.com eingeben"; 16 | "US2KeyConditionViolationPasswordStrength" = "Ein stärkeres Passwort wird benötigt"; 17 | "US2KeyConditionViolationPostcodeUK" = "Ungültige UK Postleitzahl"; 18 | "US2KeyConditionViolationPresent" = "Geben Sie eine Zeichenfolge"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Resources/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Введите минимум %d, максимум %d символов"; 10 | "US2KeyConditionViolationNumeric" = "Введите только цифры"; 11 | "US2KeyConditionViolationAlphanumeric" = "Введите цифры и буквы"; 12 | "US2KeyConditionViolationAlphabetic" = "Введите только буквы"; 13 | "US2KeyConditionViolationEmail" = "Введите действующий адрес электронной почты в формате example@example.com"; 14 | "US2KeyConditionViolationURL" = "Введите действующую ссылку в формате http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Введите действующую ссылку в формате www.example.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "Задайте более сложный пароль"; 17 | "US2KeyConditionViolationPostcodeUK" = "Введите действующий почтовый индекс UK"; 18 | "US2KeyConditionViolationPresent" = "Введите текст"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Resources/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | 10 | "US2KeyConditionViolationRange" = "En az %d, en çok %d karakter girin"; 11 | "US2KeyConditionViolationNumeric" = "Sadece rakam girin"; 12 | "US2KeyConditionViolationAlphanumeric" = "Sadece rakam ve harf girin"; 13 | "US2KeyConditionViolationAlphabetic" = "Sadece harf girin"; 14 | "US2KeyConditionViolationEmail" = "ornek@ornek.com formatında geçerli bir e-posta adresi girin"; 15 | "US2KeyConditionViolationURL" = "http(s)://www.ornek.com formatında geçerli bir adres girin"; 16 | "US2KeyConditionViolationShorthandURL" = "www.ornek.com formatında geçerli bir adres girin"; 17 | "US2KeyConditionViolationPasswordStrength" = "Daha güvenli bir parola girin"; 18 | "US2KeyConditionViolationPostcodeUK" = "Geçerli bir İngiliz posta kodu girin"; 19 | "US2KeyConditionViolationPresent" = "Bu alanı doldurmak zorunludur"; 20 | "US2KeyConditionViolationCreditCard" = "Geçerli bir kredi kartı numarası girin"; 21 | -------------------------------------------------------------------------------- /Sources/Resources/pl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Podaj minimalnie %d, maksymalnie %d znaków"; 10 | "US2KeyConditionViolationNumeric" = "Podaj jedynie liczby"; 11 | "US2KeyConditionViolationAlphanumeric" = "Podaj jedynie liczby i litery"; 12 | "US2KeyConditionViolationAlphabetic" = "Podaj jedynie litery"; 13 | "US2KeyConditionViolationEmail" = "Podaj poprawny adres e-mail w formacie example@example.com"; 14 | "US2KeyConditionViolationURL" = "Podaj poprawny adres URL w formacie http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Podaj poprawny adres URL w formacie www.example.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "Podaj bardziej bezpieczne hasło"; 17 | "US2KeyConditionViolationPostcodeUK" = "Podaj poprawny kod pocztowy, ważny w Wielkiej Brytanii."; 18 | "US2KeyConditionViolationPresent" = "Podaj tekst"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 72% 23 | 24 | 25 | 72% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Example/macOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Sources/Resources/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Juan Delgado on 09/04/2018. 6 | Copyright © 2018 ustwo. All rights reserved. 7 | */ 8 | 9 | 10 | "US2KeyConditionViolationRange" = "Introduzca mínimo %d, máximo %d caracteres"; 11 | "US2KeyConditionViolationNumeric" = "Introduzca sólo números"; 12 | "US2KeyConditionViolationAlphanumeric" = "Introduzca sólo números y letras"; 13 | "US2KeyConditionViolationAlphabetic" = "Introduzca sólo letras"; 14 | "US2KeyConditionViolationEmail" = "Introduzca un email válido con formato ejemplo@ejemplo.com"; 15 | "US2KeyConditionViolationURL" = "Introduzca una URL válida con formato http(s)://www.ejemplo.com"; 16 | "US2KeyConditionViolationShorthandURL" = "Introduzca una URL válida con formato www.ejemplo.com"; 17 | "US2KeyConditionViolationPasswordStrength" = "Introduzca una contraseña más compleja"; 18 | "US2KeyConditionViolationPostcodeUK" = "Introduzca un código postal válido"; 19 | "US2KeyConditionViolationPresent" = "Introduzca texto"; 20 | "US2KeyConditionViolationCreditCard" = "Introduzca un número de tarjeta válido"; 21 | -------------------------------------------------------------------------------- /Sources/Resources/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Inserire minimo %d, massimo %d caratteri"; 10 | "US2KeyConditionViolationNumeric" = "Inserire solo numeri"; 11 | "US2KeyConditionViolationAlphanumeric" = "Inserire solo numeri e lettere"; 12 | "US2KeyConditionViolationAlphabetic" = "Inserire solo lettere"; 13 | "US2KeyConditionViolationEmail" = "Inserire un indirizzo email valido nel formato esempio@esempio.it"; 14 | "US2KeyConditionViolationURL" = "Inserire un indirizzo valido nel formato http(s)://www.esempio.it"; 15 | "US2KeyConditionViolationShorthandURL" = "Inserire un indirizzo valido nel formato http(s)://www.esempio.it"; 16 | "US2KeyConditionViolationPasswordStrength" = "Inserire una password più sicura"; 17 | "US2KeyConditionViolationPostcodeUK" = "Inserire un codice postale valido"; 18 | "US2KeyConditionViolationPresent" = "Inserire del testo"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Resources/ro.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Introduceți minimum %d, maximum %d caractere"; 10 | "US2KeyConditionViolationNumeric" = "Introduceți numai cifre"; 11 | "US2KeyConditionViolationAlphanumeric" = "Introduceți cifre si litere"; 12 | "US2KeyConditionViolationAlphabetic" = "Introduceți numai litere"; 13 | "US2KeyConditionViolationEmail" = "Introduceți adresa dvs. de email validă în formatul example@example.com"; 14 | "US2KeyConditionViolationURL" = "Introduceți o adresă URL validă în formatul http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Introduceți o adresă URL validă în formatul www.example.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "Alegeți o parolă mai puternică"; 17 | "US2KeyConditionViolationPostcodeUK" = "Introduceți un cod poștal valid din UK "; 18 | "US2KeyConditionViolationPresent" = "Introduceți textul"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/PresentValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class PresentValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = PresentValidator() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testPresentValidator_Success() { 25 | // Given 26 | let testInput = "Foo" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testPresentValidator_Failure() { 37 | // Given 38 | let testInput = "" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Resources/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Entrez les caracteres %d minimums, %d maximums"; 10 | "US2KeyConditionViolationNumeric" = "Entrez les chiffres seulement"; 11 | "US2KeyConditionViolationAlphanumeric" = "Entrez les chiffres et lettres seulement"; 12 | "US2KeyConditionViolationAlphabetic" = "Entrez les lettres seulement"; 13 | "US2KeyConditionViolationEmail" = "Entrez l'adresse valide du courier électronique sous forme de example@example.com"; 14 | "US2KeyConditionViolationURL" = "Entrez un URL valide sous forme de http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Entrez un URL valide sous forme de www.example.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "Entrez un mot de passe plus fort"; 17 | "US2KeyConditionViolationPostcodeUK" = "Entrez un code postal valide pour le Royaume Uni"; 18 | "US2KeyConditionViolationPresent" = "Entrez le texte"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Resources/sv.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Ange ett värde mellan %d och %d tecken långt"; 10 | "US2KeyConditionViolationNumeric" = "Vänligen ange endast siffror"; 11 | "US2KeyConditionViolationAlphanumeric" = "Vänligen ange endast siffror och bokstäver"; 12 | "US2KeyConditionViolationAlphabetic" = "Vänligen ange endast bokstäver"; 13 | "US2KeyConditionViolationEmail" = "Vänligen ange en giltig emailadress med formatet example@example.com"; 14 | "US2KeyConditionViolationURL" = "Vänligen ange en giltig URL med formatet http(s)://www.example.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Vänligen ange en giltig URL med formatet www.example.com "; 16 | "US2KeyConditionViolationPasswordStrength" = "Vänligen ange ett säkrare lösenord"; 17 | "US2KeyConditionViolationPostcodeUK" = "Vänligen ange en postkod giltig i UK"; 18 | "US2KeyConditionViolationPresent" = "Vänligen ange text"; 19 | "US2KeyConditionViolationCreditCard" = "Vänligen ange ett giltigt kreditkortsnummer"; 20 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/NumericValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class NumericValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = NumericValidator() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testNumericValidator_Success() { 25 | // Given 26 | let testInput = "1234567890" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testNumericValidator_Failure() { 37 | // Given 38 | let testInput = "123a" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 73% 23 | 24 | 25 | 73% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/PostcodeValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostcodeValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class PostcodeValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = PostcodeValidator() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testPostcodeValidator_Success() { 25 | // Given 26 | let testInput = "M1 1BA" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testPostcodeValidator_Failure() { 37 | // Given 38 | let testInput = "M1AA 1BA" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Protocols/ValidatorControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidatorControl.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | public protocol ValidatorControlDelegate: AnyObject { 13 | 14 | func validatorControlDidChange(_ validatorControl: ValidatorControl) 15 | func validatorControl(_ validatorControl: ValidatorControl, changedValidState validState: Bool) 16 | func validatorControl(_ validatorControl: ValidatorControl, violatedConditions conditions: [Condition]) 17 | 18 | } 19 | 20 | 21 | public protocol ValidatorControl: AnyObject, Validatable { 22 | 23 | var isValid: Bool { get } 24 | var shouldAllowViolation: Bool { get set } 25 | var validateOnFocusLossOnly: Bool { get set } 26 | 27 | 28 | /// Delegate for the `ValidatorControl`. 29 | /// 30 | /// - Note: We recommend you specify your implementation as `weak`. 31 | var validatorDelegate: ValidatorControlDelegate? { get } 32 | 33 | } 34 | 35 | 36 | public extension ValidatorControl { 37 | 38 | var isValid: Bool { 39 | return validator.checkConditions(validatableText) == nil 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/EmailValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class EmailValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = EmailValidator() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testEmailValidator_Success() { 25 | // Given 26 | let testInput = "e_x.a+m-p_l.e@ex.example-example.ex.am" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testEmailValidator_Failure() { 37 | // Given 38 | let testInput = "example@" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/URLValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class URLValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = URLValidator() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testURLValidator_Success() { 25 | // Given 26 | let testInput = "http://www.example.com/?id=12345¶m=value" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testURLValidator_Failure() { 37 | // Given 38 | let testInput = "example.com" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/URLShorthandValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLShorthandValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class URLShorthandValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = URLShorthandValidator() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testURLShorthandValidator_Success() { 25 | // Given 26 | let testInput = "example.com" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testURLShorthandValidator_Failure() { 37 | // Given 38 | let testInput = "http://example" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/RangeValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class RangeValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = RangeValidator(configuration: ConfigurationSeeds.RangeSeeds.threeToThirteen) 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testRangeValidator_Success() { 25 | // Given 26 | let testInput = "1A2B3D4C5D" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testRangeValidator_Failure() { 37 | // Given 38 | let testInput = "1A2B3D4C5D6E" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Resources/pt-PT.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | localizable.strings 3 | FormValidatorSwift 4 | 5 | Created by Aaron McTavish on 12/01/2016. 6 | Copyright © 2016 ustwo. All rights reserved. 7 | */ 8 | 9 | "US2KeyConditionViolationRange" = "Por favor escreva no mínimo %d e no máximo %d caracteres"; 10 | "US2KeyConditionViolationNumeric" = "Por favor escreva apenas números"; 11 | "US2KeyConditionViolationAlphanumeric" = "Por favor escreva apenas letras e números"; 12 | "US2KeyConditionViolationAlphabetic" = "Por favor escreva apenas letras"; 13 | "US2KeyConditionViolationEmail" = "Por favor escreva um endereço de email válido no formato de exemplo@exemplo.com"; 14 | "US2KeyConditionViolationURL" = "Por favor escreva um endereço de web (URL) válido no formato de http(s)://www.exemplo.com"; 15 | "US2KeyConditionViolationShorthandURL" = "Por favor escreva um endereço de web (URL) válido no formato de www.exemplo.com"; 16 | "US2KeyConditionViolationPasswordStrength" = "Por favor escreva uma palavra-passe mais segura"; 17 | "US2KeyConditionViolationPostcodeUK" = "Por favor escreva um código postal válido"; 18 | "US2KeyConditionViolationPresent" = "Por favor escreva texto"; 19 | "US2KeyConditionViolationCreditCard" = "Enter a valid credit card number"; 20 | -------------------------------------------------------------------------------- /Sources/Conditions/RangeCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `RangeCondition` validates the length of a string. 14 | */ 15 | public struct RangeCondition: ConfigurableCondition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationRange", comment: "") 21 | 22 | public let regex = "" 23 | 24 | public var shouldAllowViolation = true 25 | 26 | public let configuration: RangeConfiguration 27 | 28 | 29 | // MARK: - Initializers 30 | 31 | public init(configuration: RangeConfiguration) { 32 | self.configuration = configuration 33 | } 34 | 35 | 36 | // MARK: - Check 37 | 38 | public func check(_ text: String?) -> Bool { 39 | guard let sourceText = text else { 40 | return false 41 | } 42 | 43 | return sourceText.count >= configuration.range.lowerBound && 44 | sourceText.count <= configuration.range.lowerBound.distance(to: configuration.range.upperBound) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/PasswordStrengthValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasswordStrengthValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class PasswordStrengthValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = PasswordStrengthValidator(configuration: ConfigurationSeeds.PasswordStrengthSeeds.veryStrong) 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testPasswordStrengthValidator_Success() { 25 | // Given 26 | let testInput = "F1@b9a_c12983y" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testPasswordStrengthValidator_Failure() { 37 | // Given 38 | let testInput = "Foo" 39 | let expectedResult: [Condition]? = validator.conditions 40 | 41 | // Test 42 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | 39 | // Dumb down quotes within code blocks that delimit strings instead of quotations 40 | // https://github.com/realm/jazzy/issues/714 41 | $("code q").replaceWith(function () { 42 | return ["\"", $(this).contents(), "\""]; 43 | }); 44 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Controls/ValidatorTextViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidatorTextViewTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by John Mann on 1/13/17. 6 | // Copyright © 2017 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | class ValidatorTextViewTests: XCTestCase { 14 | 15 | func testAlphabeticCondition_DefaultInit() { 16 | // Given 17 | let validatorTextView = ValidatorTextView(validator: AlphabeticValidator()) 18 | let expectShouldAllowViolation = false 19 | let expectValidateOnFocusLossOnly = false 20 | 21 | // When 22 | let actualShouldAllowViolation = validatorTextView.shouldAllowViolation 23 | let actualValidateOnFocusLossOnly = validatorTextView.validateOnFocusLossOnly 24 | 25 | // Test 26 | XCTAssertEqual(actualShouldAllowViolation, 27 | expectShouldAllowViolation, 28 | "Expected shouldAllowViolation to be: \(expectShouldAllowViolation) but found: \(actualShouldAllowViolation)") 29 | 30 | // Test 31 | XCTAssertEqual(actualValidateOnFocusLossOnly, 32 | expectValidateOnFocusLossOnly, 33 | "Expected validateOnFocusLossOnly to be: \(expectValidateOnFocusLossOnly) but found: \(actualValidateOnFocusLossOnly)") 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Controls/ValidatorTextFieldTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidatorTextFieldTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by John Mann on 1/13/17. 6 | // Copyright © 2017 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | class ValidatorTextFieldTests: XCTestCase { 14 | 15 | func testAlphabeticCondition_DefaultInit() { 16 | // Given 17 | let validatorTextField = ValidatorTextField(validator: AlphabeticValidator()) 18 | let expectShouldAllowViolation = false 19 | let expectValidateOnFocusLossOnly = false 20 | 21 | // When 22 | let actualShouldAllowViolation = validatorTextField.shouldAllowViolation 23 | let actualValidateOnFocusLossOnly = validatorTextField.validateOnFocusLossOnly 24 | 25 | // Test 26 | XCTAssertEqual(actualShouldAllowViolation, 27 | expectShouldAllowViolation, 28 | "Expected shouldAllowViolation to be: \(expectShouldAllowViolation) but found: \(actualShouldAllowViolation)") 29 | 30 | // Test 31 | XCTAssertEqual(actualValidateOnFocusLossOnly, 32 | expectValidateOnFocusLossOnly, 33 | "Expected validateOnFocusLossOnly to be: \(expectValidateOnFocusLossOnly) but found: \(actualValidateOnFocusLossOnly)") 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | 39 | // Dumb down quotes within code blocks that delimit strings instead of quotations 40 | // https://github.com/realm/jazzy/issues/714 41 | $("code q").replaceWith(function () { 42 | return ["\"", $(this).contents(), "\""]; 43 | }); 44 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/PresentConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresentConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class PresentConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let condition = PresentCondition() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testPresentCondition_Success() { 25 | // Given 26 | let testInput = "Foo" 27 | let expectedResult = true 28 | 29 | // Test 30 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testPresentCondition_Empty_Failure() { 37 | // Given 38 | let testInput = "" 39 | let expectedResult = false 40 | 41 | // Test 42 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | func testPresentCondition_Nil_Failure() { 46 | // Given 47 | let testInput: String? = nil 48 | let expectedResult = false 49 | 50 | // Test 51 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Example/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Sources/Configurations/PasswordStrengthConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasswordStrengthConfiguration.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// The strength required for the password. The strength is measured on five simple criteria - lower case characters, upper case characters, numeric characters, special characters, and is more than 8 characters long. Each of these matched criteria moves the password strength of the string up one strength. Not having 8 character minimum reduces the string by one strength level. 13 | public enum PasswordStrength: Int { 14 | case veryWeak, weak, medium, strong, veryStrong 15 | } 16 | 17 | 18 | /// Stores configuration for `PasswordStrengthCondition`. 19 | public struct PasswordStrengthConfiguration: Configuration { 20 | 21 | 22 | // MARK: - Properties 23 | 24 | /// Minimum strength required to be considered valid. 25 | public let requiredStrength: PasswordStrength 26 | 27 | 28 | // MARK: - Initializers 29 | 30 | public init() { 31 | self.init(requiredStrength: .veryStrong) 32 | } 33 | 34 | /// Initializes a `PasswordStrengthConfiguration`. 35 | /// 36 | /// - Parameter requiredStrength: Minimum strength required to be considered valid. 37 | public init(requiredStrength: PasswordStrength) { 38 | self.requiredStrength = requiredStrength 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/OptionalValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 10/04/2018. 6 | // Copyright © 2018 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class OptionalValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = OptionalValidator(conditions: [AlphabeticCondition()]) 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testOptionalValidator_Success_PrimaryAndConditionsMet() { 25 | // Given 26 | let testInput = "Foo" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | func testOptionalValidator_Success_PrimaryNotMet() { 34 | // Given 35 | let testInput = "" 36 | let expectedResult: [Condition]? = nil 37 | 38 | // Test 39 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 40 | } 41 | 42 | 43 | // MARK: - Test Failure 44 | 45 | func testOptionalValidator_Failure() { 46 | // Given 47 | let testInput = "Foo123" 48 | let expectedResult: [Condition]? = validator.conditions 49 | 50 | // Test 51 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Example/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 4.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | .DS_Store 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | -------------------------------------------------------------------------------- /Sources/Resources/Localization.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Localization.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * Convenience handler for localizating built in conditions. 14 | */ 15 | internal struct StringLocalization { 16 | 17 | 18 | static let sharedInstance = StringLocalization() 19 | 20 | 21 | private let resourceBundle: Bundle 22 | 23 | init() { 24 | let frameworkBundle = Bundle(for: DummyClass.self) 25 | 26 | if let bundleURL = frameworkBundle.resourceURL?.appendingPathComponent("FormValidatorSwift.bundle"), 27 | let resourceBundle = Bundle(url: bundleURL) { 28 | 29 | // Installed using CocoaPods 30 | self.resourceBundle = resourceBundle 31 | 32 | } else { 33 | self.resourceBundle = frameworkBundle 34 | } 35 | } 36 | 37 | 38 | /** 39 | Localizes a string based on the `Localizeable.strings` file within the framework. 40 | - parameter key: Lookup value for the strings table. 41 | - parameter comment: Comment value for the strings table. 42 | - returns: Localized string. 43 | */ 44 | func localizedString(_ key: String, comment: String) -> String { 45 | 46 | return NSLocalizedString(key, tableName: "Localizable", bundle: resourceBundle, comment: comment) 47 | } 48 | 49 | } 50 | 51 | 52 | // Use `DummyClass` to be able to get the right `NSBundle`. This is currently not possible with a `struct` in Swift 2.1. 53 | private class DummyClass: NSObject { } 54 | -------------------------------------------------------------------------------- /Sources/Conditions/NumericCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `NumericCondition` checks a string for numbers. 14 | */ 15 | public struct NumericCondition: ConfigurableCondition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationNumeric", comment: "") 21 | 22 | public let regex: String 23 | 24 | public var shouldAllowViolation = true 25 | 26 | public let configuration: NumericConfiguration 27 | 28 | 29 | // MARK: - Initializers 30 | 31 | public init(configuration: NumericConfiguration) { 32 | self.configuration = configuration 33 | 34 | let regexNumbers = configuration.allowsUnicode ? "\\p{Nd}" : "0-9" 35 | let regexWhiteSpace = configuration.allowsWhitespace ? "\\s" : "" 36 | 37 | regex = "[\(regexNumbers)\(regexWhiteSpace)]" 38 | } 39 | 40 | 41 | // MARK: - Check 42 | 43 | public func check(_ text: String?) -> Bool { 44 | guard let sourceText = text, 45 | !sourceText.isEmpty, 46 | let regExpression = try? NSRegularExpression(pattern: regex, options: .caseInsensitive) else { 47 | 48 | return false 49 | } 50 | 51 | return regExpression.numberOfMatches(in: sourceText, options: [], range: NSRange(location: 0, length: sourceText.count)) == sourceText.count 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Validators/OptionalValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 10/04/2018. 6 | // Copyright © 2018 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// The `OptionalValidator` has two stages of conditions. First the `primaryCondition` is checked. If it is met, then the subsequent `condition` are checked for violations. If the `primaryCondition` is not met, then *no* violation is returned. 13 | /// 14 | /// As an example, say you only want violations to show if there is text in the field, but otherwise ignore it as it is an optional field in a form. In this case you would use a `PresentCondition` as the `primaryCondition` and your subsequent validation requirements as the `conditions`. 15 | public struct OptionalValidator: Validator { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var primaryCondition: Condition 21 | public var conditions: [Condition] 22 | 23 | 24 | // MARK: - Initializers 25 | 26 | public init() { 27 | primaryCondition = PresentCondition() 28 | conditions = [PresentCondition()] 29 | } 30 | 31 | public init(conditions: [Condition], primaryCondition: Condition = PresentCondition()) { 32 | self.primaryCondition = primaryCondition 33 | self.conditions = conditions 34 | } 35 | 36 | 37 | // MARK: - Validity 38 | 39 | public func checkConditions(_ text: String?) -> [Condition]? { 40 | if primaryCondition.check(text) { 41 | let violations = conditions.filter { !($0.check(text)) } 42 | 43 | return violations.isEmpty ? nil : violations 44 | } 45 | 46 | return nil 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Conditions/AlphabeticCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphabeticCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 12/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `AlphabeticCondition` checks a string for occurrences of letters. 14 | */ 15 | public struct AlphabeticCondition: ConfigurableCondition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationAlphabetic", comment: "") 21 | 22 | public let regex: String 23 | 24 | public var shouldAllowViolation = true 25 | 26 | public let configuration: AlphabeticConfiguration 27 | 28 | 29 | // MARK: - Initializers 30 | 31 | public init(configuration: AlphabeticConfiguration) { 32 | self.configuration = configuration 33 | 34 | let regexLetters = configuration.allowsUnicode ? "\\p{L}" : "a-zA-Z" 35 | let regexWhiteSpace = configuration.allowsWhitespace ? "\\s" : "" 36 | 37 | regex = "[\(regexLetters)\(regexWhiteSpace)]" 38 | } 39 | 40 | 41 | // MARK: - Check 42 | 43 | public func check(_ text: String?) -> Bool { 44 | guard let sourceText = text, 45 | !sourceText.isEmpty, 46 | let regExpression = try? NSRegularExpression(pattern: regex, options: .caseInsensitive) else { 47 | 48 | return false 49 | } 50 | 51 | return regExpression.numberOfMatches(in: sourceText, options: [], range: NSRange(location: 0, length: sourceText.count)) == sourceText.count 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Example/iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ### test 19 | ``` 20 | fastlane test 21 | ``` 22 | Runs tests on the primary platforms and configurations 23 | ### verify 24 | ``` 25 | fastlane verify 26 | ``` 27 | Runs tests 28 | ### build 29 | ``` 30 | fastlane build 31 | ``` 32 | Builds scheme 33 | ### upload_cov 34 | ``` 35 | fastlane upload_cov 36 | ``` 37 | Upload code coverage reports (if running on CI) 38 | ### update_docs 39 | ``` 40 | fastlane update_docs 41 | ``` 42 | Updates the GitHub Pages documentation 43 | ### bump_version 44 | ``` 45 | fastlane bump_version 46 | ``` 47 | Bumps the version number of the project and podspec 48 | 49 | This action does the following: 50 | 51 | 52 | 53 | - Ensures a clean git status 54 | 55 | - Increment the version number (Project & Podspec) 56 | 57 | - Lints the CocoaPods Library 58 | 59 | - Commit and push the version bump 60 | 61 | - Creates a GitHub Release and git tag 62 | 63 | - Lints the CocoaPods Spec 64 | 65 | 66 | 67 | This action does NOT do the following: 68 | 69 | 70 | 71 | - Add the changelog notes to the GitHub release 72 | 73 | - Submit the updated podspec to CocoaPods 74 | 75 | ---- 76 | 77 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 78 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 79 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 80 | -------------------------------------------------------------------------------- /Sources/Conditions/AlphanumericCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlphanumericCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `AlphanumericCondition` checks a string for occurrences of letters and/or numbers. 14 | */ 15 | public struct AlphanumericCondition: ConfigurableCondition { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationAlphanumeric", comment: "") 21 | 22 | public let regex: String 23 | 24 | public var shouldAllowViolation = true 25 | 26 | public let configuration: AlphanumericConfiguration 27 | 28 | 29 | // MARK: - Initializers 30 | 31 | public init(configuration: AlphanumericConfiguration) { 32 | self.configuration = configuration 33 | 34 | let regexLettersNumbers = configuration.allowsUnicode ? "\\p{L}\\p{N}" : "a-zA-Z0-9" 35 | let regexWhiteSpace = configuration.allowsWhitespace ? "\\s" : "" 36 | 37 | regex = "[\(regexLettersNumbers)\(regexWhiteSpace)]" 38 | } 39 | 40 | 41 | // MARK: - Check 42 | 43 | public func check(_ text: String?) -> Bool { 44 | guard let sourceText = text, 45 | !sourceText.isEmpty, 46 | let regExpression = try? NSRegularExpression(pattern: regex, options: .caseInsensitive) else { 47 | 48 | return false 49 | } 50 | 51 | return regExpression.numberOfMatches(in: sourceText, options: [], range: NSRange(location: 0, length: sourceText.count)) == sourceText.count 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Example/macOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 06/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | // MARK: - Properties 16 | 17 | lazy var window: NSWindow = { 18 | let result = NSWindow(contentRect: NSRect(x: 0, 19 | y: 0, 20 | width: NSScreen.main!.frame.width / 2.0, 21 | height: NSScreen.main!.frame.height / 2.0), 22 | styleMask: [NSWindow.StyleMask.titled, NSWindow.StyleMask.miniaturizable, NSWindow.StyleMask.resizable, NSWindow.StyleMask.closable], 23 | backing: .buffered, 24 | defer: false) 25 | 26 | result.title = "New Window" 27 | result.isOpaque = false 28 | result.center() 29 | result.isMovableByWindowBackground = true 30 | result.backgroundColor = NSColor.white 31 | 32 | return result 33 | }() 34 | 35 | 36 | // MARK: - NSApplicationDelegate 37 | 38 | func applicationWillFinishLaunching(_ notification: Notification) { 39 | if #available(macOS 10.14, *) { 40 | NSApp.appearance = NSAppearance(named: .aqua) 41 | } 42 | } 43 | 44 | func applicationDidFinishLaunching(_ notification: Notification) { 45 | let viewController = FormViewController() 46 | 47 | window.contentViewController = viewController 48 | 49 | window.makeKeyAndOrderFront(self) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/Logic/NotConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class NotConditionTests: XCTestCase { 15 | 16 | let originalCondition = RangeCondition(configuration: ConfigurationSeeds.RangeSeeds.zeroToFour) 17 | 18 | 19 | // MARK: - Test Success 20 | 21 | func testNotCondition_Success() { 22 | // Given 23 | let testInput = "" 24 | var condition = NotCondition(condition: originalCondition) 25 | let expectedResult = false 26 | 27 | // When 28 | condition.localizedViolationString = "Must not be between 0 through 4." 29 | 30 | // Initial Tests 31 | AssertCondition(originalCondition, testInput: testInput, expectedResult: !expectedResult) 32 | 33 | // Test 34 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 35 | } 36 | 37 | 38 | // MARK: - Test Failure 39 | 40 | func testNotCondition_Failure() { 41 | // Given 42 | let testInput = "1A234" 43 | var condition = NotCondition(condition: originalCondition) 44 | let expectedResult = true 45 | 46 | // When 47 | condition.localizedViolationString = "Must not be between 0 through 4." 48 | 49 | // Initial Tests 50 | AssertCondition(originalCondition, testInput: testInput, expectedResult: !expectedResult) 51 | 52 | // Test 53 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Protocols/Validator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Validator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * `Validator` is a holder for conditions of type `Condition`. 14 | * The validator checks for violation of each condition. Returned will be a collection of 15 | * violated conditions or `nil` if the string to check is correct or no condition was added. 16 | */ 17 | public protocol Validator { 18 | 19 | /// Initializer that creates a condition based on default values. 20 | init() 21 | 22 | /// Conditions to use when validating text. 23 | var conditions: [Condition] { get set } 24 | 25 | /** 26 | Checks `text` for violation of each condition. 27 | - parameter text: `String` to check. 28 | - returns: An array of conditions that were violated by `text`. If no conditions were violated then `nil` is returned. 29 | */ 30 | func checkConditions(_ text: String?) -> [Condition]? 31 | 32 | /** 33 | Removes all conditions of `conditionClass` type. 34 | - parameter conditionClass: `Type` of condition to remove. 35 | */ 36 | mutating func removeConditionOfClass(_ conditionClass: T.Type) 37 | 38 | } 39 | 40 | 41 | // Default implementation for `addCondition`, `checkConditions`, and `removeConditionOfClass`. 42 | public extension Validator { 43 | 44 | func checkConditions(_ text: String?) -> [Condition]? { 45 | let violations = conditions.filter { !($0.check(text)) } 46 | 47 | return violations.isEmpty ? nil : violations 48 | } 49 | 50 | mutating func removeConditionOfClass(_ conditionClass: T.Type) { 51 | conditions = conditions.filter { !($0 is T) } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var searchIndex = lunr(function() { 3 | this.ref('url'); 4 | this.field('name'); 5 | }); 6 | 7 | var $typeahead = $('[data-typeahead]'); 8 | var $form = $typeahead.parents('form'); 9 | var searchURL = $form.attr('action'); 10 | 11 | function displayTemplate(result) { 12 | return result.name; 13 | } 14 | 15 | function suggestionTemplate(result) { 16 | var t = '
'; 17 | t += '' + result.name + ''; 18 | if (result.parent_name) { 19 | t += '' + result.parent_name + ''; 20 | } 21 | t += '
'; 22 | return t; 23 | } 24 | 25 | $typeahead.one('focus', function() { 26 | $form.addClass('loading'); 27 | 28 | $.getJSON(searchURL).then(function(searchData) { 29 | $.each(searchData, function (url, doc) { 30 | searchIndex.add({url: url, name: doc.name}); 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3 37 | }, 38 | { 39 | limit: 10, 40 | display: displayTemplate, 41 | templates: { suggestion: suggestionTemplate }, 42 | source: function(query, sync) { 43 | var results = searchIndex.search(query).map(function(result) { 44 | var doc = searchData[result.ref]; 45 | doc.url = result.ref; 46 | return doc; 47 | }); 48 | sync(results); 49 | } 50 | } 51 | ); 52 | $form.removeClass('loading'); 53 | $typeahead.trigger('focus'); 54 | }); 55 | }); 56 | 57 | var baseURL = searchURL.slice(0, -"search.json".length); 58 | 59 | $typeahead.on('typeahead:select', function(e, result) { 60 | window.location = baseURL + result.url; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/CreditCardTypeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCardTypeTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 21/12/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class CreditCardTypeTests: XCTestCase { 15 | 16 | 17 | // MARK: - Configuration 18 | 19 | private struct TestableCreditCardType { 20 | let cardType: CreditCardType 21 | let expectedDescription: String 22 | let identifier: String 23 | } 24 | 25 | private let testCases: [TestableCreditCardType] = [ 26 | TestableCreditCardType(cardType: .all, 27 | expectedDescription: "American Express, Diners Club, Discover, JCB, Maestro, Master Card, VISA", 28 | identifier: "All"), 29 | TestableCreditCardType(cardType: [.americanExpress, .visa], 30 | expectedDescription: "American Express, VISA", 31 | identifier: "Some"), 32 | TestableCreditCardType(cardType: [], 33 | expectedDescription: "", 34 | identifier: "None") 35 | ] 36 | 37 | 38 | // MARK: - Test Properties 39 | 40 | func testCreditCardType_Description() { 41 | for testCase in testCases { 42 | // When 43 | let actualResult = testCase.cardType.description 44 | 45 | // Test 46 | XCTAssertEqual(testCase.expectedDescription, 47 | actualResult, 48 | "For case: \(testCase.identifier) expected the description to be: \(testCase.expectedDescription) but found: \(actualResult)") 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /docs/docsets/FormValidatorSwift.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var searchIndex = lunr(function() { 3 | this.ref('url'); 4 | this.field('name'); 5 | }); 6 | 7 | var $typeahead = $('[data-typeahead]'); 8 | var $form = $typeahead.parents('form'); 9 | var searchURL = $form.attr('action'); 10 | 11 | function displayTemplate(result) { 12 | return result.name; 13 | } 14 | 15 | function suggestionTemplate(result) { 16 | var t = '
'; 17 | t += '' + result.name + ''; 18 | if (result.parent_name) { 19 | t += '' + result.parent_name + ''; 20 | } 21 | t += '
'; 22 | return t; 23 | } 24 | 25 | $typeahead.one('focus', function() { 26 | $form.addClass('loading'); 27 | 28 | $.getJSON(searchURL).then(function(searchData) { 29 | $.each(searchData, function (url, doc) { 30 | searchIndex.add({url: url, name: doc.name}); 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3 37 | }, 38 | { 39 | limit: 10, 40 | display: displayTemplate, 41 | templates: { suggestion: suggestionTemplate }, 42 | source: function(query, sync) { 43 | var results = searchIndex.search(query).map(function(result) { 44 | var doc = searchData[result.ref]; 45 | doc.url = result.ref; 46 | return doc; 47 | }); 48 | sync(results); 49 | } 50 | } 51 | ); 52 | $form.removeClass('loading'); 53 | $typeahead.trigger('focus'); 54 | }); 55 | }); 56 | 57 | var baseURL = searchURL.slice(0, -"search.json".length); 58 | 59 | $typeahead.on('typeahead:select', function(e, result) { 60 | window.location = baseURL + result.url; 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /Example/macOS/FormViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormViewController.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 06/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | 12 | import FormValidatorSwift 13 | 14 | 15 | final class FormViewController: NSViewController { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | var form = ControlForm() 21 | 22 | fileprivate var underlyingView: FormView { 23 | if let myView = view as? FormView { 24 | return myView 25 | } 26 | 27 | let newView = FormView() 28 | view = newView 29 | return newView 30 | } 31 | 32 | 33 | // MARK: - View Lifecycle 34 | 35 | override func loadView() { 36 | view = FormView() 37 | } 38 | 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | form.addEntry(underlyingView.titleEntry.textField) 43 | form.addEntry(underlyingView.nameEntry.textField) 44 | form.addEntry(underlyingView.emailEntry.textField) 45 | 46 | underlyingView.submitButton.action = #selector(FormViewController.submitButtonPressed(_:)) 47 | } 48 | 49 | 50 | // MARK: - Control Actions 51 | 52 | @objc func submitButtonPressed(_ sender: NSButton) { 53 | let alertMessage: String 54 | if form.isValid { 55 | alertMessage = NSLocalizedString("Success: Your data has been submitted!", comment: "") 56 | } else { 57 | alertMessage = NSLocalizedString("Error: Please correct your entries in the form.", comment: "") 58 | } 59 | 60 | let alert = NSAlert() 61 | alert.alertStyle = .critical 62 | alert.messageText = alertMessage 63 | 64 | alert.beginSheetModal(for: NSApplication.shared.mainWindow!, completionHandler: nil) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/CreditCardValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCardValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Onur Ersel on 2016-11-02. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class CreditCardValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let validator = CreditCardValidator() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testCreditCardValidator_Success() { 25 | // Given 26 | let testInput = "376031710126369" 27 | let expectedResult: [Condition]? = nil 28 | 29 | // Test 30 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | func testCreditCardValidator_GetCardType_Success() { 34 | // Given 35 | let testInput = "5300000000000000" 36 | let expectedResult: CreditCardType = [.mastercard] 37 | 38 | let exp = self.expectation(description: "Card types should be valid") 39 | 40 | validator.validCardTypes(for: testInput) { (validCardTypes) in 41 | XCTAssertEqual(validCardTypes, expectedResult, "Card type of \(testInput) should be \(expectedResult), but returned \(validCardTypes).") 42 | exp.fulfill() 43 | } 44 | 45 | self.waitForExpectations(timeout: 3, handler: nil) 46 | } 47 | 48 | 49 | // MARK: - Test Failure 50 | 51 | func testCreditCardValidator_Failure() { 52 | // Given 53 | let testInput = "3760 a317" 54 | let expectedResult: [Condition]? = validator.conditions 55 | 56 | // Test 57 | AssertValidator(validator, testInput: testInput, expectedResult: expectedResult) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Conditions/CreditCardCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCardCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Onur Ersel on 02/11/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `CreditCardCondition` checks a string for a credit card number. 14 | */ 15 | public struct CreditCardCondition: ConfigurableCondition { 16 | 17 | // MARK: - Properties 18 | 19 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationCreditCard", comment: "") 20 | 21 | public var regex: String { 22 | return configuration.cardType.regex 23 | } 24 | 25 | public var shouldAllowViolation = true 26 | 27 | public var configuration: CreditCardConfiguration 28 | 29 | 30 | // MARK: - Initializers 31 | 32 | public init(configuration: CreditCardConfiguration) { 33 | self.configuration = configuration 34 | } 35 | 36 | 37 | // MARK: - Check 38 | 39 | /** 40 | Checks if the string is a valid credit card number, after removes all whitespace. 41 | */ 42 | public func check(_ text: String?) -> Bool { 43 | guard let sourceText = text, 44 | let regExp = try? NSRegularExpression(pattern: self.regex, options: .caseInsensitive) else { 45 | return false 46 | } 47 | 48 | let sourceTextNs = sourceText as NSString 49 | let trimmedText = sourceTextNs.replacingOccurrences(of: "\\D", with: "", options: .regularExpression, range: NSRange(location: 0, length: sourceTextNs.length)) as String 50 | 51 | return check(trimmedText, withRegex: regExp) 52 | } 53 | 54 | public func check(_ trimmedText: String, withRegex regExp: NSRegularExpression) -> Bool { 55 | return regExp.firstMatch(in: trimmedText, options: [], range: NSRange(location: 0, length: trimmedText.count)) != nil 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/URLConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class URLConditionTests: XCTestCase { 15 | 16 | // MARK: - Properties 17 | 18 | let condition = URLCondition() 19 | 20 | 21 | // MARK: - Test Success 22 | 23 | func testURLCondition_Success() { 24 | // Given 25 | let testInput = "http://www.example.com/?id=12345¶m=value" 26 | let expectedResult = true 27 | 28 | // Test 29 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 30 | } 31 | 32 | 33 | // MARK: - Test Failure 34 | 35 | func testURLCondition_NoDomain_Failure() { 36 | // Given 37 | let testInput = "http://example" 38 | let expectedResult = false 39 | 40 | // Test 41 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 42 | } 43 | 44 | func testURLCondition_NoScheme_Failure() { 45 | // Given 46 | let testInput = "www.example.com" 47 | let expectedResult = false 48 | 49 | // Test 50 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 51 | } 52 | 53 | func testURLCondition_NonHTTPScheme_Failure() { 54 | // Given 55 | let testInput = "ftp://www.example.com" 56 | let expectedResult = false 57 | 58 | // Test 59 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 60 | } 61 | 62 | func testURLCondition_Nil_Failure() { 63 | // Given 64 | let testInput: String? = nil 65 | let expectedResult = false 66 | 67 | // Test 68 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /Sources/Validators/CreditCardValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCardValidator.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Onur Ersel on 02/11/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * The `CreditCardValidator` contains an `CreditCardCondition`. A valid string is a credit card number. 13 | * - seealso: `CreditCardCondition` 14 | */ 15 | public struct CreditCardValidator: ConfigurableValidator { 16 | 17 | 18 | // MARK: - Properties 19 | 20 | public var conditions: [Condition] 21 | 22 | 23 | // MARK: - Initializers 24 | 25 | public init(configuration: CreditCardConfiguration) { 26 | conditions = [CreditCardCondition(configuration: configuration)] 27 | } 28 | 29 | 30 | // MARK: - Validity 31 | 32 | /** 33 | Returns valid card types for a credit card number asynchronously. 34 | 35 | - Parameters: 36 | - creditCardNumber: Credit card number. 37 | - completion: Completion callback, returns valid card types for given number. 38 | */ 39 | public func validCardTypes(`for` creditCardNumber: String, completion : @escaping (CreditCardType) -> Void) { 40 | 41 | DispatchQueue.global().async { 42 | 43 | var validCardTypes: CreditCardType = [] 44 | defer { 45 | DispatchQueue.main.async { 46 | completion(validCardTypes) 47 | } 48 | } 49 | 50 | let creditCardCondition = CreditCardCondition() 51 | let trimmedCardNumber = String(creditCardNumber.filter { $0 != " " }) 52 | 53 | for cardType in CreditCardType.allArray { 54 | if let regex = try? NSRegularExpression(pattern: cardType.regex, options: .caseInsensitive), 55 | creditCardCondition.check(trimmedCardNumber, withRegex: regex) { 56 | validCardTypes.insert(cardType) 57 | } 58 | } 59 | 60 | } 61 | 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/Configurations/PostcodeConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostcodeConfiguration.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 04/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// Countries that are supported by `PostcodeCondition`. Each postcode is an ISO 3166-1 alpha-3 country code. There is a `regex` property that returns the regex for validating that country's postcode. 13 | public enum PostcodeCountries: String { 14 | 15 | case sweden = "SWE" 16 | case turkey = "TUR" 17 | case unitedKingdom = "GBR" 18 | case unitedStates = "USA" 19 | 20 | /// The regex for validating the country's postcode. 21 | public var regex: String { 22 | switch self { 23 | case .sweden: 24 | return "^(SE-)?[0-9]{3}\\s?[0-9]{2}$" 25 | case .turkey: 26 | return "^[0-9]{5}$" 27 | case .unitedKingdom: 28 | return "^([A-PR-UWYZa-pr-uwyz]([0-9]{1,2}|([A-HK-Ya-hk-y][0-9]|[A-HK-Ya-hk-y][0-9]([0-9]|[ABEHMNPRV-Yabehmnprv-y]))|[0-9][A-HJKS-UWa-hjks-uw])\\ {0,1}[0-9][ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}|([Gg][Ii][Rr]\\ 0[Aa][Aa])|([Ss][Aa][Nn]\\ {0,1}[Tt][Aa]1)|([Bb][Ff][Pp][Oo]\\ {0,1}([Cc]\\/[Oo]\\ )?[0-9]{1,4})|(([Aa][Ss][Cc][Nn]|[Bb][Bb][Nn][Dd]|[BFSbfs][Ii][Qq][Qq]|[Pp][Cc][Rr][Nn]|[Ss][Tt][Hh][Ll]|[Tt][Dd][Cc][Uu]|[Tt][Kk][Cc][Aa])\\ {0,1}1[Zz][Zz]))$" 29 | case .unitedStates: 30 | return "^[0-9]{5}(?:[-|\\s][0-9]{4})?$" 31 | } 32 | } 33 | 34 | public static let allValues: [PostcodeCountries] = [.sweden, .turkey, .unitedKingdom, .unitedStates] 35 | } 36 | 37 | 38 | /// Stores configuration for `PostcodeCondition`. 39 | public struct PostcodeConfiguration: Configuration { 40 | 41 | 42 | // MARK: - Properties 43 | 44 | public var country: PostcodeCountries 45 | 46 | 47 | // MARK: - Initializers 48 | 49 | public init() { 50 | self.init(country: .unitedKingdom) 51 | } 52 | 53 | public init(country: PostcodeCountries = .unitedKingdom) { 54 | self.country = country 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Validators/CompositeValidatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositeValidatorTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class CompositeValidatorTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let firstValidator = RangeValidator(configuration: ConfigurationSeeds.RangeSeeds.zeroToFour) 20 | let secondValidator = AlphanumericValidator() 21 | 22 | 23 | // MARK: - Test Success 24 | 25 | func testCompositeValidator_Success() { 26 | // Given 27 | let testInput = "1A23" 28 | let validator = CompositeValidator(validators: [firstValidator, secondValidator]) 29 | let expectedResult: [Condition]? = nil 30 | 31 | // When 32 | let actualResult = validator.checkConditions(testInput) 33 | 34 | // Initial Tests 35 | XCTAssertNil(firstValidator.checkConditions(testInput)) 36 | XCTAssertNil(secondValidator.checkConditions(testInput)) 37 | 38 | // Test 39 | XCTAssertNil(actualResult, "The `\(type(of: validator))` should respond with \(expectedResult.debugDescription) and but received \(actualResult.debugDescription).") 40 | } 41 | 42 | 43 | // MARK: - Test Failure 44 | 45 | func testCompositeValidator_Failure() { 46 | // Given 47 | let testInput = "1A234" 48 | let validator = CompositeValidator(validators: [firstValidator, secondValidator]) 49 | let expectedResult: [Condition]? = nil 50 | 51 | // When 52 | let actualResult = validator.checkConditions(testInput) 53 | 54 | // Initial Tests 55 | XCTAssertNotNil(firstValidator.checkConditions(testInput)) 56 | XCTAssertNil(secondValidator.checkConditions(testInput)) 57 | 58 | // Test 59 | XCTAssertNotNil(actualResult, "The `\(type(of: validator))` should respond with \(expectedResult.debugDescription) and but received \(actualResult.debugDescription).") 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/RangeConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class RangeConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let condition = RangeCondition(configuration: ConfigurationSeeds.RangeSeeds.threeToThirteen) 20 | 21 | 22 | // MARK: - Test Initializers 23 | 24 | func testRangeCondition_DefaultInit() { 25 | // Given 26 | let condition = RangeCondition() 27 | let expectedRange = 0..<1 28 | 29 | // When 30 | let actualRange = condition.configuration.range 31 | 32 | // Test 33 | XCTAssertEqual(actualRange, 34 | expectedRange, 35 | "Expected range to be: \(expectedRange) but found: \(actualRange)") 36 | } 37 | 38 | 39 | // MARK: - Test Success 40 | 41 | func testRangeCondition_Success() { 42 | // Given 43 | let testInput = "1A2B3D4C5D" 44 | let expectedResult = true 45 | 46 | // Test 47 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 48 | } 49 | 50 | 51 | // MARK: - Test Failure 52 | 53 | func testRangeCondition_Start_Failure() { 54 | // Given 55 | let testInput = "1A" 56 | let expectedResult = false 57 | 58 | // Test 59 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 60 | } 61 | 62 | func testRangeCondition_Length_Failure() { 63 | // Given 64 | let testInput = "1A2B3D4C5D6E" 65 | let expectedResult = false 66 | 67 | // Test 68 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 69 | } 70 | 71 | func testRangeCondition_Nil_Failure() { 72 | // Given 73 | let testInput: String? = nil 74 | let expectedResult = false 75 | 76 | // Test 77 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Example/iOS/FormViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import FormValidatorSwift 10 | import UIKit 11 | 12 | 13 | final class FormViewController: UIViewController { 14 | 15 | 16 | // MARK: - Properties 17 | 18 | var form = ControlForm() 19 | 20 | fileprivate var underlyingView: FormView { 21 | if let myView = view as? FormView { 22 | return myView 23 | } 24 | 25 | let newView = FormView() 26 | view = newView 27 | return newView 28 | } 29 | 30 | 31 | // MARK: - View Lifecycle 32 | 33 | override func loadView() { 34 | view = FormView() 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | form.addEntry(underlyingView.titleEntry.textField) 41 | form.addEntry(underlyingView.nameEntry.textField) 42 | form.addEntry(underlyingView.emailEntry.textField) 43 | 44 | underlyingView.submitButton.addTarget(self, action: #selector(FormViewController.submitButtonPressed(_:)), for: .touchUpInside) 45 | } 46 | 47 | 48 | // MARK: - Control Actions 49 | 50 | @objc func submitButtonPressed(_ sender: UIButton) { 51 | let alertTitle: String 52 | let alertMessage: String 53 | if form.isValid { 54 | alertTitle = NSLocalizedString("Success", comment: "") 55 | alertMessage = NSLocalizedString("Your data has been submitted!", comment: "") 56 | } else { 57 | alertTitle = NSLocalizedString("Error", comment: "") 58 | alertMessage = NSLocalizedString("Please correct your entries in the form.", comment: "") 59 | } 60 | 61 | let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert) 62 | alertController.popoverPresentationController?.sourceView = sender 63 | 64 | let doneAction = UIAlertAction(title: NSLocalizedString("Done", comment: ""), style: .default, handler: nil) 65 | alertController.addAction(doneAction) 66 | 67 | present(alertController, animated: true, completion: nil) 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/EmailConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class EmailConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let condition = EmailCondition() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testEmailCondition_Success() { 25 | // Given 26 | let testInput = "e_x.a+m-p_l.e@ex.example-example.ex.am" 27 | let expectedResult = true 28 | 29 | // Test 30 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | 34 | // MARK: - Test Failure 35 | 36 | func testEmailCondition_NoAt_Failure() { 37 | // Given 38 | let testInput = "example" 39 | let expectedResult = false 40 | 41 | // Test 42 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 43 | } 44 | 45 | func testEmailCondition_NoDomain_Failure() { 46 | // Given 47 | let testInput = "example@" 48 | let expectedResult = false 49 | 50 | // Test 51 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 52 | } 53 | 54 | func testEmailCondition_PartialDomain_Failure() { 55 | // Given 56 | let testInput = "example@example.ex." 57 | let expectedResult = false 58 | 59 | // Test 60 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 61 | } 62 | 63 | func testEmailCondition_Space_Failure() { 64 | // Given 65 | let testInput = "e xample@example.ex." 66 | let expectedResult = false 67 | 68 | // Test 69 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 70 | } 71 | 72 | func testEmailCondition_ReservedCharacters_Failure() { 73 | // Given 74 | let testInput = "e/xample@example.ex." 75 | let expectedResult = false 76 | 77 | // Test 78 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/URLShorthandConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLShorthandConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class URLShorthandConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let condition = URLShorthandCondition() 20 | 21 | 22 | // MARK: - Test Success 23 | 24 | func testURLShorthandCondition_FullURL_Success() { 25 | // Given 26 | let testInput = "http://www.example.com/?id=12345¶m=value" 27 | let expectedResult = true 28 | 29 | // Test 30 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 31 | } 32 | 33 | func testURLShorthandCondition_ShortURL_Success() { 34 | // Given 35 | let testInput = "example.com" 36 | let expectedResult = true 37 | 38 | // Test 39 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 40 | } 41 | 42 | 43 | // MARK: - Test Failure 44 | 45 | func testURLShorthandCondition_NoDomain_Failure() { 46 | // Given 47 | let testInput = "http://example" 48 | let expectedResult = false 49 | 50 | // Test 51 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 52 | } 53 | 54 | func testURLShorthandCondition_NoPath_Failure() { 55 | // Given 56 | let testInput = "http://" 57 | let expectedResult = false 58 | 59 | // Test 60 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 61 | } 62 | 63 | func testURLShorthandCondition_NonHTTPScheme_Failure() { 64 | // Given 65 | let testInput = "ftp://www.example.com" 66 | let expectedResult = false 67 | 68 | // Test 69 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 70 | } 71 | 72 | func testURLShorthandCondition_Nil_Failure() { 73 | // Given 74 | let testInput: String? = nil 75 | let expectedResult = false 76 | 77 | // Test 78 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - line_length 3 | - nesting 4 | opt_in_rules: 5 | - anyobject_protocol 6 | - array_init 7 | - attributes 8 | - closure_end_indentation 9 | - closure_spacing 10 | - conditional_returns_on_newline 11 | - contains_over_first_not_nil 12 | - convenience_type 13 | - empty_count 14 | - empty_string 15 | - explicit_init 16 | - fallthrough 17 | - fatal_error_message 18 | - file_header 19 | - first_where 20 | - implicit_return 21 | - implicitly_unwrapped_optional 22 | - number_separator 23 | - object_literal 24 | - overridden_super_call 25 | - private_outlet 26 | - prohibited_super_call 27 | - redundant_nil_coalescing 28 | - sorted_imports 29 | - switch_case_on_newline 30 | - unneeded_parentheses_in_closure_argument 31 | - yoda_condition 32 | 33 | excluded: 34 | - Pods 35 | - Tests 36 | - vendor 37 | 38 | attributes: 39 | always_on_same_line: 40 | - '@IBAction' 41 | - '@NSManaged' 42 | - '@nonobjc' 43 | - '@objc' 44 | 45 | file_header: 46 | required_pattern: | 47 | \/\/ 48 | \/\/ .*?\.swift 49 | \/\/ FormValidatorSwift 50 | \/\/ 51 | \/\/ Created by .*? on \d{1,2}\/\d{1,2}\/\d{4}\. 52 | \/\/ Copyright © \d{4} ustwo Fampany Ltd\. All rights reserved\. 53 | \/\/ 54 | 55 | line_length: 110 56 | trailing_whitespace: 57 | ignores_empty_lines: true 58 | ignores_comments: true 59 | type_body_length: 60 | - 300 # warning 61 | - 400 # error 62 | vertical_whitespace: 63 | max_empty_lines: 2 64 | 65 | custom_rules: 66 | # pragma mark style 67 | marks_empty_space: 68 | name: "Marks" 69 | regex: "(//MARK)" 70 | message: "There should be an empty space between // and the MARK." 71 | severity: warning 72 | 73 | marks_style: 74 | name: "Marks" 75 | regex: "(// MARK: -?[a-zA-Z0-9])" 76 | message: "Marks should follow the following structure: // MARK: - Comment." 77 | severity: warning 78 | 79 | # comments style 80 | comments_empty_space: 81 | name: "Comments" 82 | regex: "(//[a-zA-Z0-9])" 83 | match_kinds: 84 | - comment 85 | message: "There should be an empty space between // and the comment." 86 | severity: warning 87 | 88 | comments_empty_line_after: 89 | name: "Comments" 90 | regex: "([^\n]\n^ *[a-zA-Z0-9{])" 91 | match_kinds: 92 | - comment 93 | message: "There should be an empty line after a comment." 94 | severity: warning 95 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/Logic/OrConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OrConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class OrConditionTests: XCTestCase { 15 | 16 | let firstCondition = RangeCondition(configuration: ConfigurationSeeds.RangeSeeds.zeroToFour) 17 | let secondCondition = AlphanumericCondition() 18 | 19 | 20 | // MARK: - Test Initializers 21 | 22 | func testOrCondition_DefaultInit() { 23 | // Given 24 | let condition = OrCondition() 25 | let expectedCount = 1 26 | 27 | // When 28 | let actualCount = condition.conditions.count 29 | 30 | // Test 31 | XCTAssertEqual(actualCount, 32 | expectedCount, 33 | "Expected number of conditions to be: \(expectedCount) but found: \(actualCount)") 34 | } 35 | 36 | 37 | // MARK: - Test Success 38 | 39 | func testOrCondition_Success() { 40 | // Given 41 | let testInput = "" 42 | var condition = OrCondition(conditions: [firstCondition, secondCondition]) 43 | let expectedResult = true 44 | 45 | // When 46 | condition.localizedViolationString = "Min 0 Max 4 or must only contain alphanumeric" 47 | 48 | // Initial Tests 49 | AssertCondition(firstCondition, testInput: testInput, expectedResult: true) 50 | AssertCondition(secondCondition, testInput: testInput, expectedResult: false) 51 | 52 | // Test 53 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 54 | } 55 | 56 | 57 | // MARK: - Test Failure 58 | 59 | func testOrCondition_Failure() { 60 | // Given 61 | let testInput = "1A234?" 62 | var condition = OrCondition(conditions: [firstCondition, secondCondition]) 63 | let expectedResult = false 64 | 65 | // When 66 | condition.localizedViolationString = "Min 0 Max 4 or must only contain alphanumeric" 67 | 68 | // Initial Tests 69 | AssertCondition(firstCondition, testInput: testInput, expectedResult: false) 70 | AssertCondition(secondCondition, testInput: testInput, expectedResult: false) 71 | 72 | // Test 73 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Configurations/ConfigurationSeeds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurationSeeds.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 03/01/2017. 6 | // Copyright © 2017 ustwo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | /// Seed data for the various configurations. These are preconfigured configurations to use during testing. 15 | struct ConfigurationSeeds { 16 | 17 | struct AlphabeticSeeds { 18 | 19 | static let noUnicode_NoWhitespace = AlphabeticConfiguration(allowsUnicode: false, allowsWhitespace: false) 20 | static let noUnicode_Whitespace = AlphabeticConfiguration(allowsUnicode: false, allowsWhitespace: true) 21 | static let unicode_NoWhitespace = AlphabeticConfiguration(allowsUnicode: true, allowsWhitespace: false) 22 | static let unicode_Whitespace = AlphabeticConfiguration(allowsUnicode: true, allowsWhitespace: true) 23 | 24 | } 25 | 26 | struct AlphanumericSeeds { 27 | 28 | static let noUnicode_NoWhitespace = AlphanumericConfiguration(allowsUnicode: false, allowsWhitespace: false) 29 | static let noUnicode_Whitespace = AlphanumericConfiguration(allowsUnicode: false, allowsWhitespace: true) 30 | static let unicode_NoWhitespace = AlphanumericConfiguration(allowsUnicode: true, allowsWhitespace: false) 31 | static let unicode_Whitespace = AlphanumericConfiguration(allowsUnicode: true, allowsWhitespace: true) 32 | 33 | } 34 | 35 | struct NumericSeeds { 36 | 37 | static let noUnicode_NoWhitespace = NumericConfiguration(allowsUnicode: false, allowsWhitespace: false) 38 | static let noUnicode_Whitespace = NumericConfiguration(allowsUnicode: false, allowsWhitespace: true) 39 | static let unicode_NoWhitespace = NumericConfiguration(allowsUnicode: true, allowsWhitespace: false) 40 | static let unicode_Whitespace = NumericConfiguration(allowsUnicode: true, allowsWhitespace: true) 41 | 42 | } 43 | 44 | struct PasswordStrengthSeeds { 45 | 46 | static let veryStrong = PasswordStrengthConfiguration(requiredStrength: .veryStrong) 47 | static let veryWeak = PasswordStrengthConfiguration(requiredStrength: .veryWeak) 48 | 49 | } 50 | 51 | struct RangeSeeds { 52 | 53 | static let threeToThirteen = RangeConfiguration(range: 3..<13) 54 | static let zeroToFour = RangeConfiguration(range: 0..<4) 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/Logic/AndConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AndConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class AndConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let firstCondition = RangeCondition(configuration: ConfigurationSeeds.RangeSeeds.zeroToFour) 20 | let secondCondition = AlphanumericCondition() 21 | 22 | 23 | // MARK: - Test Initializers 24 | 25 | func testAndCondition_DefaultInit() { 26 | // Given 27 | let condition = AndCondition() 28 | let expectedCount = 1 29 | 30 | // When 31 | let actualCount = condition.conditions.count 32 | 33 | // Test 34 | XCTAssertEqual(actualCount, 35 | expectedCount, 36 | "Expected number of conditions to be: \(expectedCount) but found: \(actualCount)") 37 | } 38 | 39 | 40 | // MARK: - Test Success 41 | 42 | func testAndCondition_Success() { 43 | // Given 44 | let testInput = "1A23" 45 | var condition = AndCondition(conditions: [firstCondition, secondCondition]) 46 | let expectedResult = true 47 | 48 | // When 49 | condition.localizedViolationString = "Min 0 Max 4 or must only contain alphanumeric" 50 | 51 | // Initial Tests 52 | AssertCondition(firstCondition, testInput: testInput, expectedResult: true) 53 | AssertCondition(secondCondition, testInput: testInput, expectedResult: true) 54 | 55 | // Test 56 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 57 | } 58 | 59 | 60 | // MARK: - Test Failure 61 | 62 | func testAndCondition_Failure() { 63 | // Given 64 | let testInput = "1A234" 65 | var condition = AndCondition(conditions: [firstCondition, secondCondition]) 66 | let expectedResult = false 67 | 68 | // When 69 | condition.localizedViolationString = "Min 0 Max 4 or must only contain alphanumeric" 70 | 71 | // Initial Tests 72 | AssertCondition(firstCondition, testInput: testInput, expectedResult: false) 73 | AssertCondition(secondCondition, testInput: testInput, expectedResult: true) 74 | 75 | // Test 76 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Sources/Conditions/PasswordStrengthCondition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasswordStrengthCondition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * The `PasswordStrengthCondition` checks for the strength of a password string. 14 | * The strength is measured on five simple criteria: 15 | * - contains lower case characters 16 | * - contains upper case characters 17 | * - contains numeric characters 18 | * - contains special characters (e.g /';~) 19 | * - is more than 8 characters long 20 | * 21 | * Each of these matched criteria moves the password strength of the string up one strength, strength is measured on `PasswordStrength`. 22 | * 23 | * If the password strength matches or is above the required strength than the condition will pass. 24 | */ 25 | public struct PasswordStrengthCondition: ConfigurableCondition { 26 | 27 | 28 | // MARK: - Properties 29 | 30 | public var localizedViolationString = StringLocalization.sharedInstance.localizedString("US2KeyConditionViolationPasswordStrength", comment: "") 31 | 32 | public let regex = "" 33 | 34 | public var shouldAllowViolation = true 35 | 36 | public let configuration: PasswordStrengthConfiguration 37 | 38 | 39 | // MARK: - Initializers 40 | 41 | public init(configuration: PasswordStrengthConfiguration) { 42 | self.configuration = configuration 43 | } 44 | 45 | 46 | // MARK: - Check 47 | 48 | public func check(_ text: String?) -> Bool { 49 | guard let sourceText = text else { 50 | return false 51 | } 52 | 53 | let matches = [numberOfMatchesWithPattern("\\d", text: sourceText), numberOfMatchesWithPattern("[a-z]", text: sourceText), numberOfMatchesWithPattern("[A-Z]", text: sourceText), numberOfMatchesWithPattern("[^a-zA-Z\\d]", text: sourceText)] 54 | 55 | var strength = matches.reduce(0, { $0 + ($1 > 0 ? 1 : 0) }) 56 | 57 | if sourceText.count > 8 { 58 | strength += 1 59 | } else { 60 | strength -= 1 61 | } 62 | 63 | return strength >= configuration.requiredStrength.rawValue 64 | } 65 | 66 | fileprivate func numberOfMatchesWithPattern(_ pattern: String, text: String) -> Int { 67 | guard let regExpression = try? NSRegularExpression(pattern: pattern, options: []) else { 68 | return 0 69 | } 70 | 71 | return regExpression.numberOfMatches(in: text, options: [], range: NSRange(location: 0, length: text.count)) 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Sources/Protocols/Condition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Condition.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 12/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * A _Condition_ is the smallest sub element of the validation framework. 14 | * It tells how a string must be structured or wat is has to contain or not. 15 | * Validators are for storing those conditions and checking 16 | * for violations of every condition. 17 | * 18 | * Conditions are recommended working with regular expressions but can also contain 19 | * their custom checking code for detecting violations in the string to check. 20 | * 21 | * By returning a localized string in method `localizedViolationString` the 22 | * user can be informed in a convenient way what went wrong. 23 | */ 24 | public protocol Condition: CustomStringConvertible { 25 | 26 | /// Localized string which described the kind of violation. 27 | var localizedViolationString: String { get set } 28 | 29 | /// A regular expression string which the validated string is matched against. 30 | var regex: String { get } 31 | 32 | /// If set to `false` the user is not able to enter characters which would break the condition. 33 | var shouldAllowViolation: Bool { get set } 34 | 35 | /// Initializer that creates a condition based on default values. 36 | init() 37 | 38 | /** 39 | Check the custom condition. 40 | - parameter text: `String` to check. 41 | - returns: Whether the condition check passed or failed. 42 | - note: Checking a `nil` value should always return `false`. 43 | */ 44 | func check(_ text: String?) -> Bool 45 | } 46 | 47 | 48 | // Default implementation of `Condition.check(text:)` returns `true` if the `regex` is valid and there is at least one match in `text`. 49 | public extension Condition { 50 | 51 | func check(_ text: String?) -> Bool { 52 | guard let sourceText = text, 53 | let regExpression = try? NSRegularExpression(pattern: regex, options: .caseInsensitive) else { 54 | return false 55 | } 56 | 57 | return regExpression.firstMatch(in: sourceText, options: [], range: NSRange(location: 0, length: sourceText.count)) != nil 58 | } 59 | 60 | } 61 | 62 | 63 | public extension Condition { 64 | 65 | var description: String { 66 | var result = "<" 67 | 68 | result += "\(type(of: self))" 69 | result += "\n " 70 | result += "\n " 71 | 72 | result += ">" 73 | 74 | return result 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/PasswordStrengthConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasswordStrengthConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class PasswordStrengthConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Test Initializers 18 | 19 | func testPasswordStrengthCondition_DefaultInit() { 20 | // Given 21 | let condition = PasswordStrengthCondition() 22 | let expectedStrength = PasswordStrength.veryStrong 23 | 24 | // When 25 | let actualStrength = condition.configuration.requiredStrength 26 | 27 | // Test 28 | XCTAssertEqual(actualStrength, 29 | expectedStrength, 30 | "Expected required strength to be: \(expectedStrength) but found: \(actualStrength)") 31 | } 32 | 33 | 34 | // MARK: - Test Success 35 | 36 | func testPasswordStrengthCondition_VeryWeak_Success() { 37 | // Given 38 | let testInput = "Foo" 39 | let condition = PasswordStrengthCondition(configuration: ConfigurationSeeds.PasswordStrengthSeeds.veryWeak) 40 | let expectedResult = true 41 | 42 | // Test 43 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 44 | } 45 | 46 | func testPasswordStrengthCondition_VeryStrong_Success() { 47 | // Given 48 | let testInput = "F1@b9a_c12983y" 49 | let condition = PasswordStrengthCondition(configuration: ConfigurationSeeds.PasswordStrengthSeeds.veryStrong) 50 | let expectedResult = true 51 | 52 | // Test 53 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 54 | } 55 | 56 | 57 | // MARK: - Test Failure 58 | 59 | func testPasswordStrengthCondition_VeryStrong_Failure() { 60 | // Given 61 | let testInput = "Foo" 62 | let condition = PasswordStrengthCondition(configuration: ConfigurationSeeds.PasswordStrengthSeeds.veryStrong) 63 | let expectedResult = false 64 | 65 | // Test 66 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 67 | } 68 | 69 | func testPasswordStrengthCondition_Nil_Failure() { 70 | // Given 71 | let testInput: String? = nil 72 | let condition = PasswordStrengthCondition() 73 | let expectedResult = false 74 | 75 | // Test 76 | AssertCondition(condition, testInput: testInput, expectedResult: expectedResult) 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First, thanks for taking the time to submit a pull request! These are the few notes and guidelines to keep things coherent. 4 | 5 | ## Overview 6 | 7 | 1. Read and abide by the [Code of Conduct][code-of-conduct]. 8 | 1. [Fork the project](https://github.com/ustwo/formvalidator-swift/fork) and clone. 9 | 1. Check you have all [requirements](#requirements) in place. 10 | 1. Create your [_feature_ branch](#feature-branch). 11 | 1. [Install](#install) the project dependencies for development. 12 | 1. [Test](#test). 13 | 1. If you have more than trivial changes (e.g. fixing typo's), then you must include a description in the [CHANGELOG.md][changelog]. 14 | 1. Push your branch and submit a [Pull Request](https://github.com/ustwo/formvalidator-swift/compare/). 15 | 16 | We will review the changes as soon as possible. 17 | 18 | ## Requirements 19 | 20 | - [Xcode][xcode] 21 | - [Bundler][bundler] 22 | - [SwiftLint][swiftlint] 23 | 24 | ## Feature Branch 25 | 26 | Please use a descriptive and concise name for your feature branch. Below is a snippet to show how to create a branch with git. 27 | 28 | ```sh 29 | git checkout -b feature/feature-name 30 | ``` 31 | 32 | ## Install 33 | 34 | After installing [Xcode][xcode], [Bundler][bundler], and [SwiftLint][swiftlint] run the following terminal command from the project folder to install the remaining gems to be able to run all the tests and scripts as you would on the CI: 35 | 36 | ```sh 37 | bundle install 38 | ``` 39 | 40 | ## Test 41 | 42 | You can quickly run the test suite using [Fastlane][fastlane]. From the project directly, use the following terminal command: 43 | 44 | ```sh 45 | bundle exec fastlane test 46 | ``` 47 | 48 | Alternatively, you can run individual tests or the suite from [within Xcode][xcode-tests]. 49 | 50 | ## Release 51 | 52 | To bump the version numbers, create the git tag, and create a GitHub release, run (replacing `$NEW_VERSION` with the desired new version number): 53 | 54 | ```sh 55 | bundle exec fastlane bump_version version:$NEW_VERSION 56 | ``` 57 | 58 | Then update the [latest release][latest-release] with notes on GitHub. 59 | 60 | Lastly, [submit the updated `podspec`][cocoapods-submission] to CocoaPods for release. 61 | 62 | 63 | 64 | [bundler]: http://bundler.io/ 65 | [changelog]: ../CHANGELOG.md 66 | [cocoapods-submission]: https://guides.cocoapods.org/making/making-a-cocoapod.html#release 67 | [code-of-conduct]: ../CODE_OF_CONDUCT.md 68 | [fastlane]: https://fastlane.tools/ 69 | [latest-release]: https://github.com/ustwo/formvalidator-swift/releases 70 | [swiftlint]: https://github.com/realm/SwiftLint 71 | [xcode]: https://itunes.apple.com/gb/app/xcode/id497799835?mt=12# 72 | [xcode-tests]: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/05-running_tests.html 73 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Extensions/XCTestCase+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase+Additions.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | 12 | @testable import FormValidatorSwift 13 | 14 | 15 | extension XCTestCase { 16 | 17 | func AssertCondition(_ condition: Condition, testInput: String?, expectedResult: Bool, file: String = #file, line: UInt = #line) { 18 | // When 19 | let actualResult = condition.check(testInput) 20 | 21 | // Test 22 | if expectedResult != actualResult { 23 | let message = "The `\(type(of: condition))` should respond with \(expectedResult) but received \(actualResult)." 24 | 25 | #if os(iOS) || os(macOS) 26 | self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) 27 | #elseif os(tvOS) 28 | self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) 29 | #endif 30 | } 31 | } 32 | 33 | /// Asserts that a `Validator` with a given input returns the expected result. 34 | /// 35 | /// - Parameters: 36 | /// - validator: `Validator` to test. 37 | /// - testInput: Input to have `validator` check. 38 | /// - expectedResult: The expected result for running the validator's check. 39 | /// - file: File that the assertion is being run in. Defaults to `#file`. 40 | /// - line: Line that the assertion is being run from. Defaults to `#line`. 41 | /// 42 | /// - Note: Currently this checks that either the actual result and expected result are either both nil or both non-nil and have the same number of elements. As `Condition` currently does not conform to equatable, further checks are not done to ensure that the conditions in the array are the same. 43 | func AssertValidator(_ validator: Validator, testInput: String?, expectedResult: [Condition]?, file: String = #file, line: UInt = #line) { 44 | // When 45 | let actualResult = validator.checkConditions(testInput) 46 | 47 | // Test 48 | guard actualResult != nil || expectedResult != nil else { 49 | return 50 | } 51 | 52 | if let actualResult = actualResult, 53 | let expectedResult = expectedResult, 54 | actualResult.count == expectedResult.count { 55 | 56 | return 57 | } 58 | 59 | let message = "The `\(type(of: validator))` should respond with \(expectedResult.debugDescription) and but received \(actualResult.debugDescription)." 60 | 61 | #if os(iOS) || os(macOS) 62 | self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) 63 | #elseif os(tvOS) 64 | self.recordFailure(withDescription: message, inFile: file, atLine: Int(line), expected: true) 65 | #endif 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. The project team 59 | will review and investigate all complaints, and will respond in a way that it deems 60 | appropriate to the circumstances. The project team is obligated to maintain 61 | confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/PostcodeConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostcodeConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 13/01/2016. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class PostcodeConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Configuration 18 | 19 | private struct TestableCondition { 20 | 21 | let country: PostcodeCountries 22 | let condition: PostcodeCondition 23 | 24 | var successInputs: [String] { 25 | switch country { 26 | case .sweden: 27 | return ["112 50", 28 | "11434", 29 | "SE-111 21", 30 | "SE-11637", 31 | "se-11637"] 32 | case .turkey: 33 | return ["34345"] 34 | case .unitedKingdom: 35 | return ["M1 1BA"] 36 | case .unitedStates: 37 | return ["20500", 38 | "95014-2083"] 39 | } 40 | } 41 | 42 | var failureInputs: [String?] { 43 | switch country { 44 | case .sweden: 45 | return ["113 4", 46 | "116233", 47 | "us-125 41", 48 | "us-125e1", 49 | nil] 50 | case .turkey: 51 | return ["3411", 52 | "347001", 53 | "34 700", 54 | "3470a", 55 | nil] 56 | case .unitedKingdom: 57 | return ["M1AA 1BA", 58 | "M1 1BAA", 59 | nil] 60 | case .unitedStates: 61 | return ["1234", 62 | "12345-1", 63 | nil] 64 | } 65 | } 66 | 67 | init(country: PostcodeCountries) { 68 | self.country = country 69 | condition = PostcodeCondition(configuration: PostcodeConfiguration(country: country)) 70 | } 71 | 72 | } 73 | 74 | 75 | // MARK: - Tests 76 | 77 | func testPostcodeCondition_Success() { 78 | for country in PostcodeCountries.allValues { 79 | let testCondition = TestableCondition(country: country) 80 | 81 | for input in testCondition.successInputs { 82 | AssertCondition(testCondition.condition, testInput: input, expectedResult: true) 83 | } 84 | } 85 | } 86 | 87 | func testPostcodeCondition_Failure() { 88 | for country in PostcodeCountries.allValues { 89 | let testCondition = TestableCondition(country: country) 90 | 91 | for input in testCondition.failureInputs { 92 | AssertCondition(testCondition.condition, testInput: input, expectedResult: false) 93 | } 94 | } 95 | } 96 | 97 | func testPostCondition_ChangeCountry() { 98 | // Given 99 | var condition = PostcodeCondition() 100 | 101 | // When 102 | condition.configuration.country = .sweden 103 | 104 | // Then 105 | XCTAssertEqual(condition.configuration.country, PostcodeCountries.sweden) 106 | XCTAssertEqual(condition.regex, PostcodeCountries.sweden.regex) 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Tests/Unit Tests/Conditions/CreditCardConditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCardConditionTests.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Onur Ersel on 2016-11-02. 6 | // Copyright © 2016 ustwo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import FormValidatorSwift 12 | 13 | 14 | final class CreditCardConditionTests: XCTestCase { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | // Conditions 20 | let conditionAll = CreditCardCondition() 21 | 22 | let cardNumberWhitespace = "3760 3171 0126 369" 23 | let cardNumberDashes = "3760-3171-0126-369" 24 | let cardNumberDigitsAndChars = "3760Das3171Uyn0126‘[ƺ369" 25 | 26 | // Invalid Card Numbers 27 | let cardNumberInvalid_1 = "0000000000000000" 28 | let cardNumberInvalid_2 = "67628 568" 29 | 30 | // Condition - Valid Card Number pairs 31 | let cardConditionsAndNumbers: [(CreditCardType, CreditCardCondition, String)] = [ 32 | (CreditCardType.americanExpress, CreditCardCondition(configuration: CreditCardConfiguration(cardType: .americanExpress)), "376031710126369"), 33 | (CreditCardType.dinersClub, CreditCardCondition(configuration: CreditCardConfiguration(cardType: .dinersClub)), "30085182354725"), 34 | (CreditCardType.discover, CreditCardCondition(configuration: CreditCardConfiguration(cardType: .discover)), "6011359046468736"), 35 | (CreditCardType.jcb, CreditCardCondition(configuration: CreditCardConfiguration(cardType: .jcb)), "3535983484092395"), 36 | (CreditCardType.maestro, CreditCardCondition(configuration: CreditCardConfiguration(cardType: .maestro)), "6762856858323942"), 37 | (CreditCardType.mastercard, CreditCardCondition(configuration: CreditCardConfiguration(cardType: .mastercard)), "5480785928215247"), 38 | (CreditCardType.visa, CreditCardCondition(configuration: CreditCardConfiguration(cardType: .visa)), "4024007127428075"), 39 | ] 40 | 41 | 42 | 43 | // MARK: - Test Success 44 | 45 | func testCreditCardCondition_Success() { 46 | // Given 47 | let expectedResult = true 48 | 49 | // Test 50 | for pair in cardConditionsAndNumbers { 51 | AssertCondition(conditionAll, testInput: pair.2, expectedResult: expectedResult) 52 | } 53 | 54 | for pair in cardConditionsAndNumbers { 55 | AssertCondition(pair.1, testInput: pair.2, expectedResult: expectedResult) 56 | } 57 | 58 | for card in cardConditionsAndNumbers { 59 | for pair in cardConditionsAndNumbers { 60 | AssertCondition(card.1, testInput: pair.2, expectedResult: (pair.0 == card.0)) 61 | } 62 | } 63 | 64 | 65 | 66 | AssertCondition(conditionAll, testInput: cardNumberWhitespace, expectedResult: expectedResult) 67 | AssertCondition(conditionAll, testInput: cardNumberDashes, expectedResult: expectedResult) 68 | AssertCondition(conditionAll, testInput: cardNumberDigitsAndChars, expectedResult: expectedResult) 69 | } 70 | 71 | 72 | // MARK: - Test Failure 73 | 74 | func testCreditCardCondition_Failure () { 75 | // Given 76 | let expectedResult = false 77 | 78 | // Test 79 | AssertCondition(conditionAll, testInput: cardNumberInvalid_1, expectedResult: expectedResult) 80 | AssertCondition(conditionAll, testInput: cardNumberInvalid_2, expectedResult: expectedResult) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/Protocols/Form.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Form.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | * A form to assist in validating `ValidatorControl` objects' current states. 14 | */ 15 | public protocol Form { 16 | // MARK: - Properties 17 | 18 | /// Entries in the form. 19 | var entries: [FormEntry] { get set } 20 | 21 | /// Whether or not the entire form is valid. 22 | var isValid: Bool { get } 23 | 24 | 25 | // MARK: - Initializers 26 | 27 | /** 28 | Creates an empty `Form`. 29 | */ 30 | init() 31 | 32 | /** 33 | Creates a `Form` where each `Validatable` uses its own `Validator` for validation. 34 | - parameter validatables: Array of `Validatable`. 35 | */ 36 | init(validatables: [ValidatorControl]) 37 | 38 | /** 39 | Creates a `Form` where each `Validatable` uses a custom `Validator` for validation. If `validatables` and `validators` have a different number of elements then returns `nil`. 40 | 41 | - parameter validatables: Array of `Validatable`. 42 | - parameter validators: Array of `Validator`. 43 | */ 44 | init?(validatables: [ValidatorControl], validators: [Validator]) 45 | 46 | 47 | // MARK: - Manipulate Entry 48 | 49 | mutating func addEntry(_ control: ValidatorControl) 50 | 51 | mutating func removeControlAtIndex(_ index: Int) -> ValidatorControl? 52 | 53 | 54 | // MARK: - Check 55 | 56 | /** 57 | Checks the text from each entry in `entries`. 58 | - returns: An array of conditions that were violated. If no conditions were violated then `nil` is returned. 59 | */ 60 | func checkConditions() -> [Condition]? 61 | 62 | } 63 | 64 | 65 | // Default implementation for `isValid`, `init(validatables:)`, `init?(validatables:validators:)`, and `checkConditions`. 66 | public extension Form { 67 | 68 | 69 | // MARK: - Properties 70 | 71 | var isValid: Bool { 72 | return checkConditions() == nil 73 | } 74 | 75 | 76 | // MARK: - Initializers 77 | 78 | init(validatables: [ValidatorControl]) { 79 | self.init() 80 | entries = validatables.map { FormEntry(validatable: $0, validator: $0.validator) } 81 | } 82 | 83 | init?(validatables: [ValidatorControl], validators: [Validator]) { 84 | guard validatables.count == validators.count else { 85 | return nil 86 | } 87 | 88 | self.init() 89 | 90 | var entries = [FormEntry]() 91 | for index in 0 ..< validatables.count { 92 | entries.append(FormEntry(validatable: validatables[index], validator: validators[index])) 93 | } 94 | self.entries = entries 95 | } 96 | 97 | 98 | // MARK: - Manipulate Entry 99 | 100 | mutating func addEntry(_ control: ValidatorControl) { 101 | entries.append(FormEntry(validatable: control, validator: control.validator)) 102 | } 103 | 104 | mutating func removeControlAtIndex(_ index: Int) -> ValidatorControl? { 105 | let entry = entries.remove(at: index) 106 | return entry.validatable 107 | } 108 | 109 | 110 | // MARK: - Check 111 | 112 | func checkConditions() -> [Condition]? { 113 | let violatedConditions = entries.map { $0.checkConditions() }.filter { $0 != nil }.map { $0! }.flatMap { $0 } 114 | 115 | return violatedConditions.isEmpty ? nil : violatedConditions 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /Sources/Controls/ValidatorTextView-AppKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidatorTextView-AppKit.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 10/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | 12 | open class ValidatorTextView: NSTextView, ValidatorControl { 13 | 14 | 15 | // MARK: - Properties 16 | 17 | open var shouldAllowViolation = false 18 | open var validateOnFocusLossOnly = false 19 | public let validator: Validator 20 | open weak var validatorDelegate: ValidatorControlDelegate? 21 | 22 | open var validatableText: String? { 23 | return string 24 | } 25 | 26 | private var didEndEditing = false 27 | private var lastIsValid: Bool? 28 | 29 | 30 | // MARK: - Initializers 31 | 32 | public convenience init(validator: Validator) { 33 | self.init(frame: CGRect.zero, validator: validator) 34 | } 35 | 36 | public convenience init(frame: CGRect, validator: Validator) { 37 | self.init(frame: frame, textContainer: nil, validator: validator) 38 | } 39 | 40 | public init(frame: CGRect, textContainer: NSTextContainer?, validator: Validator) { 41 | self.validator = validator 42 | 43 | super.init(frame: frame, textContainer: textContainer) 44 | } 45 | 46 | public required init?(coder aDecoder: NSCoder) { 47 | fatalError("Not implemented.") 48 | } 49 | 50 | 51 | // MARK: - NSTextView 52 | 53 | open override func shouldChangeText(in affectedCharRange: NSRange, replacementString: String?) -> Bool { 54 | let superShouldChange = super.shouldChangeText(in: affectedCharRange, replacementString: replacementString) 55 | 56 | guard let replacementString = replacementString, 57 | let sourceText = validatableText else { 58 | 59 | return superShouldChange 60 | } 61 | 62 | let originalString = NSString(string: sourceText) 63 | 64 | let futureString = originalString.replacingCharacters(in: affectedCharRange, with: replacementString) 65 | let conditions = validator.checkConditions(futureString) 66 | 67 | if !validateOnFocusLossOnly && affectedCharRange.location != 0, 68 | let conditions = conditions, 69 | (!shouldAllowViolation || !(conditions.isEmpty || conditions[0].shouldAllowViolation)) { 70 | 71 | return false 72 | } 73 | 74 | return superShouldChange 75 | } 76 | 77 | open override func didChangeText() { 78 | super.didChangeText() 79 | 80 | defer { 81 | // Inform delegate about changes 82 | validatorDelegate?.validatorControlDidChange(self) 83 | } 84 | 85 | // Only validate if violations are allowed 86 | // Validate according to `validateOnFocusLossOnly` while editing first time or after focus loss 87 | guard shouldAllowViolation && 88 | (!validateOnFocusLossOnly || (validateOnFocusLossOnly && didEndEditing)) else { 89 | return 90 | } 91 | 92 | let conditions = validator.checkConditions(validatableText) 93 | let isValid = conditions == nil 94 | if lastIsValid != isValid { 95 | lastIsValid = isValid 96 | 97 | // Inform delegate about valid state change 98 | validatorDelegate?.validatorControl(self, changedValidState: isValid) 99 | 100 | if !isValid { 101 | // Inform delegatate about violation 102 | validatorDelegate?.validatorControl(self, violatedConditions: conditions!) 103 | } 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /Example/iOS/FormView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormView.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 14/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import FormValidatorSwift 10 | import UIKit 11 | 12 | 13 | final class FormView: UIView { 14 | 15 | 16 | // MARK: - Properties 17 | 18 | let titleEntry = FormEntryView() 19 | let nameEntry = FormEntryView() 20 | let emailEntry = FormEntryView() 21 | 22 | let submitButton = UIButton(type: .system) 23 | 24 | fileprivate let bottomBufferView = UIView() 25 | fileprivate let stackView = UIStackView() 26 | 27 | 28 | // MARK: - Initializers 29 | 30 | convenience init() { 31 | self.init(frame: CGRect.zero) 32 | } 33 | 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | 37 | backgroundColor = UIColor.white 38 | 39 | 40 | // Setup 41 | 42 | stackView.axis = .vertical 43 | stackView.distribution = .fill 44 | stackView.alignment = .fill 45 | addSubview(stackView) 46 | 47 | titleEntry.textLabel.text = NSLocalizedString("Title", comment: "") 48 | titleEntry.textField.shouldAllowViolation = true 49 | stackView.addArrangedSubview(titleEntry) 50 | 51 | nameEntry.textLabel.text = NSLocalizedString("Surname", comment: "") 52 | stackView.addArrangedSubview(nameEntry) 53 | 54 | emailEntry.textLabel.text = NSLocalizedString("Email", comment: "") 55 | emailEntry.textField.shouldAllowViolation = true 56 | emailEntry.textField.validateOnFocusLossOnly = true 57 | stackView.addArrangedSubview(emailEntry) 58 | 59 | submitButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body) 60 | submitButton.setTitle(NSLocalizedString("Submit", comment: ""), for: UIControl.State()) 61 | stackView.addArrangedSubview(submitButton) 62 | 63 | bottomBufferView.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: stackView.axis) 64 | bottomBufferView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: stackView.axis) 65 | stackView.addArrangedSubview(bottomBufferView) 66 | 67 | 68 | // Accessibility 69 | 70 | titleEntry.textLabel.accessibilityIdentifier = FormAccessibility.Identifiers.TitleLabel 71 | titleEntry.textField.accessibilityIdentifier = FormAccessibility.Identifiers.TitleTextField 72 | 73 | nameEntry.textLabel.accessibilityIdentifier = FormAccessibility.Identifiers.NameLabel 74 | nameEntry.textField.accessibilityIdentifier = FormAccessibility.Identifiers.NameTextField 75 | 76 | emailEntry.textLabel.accessibilityIdentifier = FormAccessibility.Identifiers.EmailLabel 77 | emailEntry.textField.accessibilityIdentifier = FormAccessibility.Identifiers.EmailTextField 78 | 79 | submitButton.accessibilityIdentifier = FormAccessibility.Identifiers.SubmitButton 80 | 81 | 82 | // Layout 83 | 84 | let stackViewMargin: CGFloat = 20.0 85 | stackView.translatesAutoresizingMaskIntoConstraints = false 86 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: stackViewMargin)) 87 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: stackViewMargin)) 88 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -stackViewMargin)) 89 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: -stackViewMargin)) 90 | 91 | stackView.spacing = stackViewMargin 92 | } 93 | 94 | required init?(coder aDecoder: NSCoder) { 95 | super.init(coder: aDecoder) 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Example/iOS/FormEntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormEntryView.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 15/01/2016. 6 | // Copyright © 2016 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import FormValidatorSwift 10 | import UIKit 11 | 12 | 13 | final class FormEntryView: UIView, ValidatorControlDelegate, UITextFieldDelegate { 14 | 15 | 16 | // MARK: - Properties 17 | 18 | let textLabel = UILabel() 19 | let textField = ValidatorTextField(validator: V()) 20 | 21 | let errorLabel = UILabel() 22 | 23 | fileprivate let stackView = UIStackView() 24 | 25 | 26 | // MARK: - Initializers 27 | 28 | convenience init() { 29 | self.init(frame: CGRect.zero) 30 | } 31 | 32 | override init(frame: CGRect) { 33 | super.init(frame: frame) 34 | 35 | 36 | // Setup 37 | 38 | stackView.axis = .vertical 39 | stackView.distribution = .fill 40 | stackView.alignment = .fill 41 | addSubview(stackView) 42 | 43 | textLabel.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.headline) 44 | textLabel.textAlignment = .center 45 | stackView.addArrangedSubview(textLabel) 46 | 47 | textField.autocorrectionType = .no 48 | textField.borderStyle = .line 49 | textField.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body) 50 | textField.setValidatorDelegate(self) 51 | stackView.addArrangedSubview(textField) 52 | 53 | errorLabel.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.headline) 54 | errorLabel.isHidden = true 55 | errorLabel.lineBreakMode = .byWordWrapping 56 | errorLabel.numberOfLines = 0 57 | stackView.addArrangedSubview(errorLabel) 58 | 59 | 60 | // Accessibility 61 | 62 | errorLabel.accessibilityIdentifier = FormAccessibility.Identifiers.ErrorLabel 63 | 64 | 65 | // Layout 66 | 67 | let stackViewMargin: CGFloat = 0.0 68 | stackView.translatesAutoresizingMaskIntoConstraints = false 69 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: stackViewMargin)) 70 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: stackViewMargin)) 71 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -stackViewMargin)) 72 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: -stackViewMargin)) 73 | } 74 | 75 | required init?(coder aDecoder: NSCoder) { 76 | super.init(coder: aDecoder) 77 | } 78 | 79 | 80 | // MARK: - ValidatorControlDelegate 81 | 82 | func validatorControl(_ validatorControl: ValidatorControl, changedValidState validState: Bool) { 83 | guard let controlView = validatorControl as? UIView else { 84 | return 85 | } 86 | 87 | if validState { 88 | controlView.layer.borderColor = nil 89 | controlView.layer.borderWidth = 0.0 90 | errorLabel.isHidden = true 91 | } else { 92 | controlView.layer.borderColor = UIColor.red.cgColor 93 | controlView.layer.borderWidth = 2.0 94 | } 95 | } 96 | 97 | func validatorControl(_ validatorControl: ValidatorControl, violatedConditions conditions: [Condition]) { 98 | var errorText = "" 99 | for condition in conditions { 100 | errorText += condition.localizedViolationString 101 | } 102 | errorLabel.text = errorText 103 | 104 | errorLabel.isHidden = false 105 | } 106 | 107 | func validatorControlDidChange(_ validatorControl: ValidatorControl) { 108 | // Not used in this example yet 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /Example/macOS/FormEntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormEntryView.swift 3 | // FormValidatorSwift 4 | // 5 | // Created by Aaron McTavish on 06/01/2017. 6 | // Copyright © 2017 ustwo Fampany Ltd. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | import FormValidatorSwift 12 | 13 | 14 | final class FormEntryView: NSView, ValidatorControlDelegate, NSTextFieldDelegate { 15 | 16 | 17 | // MARK: - Properties 18 | 19 | let textLabel = NSTextField() 20 | let textField = ValidatorTextField(validator: V()) 21 | 22 | let errorLabel = NSTextField() 23 | 24 | fileprivate let stackView = NSStackView() 25 | 26 | 27 | // MARK: - Initializers 28 | 29 | convenience init() { 30 | self.init(frame: CGRect.zero) 31 | } 32 | 33 | override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | 36 | 37 | // Setup 38 | 39 | stackView.orientation = .vertical 40 | stackView.distribution = .fill 41 | stackView.distribution = .fill 42 | addSubview(stackView) 43 | 44 | textLabel.font = NSFont.labelFont(ofSize: 12.0) 45 | textLabel.alignment = .center 46 | textLabel.isEditable = false 47 | textLabel.isBezeled = false 48 | textLabel.drawsBackground = false 49 | textLabel.isSelectable = false 50 | stackView.addArrangedSubview(textLabel) 51 | 52 | textField.font = NSFont.messageFont(ofSize: 12.0) 53 | textField.setValidatorDelegate(self) 54 | stackView.addArrangedSubview(textField) 55 | 56 | errorLabel.font = NSFont.boldSystemFont(ofSize: 14.0) 57 | errorLabel.isHidden = true 58 | errorLabel.lineBreakMode = .byWordWrapping 59 | errorLabel.isEditable = false 60 | errorLabel.isBezeled = false 61 | errorLabel.drawsBackground = false 62 | errorLabel.isSelectable = false 63 | errorLabel.maximumNumberOfLines = 2 64 | stackView.addArrangedSubview(errorLabel) 65 | 66 | 67 | // Accessibility 68 | 69 | errorLabel.setAccessibilityIdentifier(FormAccessibility.Identifiers.ErrorLabel) 70 | 71 | 72 | // Layout 73 | 74 | let stackViewMargin: CGFloat = 0.0 75 | stackView.translatesAutoresizingMaskIntoConstraints = false 76 | 77 | NSLayoutConstraint.activate([ 78 | stackView.topAnchor.constraint(equalTo: topAnchor, constant: stackViewMargin), 79 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -stackViewMargin), 80 | stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: stackViewMargin), 81 | stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -stackViewMargin) 82 | ]) 83 | } 84 | 85 | required init?(coder aDecoder: NSCoder) { 86 | super.init(coder: aDecoder) 87 | } 88 | 89 | 90 | // MARK: - ValidatorControlDelegate 91 | 92 | func validatorControl(_ validatorControl: ValidatorControl, changedValidState validState: Bool) { 93 | guard let controlView = validatorControl as? NSView else { 94 | return 95 | } 96 | 97 | if validState { 98 | controlView.layer?.borderColor = nil 99 | controlView.layer?.borderWidth = 0.0 100 | errorLabel.isHidden = true 101 | } else { 102 | controlView.layer?.borderColor = NSColor.red.cgColor 103 | controlView.layer?.borderWidth = 2.0 104 | } 105 | } 106 | 107 | func validatorControl(_ validatorControl: ValidatorControl, violatedConditions conditions: [Condition]) { 108 | var errorText = "" 109 | for condition in conditions { 110 | errorText += condition.localizedViolationString 111 | } 112 | errorLabel.stringValue = errorText 113 | 114 | errorLabel.isHidden = false 115 | } 116 | 117 | func validatorControlDidChange(_ validatorControl: ValidatorControl) { 118 | // Not used in this example yet 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /iOS Example.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /FormValidatorSwift.xcodeproj/xcshareddata/xcschemes/FormValidatorSwift tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /macOS Example.xcodeproj/xcshareddata/xcschemes/macOS Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /FormValidatorSwift.xcodeproj/xcshareddata/xcschemes/FormValidatorSwift iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | --------------------------------------------------------------------------------