Skip to content

Commit afeaa29

Browse files
committed
Update Example and README
1 parent 9e30f19 commit afeaa29

File tree

6 files changed

+97
-43
lines changed

6 files changed

+97
-43
lines changed

Example/FormHookExample/ContentView.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ enum Gender: String, CaseIterable {
3131
case female = "Female"
3232
}
3333

34-
struct ContentView: HookView {
34+
struct ContentView: View {
3535

3636
@FocusState var focusField: FormFieldName?
3737

3838
@ViewBuilder
39-
var hookBody: some View {
39+
var body: some View {
4040
ContextualForm(focusedFieldBinder: $focusField) { form in
4141
Form {
4242
Section("Name") {
@@ -82,7 +82,7 @@ struct ContentView: HookView {
8282
.submitLabel(.next)
8383

8484
if let error = fieldState.error.first {
85-
VStack {
85+
VStack(alignment: .leading) {
8686
textField
8787
Text(error)
8888
.font(.system(size: 10)).foregroundColor(.red)
@@ -105,7 +105,7 @@ struct ContentView: HookView {
105105
.submitLabel(.next)
106106

107107
if let error = fieldState.error.first {
108-
VStack {
108+
VStack(alignment: .leading) {
109109
textField
110110
Text(error)
111111
.font(.system(size: 10)).foregroundColor(.red)
@@ -129,7 +129,7 @@ struct ContentView: HookView {
129129
.submitLabel(.go)
130130

131131
if let error = fieldState.error.first {
132-
VStack {
132+
VStack(alignment: .leading) {
133133
textField
134134
Text(error)
135135
.font(.system(size: 10)).foregroundColor(.red)
@@ -153,7 +153,7 @@ struct ContentView: HookView {
153153
}
154154

155155
if let error = fieldState.error.first {
156-
VStack {
156+
VStack(alignment: .leading) {
157157
picker
158158
Text(error)
159159
.font(.system(size: 10)).foregroundColor(.red)
@@ -177,9 +177,10 @@ struct ContentView: HookView {
177177
) {
178178
Text(field.name.rawValue)
179179
}
180+
.environment(\.locale, Locale.init(identifier: "en"))
180181

181182
if let error = fieldState.error.first {
182-
VStack {
183+
VStack(alignment: .leading) {
183184
picker
184185
Text(error)
185186
.font(.system(size: 10)).foregroundColor(.red)
@@ -213,7 +214,7 @@ struct ContentView: HookView {
213214
.submitLabel(.next)
214215

215216
if let error = fieldState.error.first {
216-
VStack {
217+
VStack(alignment: .leading) {
217218
textField
218219
Text(error)
219220
.font(.system(size: 10)).foregroundColor(.red)
@@ -226,7 +227,7 @@ struct ContentView: HookView {
226227

227228
@ViewBuilder
228229
var phoneView: some View {
229-
let phoneRegEx = "^\\d{3}-\\d{3}-\\d{4}$"
230+
let phoneRegEx = "^[0-9+]{0,1}+[0-9]{5,16}$"
230231
let phonePatternValidator = PatternMatchingValidator<String>(pattern: phoneRegEx) { result in
231232
if result {
232233
return []
@@ -246,7 +247,7 @@ struct ContentView: HookView {
246247
.submitLabel(.next)
247248

248249
if let error = fieldState.error.first {
249-
VStack {
250+
VStack(alignment: .leading) {
250251
textField
251252
Text(error)
252253
.font(.system(size: 10)).foregroundColor(.red)

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ var hookBody: some View {
131131
TextField("Username", text: field.value)
132132
}
133133
}
134+
135+
// this code achieves the same
136+
137+
@ViewBuilder
138+
var hookBody: some View {
139+
ContextualForm(...) { form in
140+
let (field, fieldState, formState) = useController(name: FieldName.username, defaultValue: "")
141+
TextField("Username", text: field.value)
142+
}
143+
}
134144
```
135145

136146
</details>
@@ -140,6 +150,47 @@ var hookBody: some View {
140150
## SwiftUI Component
141151
👇 Click to open the description.
142152

153+
<details>
154+
<summary><CODE>ContextualForm</CODE></summary>
155+
156+
```swift
157+
struct ContextualForm<Content, FieldName>: View where Content: View, FieldName: Hashable {
158+
init(mode: Mode = .onSubmit,
159+
reValidateMode: ReValidateMode = .onChange,
160+
resolver: Resolver<FieldName>? = nil,
161+
context: Any? = nil,
162+
shouldUnregister: Bool = true,
163+
shouldFocusError: Bool = true,
164+
delayErrorInNanoseconds: UInt64 = 0,
165+
@_implicitSelfCapture onFocusField: @escaping (FieldName) -> Void,
166+
@ViewBuilder content: @escaping (FormControl<FieldName>) -> Content
167+
)
168+
169+
@available(macOS 12.0, iOS 15.0, tvOS 15.0, *)
170+
init(mode: Mode = .onSubmit,
171+
reValidateMode: ReValidateMode = .onChange,
172+
resolver: Resolver<FieldName>? = nil,
173+
context: Any? = nil,
174+
shouldUnregister: Bool = true,
175+
shouldFocusError: Bool = true,
176+
delayErrorInNanoseconds: UInt64 = 0,
177+
focusedFieldBinder: FocusState<FieldName?>.Binding,
178+
@ViewBuilder content: @escaping (FormControl<FieldName>) -> Content
179+
)
180+
```
181+
It wraps a call of `useForm` inside the `hookBody` and passes the FormControl value to a `Context.Provider<Form>`
182+
183+
It is identical to
184+
185+
```swift
186+
let form: FormControl<FieldName> = useForm(...)
187+
Context.Provider(value: form) {
188+
...
189+
}
190+
```
191+
192+
</details>
193+
143194
<details>
144195
<summary><CODE>Controller</CODE></summary>
145196

Sources/FormHook/Form.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ public struct ContextualForm<Content, FieldName>: View where Content: View, Fiel
588588
shouldUnregister: Bool = true,
589589
shouldFocusError: Bool = true,
590590
delayErrorInNanoseconds: UInt64 = 0,
591-
@_implicitSelfCapture onFocusedField: @escaping (FieldName) -> Void,
591+
@_implicitSelfCapture onFocusField: @escaping (FieldName) -> Void,
592592
@ViewBuilder content: @escaping (FormControl<FieldName>) -> Content
593593
) {
594594
self.formOptions = .init(
@@ -599,7 +599,7 @@ public struct ContextualForm<Content, FieldName>: View where Content: View, Fiel
599599
shouldUnregister: shouldUnregister,
600600
shouldFocusError: shouldFocusError,
601601
delayErrorInNanoseconds: delayErrorInNanoseconds,
602-
onFocusedField: onFocusedField
602+
onFocusField: onFocusField
603603
)
604604
self.contentBuilder = content
605605
}

Sources/FormHook/Hook/UseForm.swift

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public func useForm<FieldName>(
1717
shouldUnregister: Bool = true,
1818
shouldFocusError: Bool,
1919
delayErrorInNanoseconds: UInt64 = 0,
20-
@_implicitSelfCapture onFocusedField: @escaping (FieldName) -> Void
20+
@_implicitSelfCapture onFocusField: @escaping (FieldName) -> Void
2121
) -> FormControl<FieldName> where FieldName: Hashable {
2222
useForm(
2323
FormOption(
@@ -28,7 +28,7 @@ public func useForm<FieldName>(
2828
shouldUnregister: shouldUnregister,
2929
shouldFocusError: shouldFocusError,
3030
delayErrorInNanoseconds: delayErrorInNanoseconds,
31-
onFocusedField: onFocusedField
31+
onFocusField: onFocusField
3232
)
3333
)
3434
}
@@ -82,7 +82,7 @@ public struct FormOption<FieldName> where FieldName: Hashable {
8282
shouldUnregister: Bool,
8383
shouldFocusError: Bool,
8484
delayErrorInNanoseconds: UInt64,
85-
onFocusedField: @escaping (FieldName) -> Void
85+
onFocusField: @escaping (FieldName) -> Void
8686
) {
8787
self.mode = mode
8888
self.reValidateMode = reValidateMode
@@ -91,7 +91,7 @@ public struct FormOption<FieldName> where FieldName: Hashable {
9191
self.shouldUnregister = shouldUnregister
9292
self.shouldFocusError = shouldFocusError
9393
self.delayErrorInNanoseconds = delayErrorInNanoseconds
94-
self.focusedFieldOption = .init(onFocusedField)
94+
self.focusedFieldOption = .init(onFocusField)
9595
}
9696

9797
@available(macOS 12.0, iOS 15.0, tvOS 15.0, *)
@@ -115,11 +115,11 @@ public struct FormOption<FieldName> where FieldName: Hashable {
115115
}
116116

117117
struct FocusedFieldOption {
118-
private let _focusedFieldBinder: Any?
119-
private let onFocusedField: ((FieldName) -> Void)?
118+
private let anyFocusedFieldBinder: Any?
119+
private let onFocusField: ((FieldName) -> Void)?
120120

121121
var hasFocusedFieldBinder: Bool {
122-
_focusedFieldBinder != nil
122+
anyFocusedFieldBinder != nil
123123
}
124124

125125
var focusedFieldBindingValue: FieldName? {
@@ -130,26 +130,27 @@ public struct FormOption<FieldName> where FieldName: Hashable {
130130
}
131131

132132
@available(macOS 12.0, iOS 15.0, tvOS 15.0, *)
133-
private var focusedFieldBinder: FocusState<FieldName?>.Binding? {
134-
_focusedFieldBinder as? FocusState<FieldName?>.Binding
133+
var focusedFieldBinder: FocusState<FieldName?>.Binding? {
134+
anyFocusedFieldBinder as? FocusState<FieldName?>.Binding
135135
}
136136

137137
@available(macOS 12.0, iOS 15.0, tvOS 15.0, *)
138138
init(_ focusedFieldBinder: FocusState<FieldName?>.Binding) {
139-
self._focusedFieldBinder = focusedFieldBinder
140-
self.onFocusedField = nil
139+
self.anyFocusedFieldBinder = focusedFieldBinder
140+
self.onFocusField = nil
141141
}
142142

143-
init(_ onFocusedField: @escaping (FieldName) -> Void) {
144-
self._focusedFieldBinder = nil
145-
self.onFocusedField = onFocusedField
143+
init(_ onFocusField: @escaping (FieldName) -> Void) {
144+
self.anyFocusedFieldBinder = nil
145+
self.onFocusField = onFocusField
146146
}
147147

148148
func triggerFocus(on field: FieldName) {
149+
if let onFocusField {
150+
return onFocusField(field)
151+
}
149152
if #available(macOS 12.0, iOS 15.0, tvOS 15.0, *) {
150153
focusedFieldBinder?.wrappedValue = field
151-
} else {
152-
onFocusedField?(field)
153154
}
154155
}
155156
}

Tests/FormHookTests/FormHookTests.swift

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class FormHookTests: QuickSpec {
3737
shouldUnregister: true,
3838
shouldFocusError: true,
3939
delayErrorInNanoseconds: 0,
40-
onFocusedField: { _ in }
40+
onFocusField: { _ in }
4141
)
4242
formControl = .init(options: options, formState: .init(
4343
get: { formState },
@@ -129,7 +129,7 @@ final class FormHookTests: QuickSpec {
129129
shouldUnregister: false,
130130
shouldFocusError: true,
131131
delayErrorInNanoseconds: 0,
132-
onFocusedField: { _ in }
132+
onFocusField: { _ in }
133133
)
134134
formControl = .init(options: options, formState: .init(
135135
get: { formState },
@@ -170,7 +170,7 @@ final class FormHookTests: QuickSpec {
170170
shouldUnregister: true,
171171
shouldFocusError: true,
172172
delayErrorInNanoseconds: 0,
173-
onFocusedField: { _ in }
173+
onFocusField: { _ in }
174174
)
175175
formControl = .init(options: options, formState: .init(
176176
get: { formState },
@@ -348,7 +348,7 @@ final class FormHookTests: QuickSpec {
348348
shouldUnregister: true,
349349
shouldFocusError: true,
350350
delayErrorInNanoseconds: 0,
351-
onFocusedField: { _ in }
351+
onFocusField: { _ in }
352352
)
353353
formControl = .init(options: options, formState: .init(
354354
get: { formState },
@@ -406,7 +406,7 @@ final class FormHookTests: QuickSpec {
406406
shouldUnregister: true,
407407
shouldFocusError: true,
408408
delayErrorInNanoseconds: 0,
409-
onFocusedField: { _ in }
409+
onFocusField: { _ in }
410410
)
411411
formControl = .init(options: options, formState: .init(
412412
get: { formState },
@@ -544,7 +544,7 @@ final class FormHookTests: QuickSpec {
544544
shouldUnregister: true,
545545
shouldFocusError: true,
546546
delayErrorInNanoseconds: 0,
547-
onFocusedField: { _ in }
547+
onFocusField: { _ in }
548548
)
549549
formControl = .init(options: options, formState: .init(
550550
get: { formState },
@@ -853,7 +853,7 @@ final class FormHookTests: QuickSpec {
853853
shouldUnregister: true,
854854
shouldFocusError: true,
855855
delayErrorInNanoseconds: 0,
856-
onFocusedField: { _ in }
856+
onFocusField: { _ in }
857857
)
858858
formControl = .init(options: options, formState: .init(
859859
get: { formState },
@@ -917,7 +917,7 @@ final class FormHookTests: QuickSpec {
917917
shouldUnregister: true,
918918
shouldFocusError: true,
919919
delayErrorInNanoseconds: 0,
920-
onFocusedField: { _ in }
920+
onFocusField: { _ in }
921921
)
922922
formControl = .init(options: options, formState: .init(
923923
get: { formState },
@@ -1022,7 +1022,7 @@ final class FormHookTests: QuickSpec {
10221022
shouldUnregister: true,
10231023
shouldFocusError: true,
10241024
delayErrorInNanoseconds: 0,
1025-
onFocusedField: { _ in }
1025+
onFocusField: { _ in }
10261026
)
10271027
formControl = .init(options: options, formState: .init(
10281028
get: { formState },
@@ -1513,7 +1513,7 @@ final class FormHookTests: QuickSpec {
15131513
shouldUnregister: true,
15141514
shouldFocusError: true,
15151515
delayErrorInNanoseconds: 0,
1516-
onFocusedField: { _ in }
1516+
onFocusField: { _ in }
15171517
)
15181518
formControl = .init(options: options, formState: .init(
15191519
get: { formState },
@@ -1559,6 +1559,7 @@ final class FormHookTests: QuickSpec {
15591559
let result = await formControl.trigger(name: .a)
15601560
expect(result) == false
15611561

1562+
try await Task.sleep(nanoseconds: 110_000_000)
15621563
let fieldState = await formControl.getFieldState(name: .a)
15631564
expect(fieldState.isInvalid) == true
15641565
expect(fieldState.error) == ["Failed to validate a"]
@@ -1747,7 +1748,7 @@ final class FormHookTests: QuickSpec {
17471748
shouldUnregister: true,
17481749
shouldFocusError: true,
17491750
delayErrorInNanoseconds: 0,
1750-
onFocusedField: { _ in }
1751+
onFocusField: { _ in }
17511752
)
17521753
formControl = .init(options: options, formState: .init(
17531754
get: { formState },
@@ -1879,7 +1880,7 @@ final class FormHookTests: QuickSpec {
18791880
shouldUnregister: false,
18801881
shouldFocusError: true,
18811882
delayErrorInNanoseconds: 0,
1882-
onFocusedField: { _ in }
1883+
onFocusField: { _ in }
18831884
)
18841885
formControl = .init(options: options, formState: .init(
18851886
get: { formState },

0 commit comments

Comments
 (0)