Meet Validator: Chainable Form Validation Rules for Swift Apps

Written by nsvasilev | Published 2026/02/12
Tech Story Tags: swift | swiftui | uikit | xcode | ios-app-development | ios | watch-os | macos

TLDRValidator is a modern, lightweight Swift framework that provides elegant and type-safe input validation.via the TL;DR App

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

  1. 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: Validator on GitHub

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.


Written by nsvasilev | iOS engineer with 8+ years of experience
Published by HackerNoon on 2026/02/12