Skip to content

A SwiftUI implementation of React Hooks Form. A custom Hook inherits from React Hooks to handle input forms

License

Notifications You must be signed in to change notification settings

dungntm58/swiftui-hooks-form

Repository files navigation

SwiftUI Hooks Form

A SwiftUI implementation of React Hooks Form.

Performant, flexible and extensible forms with easy-to-use validation.

πŸ“” API Reference

test release Swift5 Platform license



Introduction

SwiftUI Hooks Form is a Swift implementation of React Hook Form

This library continues working from SwiftUI Hooks. Thank ra1028 for developing the library.


Getting Started

Requirements

Minimum Version
Swift 5.7
Xcode 14.0
iOS 13.0
macOS 10.15
tvOS 13.0

Installation

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"),
]),

Documentation


Hooks API

πŸ‘‡ 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)
    }
}

SwiftUI Component

πŸ‘‡ 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

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

FieldOption

struct FieldOption<FieldName, Value> {
    let name: FieldName
    let value: Binding<Value>
}

ControllerRenderOption

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.


Acknowledgements


License

MIT Β© Dung Nguyen