Formify is a Swift library designed for easy form and input validation. With Formify, you can easily validate all your TextField or TextEditor elements without needing to add any special changes or modifiers.
- 👌 Ease of use: It is very easy to implement Formify into your views and validate inputs - even in existing ones.
- 🏎️ Speed: With no special magic or any ObservableObjects and subscribers, the library is very fast
- 📐 Size: Very small footprint
- 🚀 Performance: Minimal performance impact
- Swift 5.9
- iOS 16.0 / macOS 13.0 / tvOS 16.0 / watchOS 9.0 / visionOS 1.0
Add the following to the Package.swift of your Swift package:
dependencies: [
.package(url: "https://github.com/sanzaru/formify.git", from: "0.0.1")
]
Add the following package to your project:
https://github.com/sanzaru/formify.git
Formify uses FormifyField
objects with FormifyOperator
operators for input management and validation.
All FormifyField
objects come with a value
attribute of type String. This value can be used as a binding inside a
TextField
or TextEditor
view.
You can check the validity by simply checking the isValid
attribute or the errors
array of the field.
The simplest form of validation would be to declare a state variable inside a view, use the value
of the
FormifyField
inside a TextField, and check the isValid
attribute:
...
@State private var formField = FormifyField(operators: [.required, .pattern("[A-Za-z ]+")])
...
TextField("", text: $formField.value)
.textFieldStyle(.plain)
...
Button { } label: {
Text("Submit")
}
.disabled(!formField.isValid)
...
Note
You can also pass the FormifyField
as a @Binding
into views or use the object inside an @ObservableObject
as a
@Published
variable. See the example for more information.
Name | Description | Example |
---|---|---|
.required | If set, the field becomes required and cannot be left empty. | .required |
.minLength(Int) | If set, the value must be longer than the provided length. | .minLength(10) |
.maxLength(Int) | If set, the value must be shorter than the provided length. | .maxLength(10) |
.pattern(String) | If set, the value must match the provided regular expression. | .pattern("[a-zA-Z]") |
If set, the value must match an internal regular expression for email addresses. | .email |
|
.phone | If set, the value must match an internal regular expression for phone numbers. | .phone |
.urlWithScheme | If set, the value must match an internal regular expression for URLs with scheme (e.g. https://example.com, file://some-file). | .urlWithScheme |
.urlNoScheme | If set, the value must match an internal regular expression for URLs without scheme (e.g. localhost, example.com). | .urlNoScheme |
.disableTrimming | If set, the automatic trimming of white spaces and new lines is disabled. | .disableTrimming |
.custom | If set, the given validation handler (String) -> Bool will be used for validation. |
.custom({ $0 == "someValue" }) |
The following example shows a simple view with a form containing three fields: a name, an email address, and a custom value. The name and email fields are required and validated against specific patterns. The name field also has minimum and maximum length validation, while the custom field is only required without additional validation:
import SwiftUI
import Formify
struct ContentView: View {
struct FormFields {
var name = FormifyField(operators: [.required, .minLength(10), .maxLength(20), .pattern("[A-Za-z ]+")])
var email = FormifyField("foo@bar.com", operators: [.pattern("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")])
var custom = FormifyField("Preset value", operators: [.required])
var isValid: Bool {
name.isValid && email.isValid && custom.isValid
}
}
@State private var formFields = FormFields()
var body: some View {
NavigationStack {
Form {
// Name text field
VStack(alignment: .leading) {
Text("Name*")
.foregroundColor(.teal)
TextField("John Doe", text: $formFields.name.value)
.textFieldStyle(.plain)
.modifier(FormValidationErrorWrapperModifier(formField: $formFields.name))
}
// Email text field
VStack(alignment: .leading) {
Text("Email*")
.foregroundColor(.teal)
TextField("example@mail.com", text: $formFields.email.value)
.textFieldStyle(.plain)
.modifier(FormValidationErrorWrapperModifier(formField: $formFields.email))
}
// Custom text field
VStack(alignment: .leading) {
Text("Custom")
.foregroundColor(.teal)
TextField("Some value", text: $formFields.custom.value)
.textFieldStyle(.plain)
.modifier(FormValidationErrorWrapperModifier(formField: $formFields.custom))
}
}
.navigationTitle("Example Form")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button { print("Submit") } label: {
Text("Submit")
}
.disabled(!formFields.isValid)
}
}
}
}
}
Additionally, the following example shows a simple ViewModifier that wraps all errors and displays a message underneath the TextField:
import SwiftUI
import Formify
struct FormValidationErrorWrapperModifier: ViewModifier {
@Binding var formField: FormifyField
func body(content: Content) -> some View {
VStack(alignment: .leading) {
content
let errors = formField.errors
if !errors.isEmpty, formField.isTouched {
ForEach(errors, id: \.self) { error in
Group {
switch error {
case .pattern: Text("Invalid pattern")
case .required: Text("Required")
case .minLength(let length): Text("Min length \(length) / \(formField.minLength ?? 0)")
case .maxLength(let length): Text("Max length \(length) / \(formField.maxLength ?? 0)")
}
}
.font(.caption)
.foregroundColor(.red)
}
}
}
}
}