Form validation is one of the most common and often frustrating tasks in iOS development. From email fields to passwords, from phone numbers to custom user input, validating forms can quickly become repetitive and error-prone. Every developer has faced it: different screens, different rules, duplicated code, subtle bugs.
To tackle this problem, I created Validator - a lightweight Swift library designed to make form validation simple, consistent, and easy to maintain. It works seamlessly with both UIKit and SwiftUI, reducing boilerplate and letting developers focus on building great apps.
Why Form Validation is Hard
You might think form validation is trivial. After all, how complex can it be to check an email or a password? But in real-world apps, validation can become messy fast:
- Rules often change depending on business requirements.
- Validation logic is repeated across multiple screens.
- Error messages need to be consistent and user-friendly.
- SwiftUI and UIKit handle state differently, which complicates integration.
Consider an email field. On the login screen, you might just check if it’s not empty. On the registration screen, you check for proper email format. On the profile update screen, you may also need to check uniqueness against a server. Without a central validation system, this logic becomes scattered, hard to test, and error-prone.
Introducing Validator
Validator is built to solve these challenges by providing:
- Unified validation rules for any type of input.
- Chainable rules, so you can apply multiple validations to a single field.
- Clear, customizable error messages.
- Seamless SwiftUI and UIKit support.
- Lightweight and easy to integrate, with minimal boilerplate.
At its core, Validator allows you to define rules and validate input, receiving clear feedback for valid and invalid states. Here’s a simple example:
import ValidatorCore
let validator = Validator()
let result = validator.validate(input: "text", rule: LengthValidationRule(min: 4, error: "Text must be at least 4 characters long"))
switch result {
case .valid:
print("Text is valid")
case let .invalid(errors):
// handle validation errors
print("Validation errors: \(errors.map(\.message))")
}
This simple snippet already demonstrates the power of Validator: readable code, clear results, and no messy conditionals scattered across your project.
Key Features
- Chainable Rules
Validator supports applying multiple rules to a single field in a chainable and intuitive way:
validator.validate(input: "12345", rules: [
LengthValidationRule(min: 4, error: "Text must be at least 4 characters long"),
/// Other rules
])
This ensures all conditions are checked consistently, and you receive a comprehensive list of errors if validation fails.
2. Creating Custom Rules
One of the most powerful features of Validator is the ability to create your own validation rules. This makes the library extremely flexible and adaptable to any project requirements.
For example, let’s implement a NumberValidationRule that ensures the input contains only numeric characters:
import ValidatorCore
/// A number validation rule.
public struct NumberValidationRule: IValidationRule {
// MARK: Types
public typealias Input = String
// MARK: Properties
/// The validation error.
public let error: IValidationError
// MARK: Initialization
public init(error: IValidationError) {
self.error = error
}
// MARK: IValidationRule
public func validate(input: String) -> Bool {
return !input.isEmpty && input.allSatisfy { $0.isNumber }
}
}
// Usage:
let numberRule = NumberValidationRule(error: "Input must be a number")
let result = numberRule.validate(input: "12345") // returns true
let result2 = numberRule.validate(input: "12a45") // returns false
3. Customizable Error Messages
Every validation rule can have its own error message. This allows you to provide friendly, human-readable feedback to users, improving the overall UX.
let passwordRule = LengthValidationRule(min: 8, error: "Password must be at least 8 characters")
let numericRule = NumberValidationRule(error: "Password must include at least one number")
4. SwiftUI Integration
Validator integrates seamlessly with SwiftUI. You can bind your input fields to a form manager that automatically handles validation and error display:
import SwiftUI
import ValidatorUI
import ValidatorCore
class Form: ObservableObject {
@Published
var manager = FormFieldManager()
@FormField(rules: [LengthValidationRule(max: 20, error: "First name is too long")])
var firstName: String = ""
@FormField(rules: [LengthValidationRule(min: 5, error: "Last name is too short")])
var lastName: String = ""
lazy var firstNameValidationContainer = _firstName.validate(manager: manager)
lazy var lastNameValidationContainer = _lastName.validate(manager: manager)
}
struct ContentView: View {
@ObservedObject private var form = Form()
var body: some View {
VStack {
TextField("First Name", text: $form.firstName)
.validate(validationContainer: form.firstNameValidationContainer) { errors in
Text(errors.map { $0.message }.joined(separator: " "))
}
TextField("Last Name", text: $form.lastName)
.validate(validationContainer: form.lastNameValidationContainer) { errors in
Text(errors.map { $0.message }.joined(separator: " "))
}
Button(action: { self.form.manager.validate() }, label: { Text("Validate") })
Spacer()
}
.onReceive(
form.manager.$isValid,
perform: { value in
if value {
print("All form fields are valid")
} else {
print("Some form fields are invalid")
}
}
)
}
}
This approach keeps your views reactive, with minimal code, and ensures validation is consistent across your app.
5. UIKit Support
Validator is not limited to SwiftUI. You can integrate it into UIKit projects with similar simplicity:
import UIKit
import ValidatorUI
import ValidatorCore
class ViewController: UIViewController {
let textField: UITextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
/// Adds validation rule to the text field.
textField.add(rule: LengthValidationRule(max: 10, error: "Text must be at most 10 characters"))
/// Enables automatic validation on input change.
textField.validateOnInputChange(isEnabled: true)
/// Handle the validation result.
textField.validationHandler = { result in
switch result {
case .valid:
print("Text is valid")
case let .invalid(errors):
print("Validation errors: \(errors)")
}
}
}
/// Setup UITextField ...
}
Conclusion
Form validation is a common pain point in iOS development, but it doesn’t have to be complicated. With Validator, you can handle multiple rules, provide user-friendly feedback, and keep your code clean.
If you want to save time and reduce bugs in your projects, check out Validator and try it in your next Swift or SwiftUI app:
Next Steps
- Integrate Validator into your project and define your rules
- Contribute to the library or suggest improvements on GitHub
- Share feedback and examples to help the community
Validator is about writing less code, validating more consistently, and making users happy.
