Formify - 适用于快速简易表单和输入验证的 Swift 库

Formify 是一个 Swift 库,专为简化表单和输入验证而设计。借助 Formify,您可以轻松验证所有 TextField 或 TextEditor 元素,而无需添加任何特殊的更改或修饰符。

主要特性

要求

安装

Swift Package Manager

将以下内容添加到您的 Swift 包的 Package.swift 文件中

dependencies: [
    .package(url: "https://github.com/sanzaru/formify.git", from: "0.0.1")
]

XCode

将以下软件包添加到您的项目中

https://github.com/sanzaru/formify.git

用法

Formify 使用带有 FormifyOperator 运算符的 FormifyField 对象来进行输入管理和验证。所有 FormifyField 对象都带有一个 String 类型的 value 属性。此值可以用作 TextFieldTextEditor 视图内的绑定。

您可以通过简单地检查字段的 isValid 属性或 errors 数组来检查有效性。

最简单的验证形式是在视图内声明一个状态变量,在 TextField 内使用 FormifyFieldvalue,并检查 isValid 属性

...

@State private var formField = FormifyField(operators: [.required, .pattern("[A-Za-z ]+")])

...

TextField("", text: $formField.value)
    .textFieldStyle(.plain)

...

Button { } label: {
    Text("Submit")
}
.disabled(!formField.isValid)

...

注意

您还可以将 FormifyField 作为 @Binding 传递到视图中,或者在 @ObservableObject 中将该对象用作 @Published 变量。有关更多信息,请参见示例。

运算符

名称 描述 示例
.required 如果设置,则该字段变为必填项,不能为空。 .required
.minLength(Int) 如果设置,则该值必须长于提供的长度。 .minLength(10)
.maxLength(Int) 如果设置,则该值必须短于提供的长度。 .maxLength(10)
.pattern(String) 如果设置,则该值必须与提供的正则表达式匹配。 .pattern("[a-zA-Z]")
.email 如果设置,则该值必须与电子邮件地址的内部正则表达式匹配。 .email
.phone 如果设置,则该值必须与电话号码的内部正则表达式匹配。 .phone
.urlWithScheme 如果设置,则该值必须与带有 scheme 的 URL 的内部正则表达式匹配(例如 https://example.com, file://some-file)。 .urlWithScheme
.urlNoScheme 如果设置,则该值必须与不带 scheme 的 URL 的内部正则表达式匹配(例如 localhost, example.com)。 .urlNoScheme
.disableTrimming 如果设置,则禁用自动修剪空格和换行符。 .disableTrimming
.custom 如果设置,则给定的验证处理程序 (String) -> Bool 将用于验证。 .custom({ $0 == "someValue" })

示例

以下示例展示了一个简单的视图,其中包含一个表单,该表单包含三个字段:姓名、电子邮件地址和自定义值。姓名和电子邮件字段是必填项,并根据特定模式进行验证。姓名字段还具有最小和最大长度验证,而自定义字段仅为必填项,没有额外的验证

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)
                }
            }
        }
    }
}

此外,以下示例展示了一个简单的 ViewModifier,它可以包装所有错误并在 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)
                }
            }
        }
    }
}