A simple and customizable Declarative form validator for SwiftUI
SwiftUIFormValidator
A declarative SwiftUI form validation. Clean, simple, and customizable.
Screenshot
Usage
Basic Setup
// 1
import FormValidator
// 2
class FormInfo: ObservableObject {
@Published var firstName: String = ""
// 3
lazy var form = {
FormValidation(validationType: .immediate)
}()
// 4
lazy var firstNameValidation: ValidationContainer = {
$firstName.nonEmptyValidator(form: form, errorMessage: "First name is not valid")
}()
}
struct ContentView: View {
// 5
@ObservedObject var formInfo = FormInfo()
var body: some View {
TextField("First Name", text: $formInfo.firstName)
.validation(formInfo.firstNameValidation) // 6
}
}
- Import
FormValidator
. - Declare an
ObservableObject
for the form with any name, for example, FormInfo, LoginInfo, or any name. - Declare
FormValidation
object and choose a validation type. - Declare a
ValidationContainer
for the field you need to validate. - In your view, declare the
FormValidation
object. - Declare
validation(formInfo.firstNameValidation)
with the validation of the field.
Congratulation!! your field is now validated for you!
Inline Validation
For fast validation, you can use InlineValidator
and provide your validation logic in line:
lazy var lastNamesValidation: ValidationContainer = {
$lastNames.inlineValidator(form: form) { value in
// Put validation logic here
!value.isEmpty
}
}()
Validation Types
You can choose between 2 different validation types: FormValidation(validationType: .immediate)
and FormValidation(validationType: .deffered)
- immediate: the validation is triggered every time the field is changed. An error
message will be shown in case the value is invalid. - deferred: in this case, the validation will be triggered manually only using
FormValidation.triggerValidation()
The error messages will be displayed only after triggering the validation manually.
Manual Validation
You can trigger the form validation any time by calling FormValidation.triggerValidation()
. After the validation, each field in the form will
display error message if it's invalid.
React to Validation Change
You can react to validation change using FormValidation.$allValid
and FormValidation.$validationMessages
VStack {} // parent view of the form
.onReceive(formInfo.form.$allValid) { isValid in self.isSaveDisabled = !isValid }
.onReceive(formInfo.form.$validationMessages) { messages in print(messages) }
:tada: Installation
CocoaPods
Use the following entry in your Podfile:
pod 'SwiftUIFormValidator'
Then run pod install
.
Swift Package Manager
Add the following as a dependency to your Package.swift
:
.package(url: "https://github.com/ShabanKamell/SwiftUIFormValidator.git")
and then specify "SwiftUIFormValidator"
as a dependency of the Target in which you wish to use SwiftUIFormValidator.
Here's an example PackageDescription
:
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
dependencies: [
.package(url: "https://github.com/ShabanKamell/SwiftUIFormValidator")
],
targets: [
.target(
name: "MyPackage",
dependencies: ["SwiftUIFormValidator"])
]
)
Accio
Accio is a dependency manager based on SwiftPM which can build frameworks for iOS/macOS/tvOS/watchOS. Therefore the integration steps of SwiftUIFormValidator are exactly the same as described above. Once your Package.swift
file is configured, run accio update
instead of swift package update
.
Don't forget to add import FormValidator
to use the framework.
Carthage
Carthage users can point to this repository and use generated SwiftUIFormValidator
framework.
Make the following entry in your Cartfile:
github "ShabanKamell/SwiftUIFormValidator"
Then run carthage update
.
If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained over at Carthage.
Validators
Validator | Description |
---|---|
NonEmptyValidator | Validates if a string is empty of blank |
EmailValidator | Validates if the email is valid or not. |
DateValidator | Validates if a date falls within after & before dates. |
PatternValidator | Validates if a patten is matched or not. |
InlineValidator | Validates if a condition is valid or not. |
Custom Validators
In easy steps, you can add a custom validator:
// 1
class CountValidator: FormValidator {
public var publisher: ValidationPublisher!
public var subject: ValidationSubject = .init()
public var latestValidation: Validation = .failure(message: "")
public var onChanged: ((Validation) -> Void)? = nil
func validate(
value: String,
errorMessage: @autoclosure @escaping ValidationErrorClosure
) -> Validation {
guard value.count == 2 else {
return .failure(message: errorMessage())
}
return .success
}
}
// 2
public extension Published.Publisher where Value == String {
func countValidator(
form: FormValidation,
errorMessage: @autoclosure @escaping ValidationErrorClosure = ""
) -> ValidationContainer {
let validator = CountValidator()
let message = errorMessage()
return ValidationPublishers.create(
form: form,
validator: validator,
for: self,
errorMessage: !message.isEmpty ? message : "Count must be 2")
}
}
- Implement
FormValidator
protocol. - Add the validator logic in an extension to
Published.Publisher
.
Validation Messages
You can provide a validation message for every field by providing errorMessage
$firstName.nonEmptyValidator(form: form, errorMessage: "First name is not valid")
If you don't provide a message, a default one will be used for built-in providers.
All default messages are located in DefaultValidationMessages
class.
$firstName.nonEmptyValidator(form: form) // will use the default message
In this example, DefaultValidationMessages.required
will be used.
Overriding Default Validation Messages
You can override the default validation messages by inheriting from DefaultValidationMessages
class ValidationMessages: DefaultValidationMessages {
public override var required: String {
"Required field"
}
// Override any message ...
}
Or if you need to override all the messages, you can implement ValidationMessagesProtocol
.
And provide the messages to FormValidation
FormValidation(validationType: .immediate, messages: ValidationMessages())