A SwiftUI implementation of React Hooks Form.
Performant, flexible and extensible forms with easy-to-use validation.
SwiftUI Hooks Form is a Swift implementation of React Hook Form
This library continues working from SwiftUI Hooks. Thank ra1028 for developing the library.
Minimum Version | |
---|---|
Swift | 5.7 |
Xcode | 14.0 |
iOS | 13.0 |
macOS | 10.15 |
tvOS | 13.0 |
The module name of the package is FormHook
. Choose one of the instructions below to install and add the following import statement to your source code.
import FormHook
From Xcode menu: File
> Swift Packages
> Add Package Dependency
https://github.com/dungntm58/swiftui-hooks-form
In your Package.swift
file, first add the following to the package dependencies
:
.package(url: "https://github.com/dungntm58/swiftui-hooks-form"),
And then, include "Hooks" as a dependency for your target:
.target(name: "<target>", dependencies: [
.product(name: "FormHook", package: "swiftui-hooks-form"),
]),
👇 Click to open the description.
useForm
func useForm<FieldName>(
mode: Mode = .onSubmit,
reValidateMode: ReValidateMode = .onChange,
resolver: Resolver<FieldName>? = nil,
context: Any? = nil,
shouldUnregister: Bool = true,
criteriaMode: CriteriaMode = .all,
delayErrorInNanoseconds: UInt64 = 0
) -> FormControl<FieldName> where FieldName: Hashable
useForm
is a custom hook for managing forms with ease. It returns a FormControl
instance.
useController
func useController<FieldName, Value>(
name: FieldName,
defaultValue: Value,
rules: any Validator<Value>,
shouldUnregister: Bool = false
) -> ControllerRenderOption<FieldName, Value> where FieldName: Hashable
This custom hook powers Controller
. Additionally, it shares the same props and methods as Controller
. It's useful for creating reusable Controlled
input.
useController
must be called in a Context
scope.
enum FieldName: Hashable {
case username
case password
}
@ViewBuilder
var hookBody: some View {
let form: FormControl<FieldName> = useForm()
Context.Provider(value: form) {
let (field, fieldState, formState) = useController(name: FieldName.username, defaultValue: "")
TextField("Username", text: field.value)
}
}
// this code achieves the same
@ViewBuilder
var body: some View {
ContextualForm(...) { form in
let (field, fieldState, formState) = useController(name: FieldName.username, defaultValue: "")
TextField("Username", text: field.value)
}
}
👇 Click to open the description.
ContextualForm
struct ContextualForm<Content, FieldName>: View where Content: View, FieldName: Hashable {
init(mode: Mode = .onSubmit,
reValidateMode: ReValidateMode = .onChange,
resolver: Resolver<FieldName>? = nil,
context: Any? = nil,
shouldUnregister: Bool = true,
shouldFocusError: Bool = true,
delayErrorInNanoseconds: UInt64 = 0,
@_implicitSelfCapture onFocusField: @escaping (FieldName) -> Void,
@ViewBuilder content: @escaping (FormControl<FieldName>) -> Content
)
@available(macOS 12.0, iOS 15.0, tvOS 15.0, *)
init(mode: Mode = .onSubmit,
reValidateMode: ReValidateMode = .onChange,
resolver: Resolver<FieldName>? = nil,
context: Any? = nil,
shouldUnregister: Bool = true,
shouldFocusError: Bool = true,
delayErrorInNanoseconds: UInt64 = 0,
focusedFieldBinder: FocusState<FieldName?>.Binding,
@ViewBuilder content: @escaping (FormControl<FieldName>) -> Content
)
It wraps a call of useForm
inside the hookBody
and passes the FormControl value to a Context.Provider<Form>
It is identical to
let form: FormControl<FieldName> = useForm(...)
Context.Provider(value: form) {
...
}
Controller
import SwiftUI
struct Controller<Content, FieldName, Value>: View where Content: View, FieldName: Hashable {
init(
name: FieldName,
defaultValue: Value,
rules: any Validator<Value> = NoopValidator(),
@ViewBuilder render: @escaping (ControllerRenderOption<FieldName, Value>) -> Content
)
}
struct FieldOption<FieldName, Value> {
let name: FieldName
let value: Binding<Value>
}
typealias ControllerRenderOption<FieldName, Value> = (field: FieldOption<FieldName, Value>, fieldState: FieldState, formState: FormState<FieldName>) where FieldName: Hashable
It wraps a call of useController
inside the hookBody
. Like useController
, you guarantee Controller
must be used in a Context
scope.