@@ -10,31 +10,27 @@ import Hooks
1010import FormHook
1111
1212enum FormFieldName : String , CaseIterable {
13- case username
14- case password
15-
16- var title : String {
17- switch self {
18- case . username:
19- return " Username "
20- case . password:
21- return " Password "
22- }
23- }
13+ case firstName = " First name "
14+ case lastName = " Last name "
15+ case password = " Password "
16+ case gender = " Gender "
17+ case email = " Email "
18+ case phone = " Phone "
19+ case dob = " Date of birth "
2420
2521 func messages( for validationResult: Bool ) -> [ String ] {
2622 if validationResult {
2723 return [ ]
2824 }
29- switch self {
30- case . username:
31- return [ " Username is required " ]
32- case . password:
33- return [ " Password is required " ]
34- }
25+ return [ " \( rawValue) is required " ]
3526 }
3627}
3728
29+ enum Gender : String , CaseIterable {
30+ case male = " Male "
31+ case female = " Female "
32+ }
33+
3834struct ContentView : HookView {
3935
4036 @FocusState var focusField : FormFieldName ?
@@ -43,49 +39,216 @@ struct ContentView: HookView {
4339 var hookBody : some View {
4440 ContextualForm { ( form: FormControl < FormFieldName > ) in
4541 Form {
46- VStack ( spacing: 16 ) {
47- ForEach ( FormFieldName . allCases, id: \. self) { name in
48- Controller (
49- name: name,
50- defaultValue: " " ,
51- rules: NotEmptyValidator ( name. messages ( for: ) )
52- ) { field, fieldState, _ in
53- switch name {
54- case . username:
55- TextField ( name. title, text: field. value)
56- . focused ( $focusField, equals: name)
57- . textContentType ( . username)
58- . submitLabel ( . next)
59- case . password:
60- SecureField ( name. title, text: field. value)
61- . focused ( $focusField, equals: name)
62- . textContentType ( . password)
63- . submitLabel ( . go)
64- }
42+ Section ( " Name " ) {
43+ firstNameView
44+ lastNameView
45+ }
46+
47+ Section ( " Password " ) {
48+ passwordView
49+ }
50+
51+ Section ( " Basic Info " ) {
52+ genderView
53+ dobView
54+ emailView
55+ phoneView
56+ }
57+
58+ Button ( " Submit " ) {
59+ focusField = nil
60+ hideKeyboard ( )
61+ Task {
62+ try await form. handleSubmit ( onValid: { _, _ in
6563
66- if let error = fieldState. error. first {
67- Text ( error)
68- }
69- }
70- }
71-
72- Button ( " Submit " ) {
73- self . focusField = nil
74- hideKeyboard ( )
75- Task {
76- do {
77- try await form. handleSubmit ( onValid: { _, _ in
78-
79- } , onInvalid: { _, errors in
80- self . focusField = FormFieldName . allCases. first ( where: errors. errorFields. contains ( _: ) )
81- } )
82- } catch { }
83- }
64+ } , onInvalid: { _, errors in
65+ focusField = FormFieldName . allCases. first ( where: errors. errorFields. contains ( _: ) )
66+ } )
8467 }
8568 }
8669 }
8770 }
8871 }
72+
73+ var firstNameView : some View {
74+ Controller (
75+ name: FormFieldName . firstName,
76+ defaultValue: " " ,
77+ rules: NotEmptyValidator ( FormFieldName . firstName. messages ( for: ) )
78+ ) { field, fieldState, _ in
79+ let textField = TextField ( field. name. rawValue, text: field. value)
80+ . focused ( $focusField, equals: field. name)
81+ . submitLabel ( . next)
82+
83+ if let error = fieldState. error. first {
84+ VStack {
85+ textField
86+ Text ( error)
87+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
88+ }
89+ } else {
90+ textField
91+ }
92+ }
93+ }
94+
95+ var lastNameView : some View {
96+ Controller (
97+ name: FormFieldName . lastName,
98+ defaultValue: " " ,
99+ rules: NotEmptyValidator ( FormFieldName . lastName. messages ( for: ) )
100+ ) { field, fieldState, _ in
101+ let textField = TextField ( field. name. rawValue, text: field. value)
102+ . focused ( $focusField, equals: field. name)
103+ . submitLabel ( . next)
104+
105+ if let error = fieldState. error. first {
106+ VStack {
107+ textField
108+ Text ( error)
109+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
110+ }
111+ } else {
112+ textField
113+ }
114+ }
115+ }
116+
117+ var passwordView : some View {
118+ Controller (
119+ name: FormFieldName . password,
120+ defaultValue: " " ,
121+ rules: NotEmptyValidator ( FormFieldName . password. messages ( for: ) )
122+ ) { field, fieldState, _ in
123+ let textField = SecureField ( field. name. rawValue, text: field. value)
124+ . focused ( $focusField, equals: field. name)
125+ . textContentType ( . newPassword)
126+ . submitLabel ( . go)
127+
128+ if let error = fieldState. error. first {
129+ VStack {
130+ textField
131+ Text ( error)
132+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
133+ }
134+ } else {
135+ textField
136+ }
137+ }
138+ }
139+
140+ var genderView : some View {
141+ Controller (
142+ name: FormFieldName . gender,
143+ defaultValue: Gender . male
144+ ) { field, fieldState, _ in
145+ let picker = Picker ( field. name. rawValue, selection: field. value) {
146+ ForEach ( Gender . allCases, id: \. self) { gender in
147+ Text ( gender. rawValue)
148+ }
149+ }
150+
151+ if let error = fieldState. error. first {
152+ VStack {
153+ picker
154+ Text ( error)
155+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
156+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
157+ }
158+ } else {
159+ picker
160+ }
161+ }
162+ }
163+
164+ var dobView : some View {
165+ Controller (
166+ name: FormFieldName . dob,
167+ defaultValue: Date ( )
168+ ) { field, fieldState, _ in
169+ let picker = DatePicker (
170+ selection: field. value,
171+ in: ... Date . now,
172+ displayedComponents: . date
173+ ) {
174+ Text ( field. name. rawValue)
175+ }
176+
177+ if let error = fieldState. error. first {
178+ VStack {
179+ picker
180+ Text ( error)
181+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
182+ }
183+ } else {
184+ picker
185+ }
186+ }
187+ }
188+
189+ @ViewBuilder
190+ var phoneView : some View {
191+ let phoneRegEx = " ^ \\ d{3}- \\ d{3}- \\ d{4}$ "
192+ let phonePatternValidator = PatternMatchingValidator < String > ( pattern: phoneRegEx) { result in
193+ if result {
194+ return [ ]
195+ }
196+ return [ " Phone is not correct " ]
197+ }
198+
199+ Controller (
200+ name: FormFieldName . phone,
201+ defaultValue: " " ,
202+ rules: NotEmptyValidator ( FormFieldName . phone. messages ( for: ) ) . and ( validator: phonePatternValidator)
203+ ) { field, fieldState, _ in
204+ let textField = TextField ( field. name. rawValue, text: field. value)
205+ . focused ( $focusField, equals: field. name)
206+ . keyboardType ( . numberPad)
207+ . submitLabel ( . next)
208+
209+ if let error = fieldState. error. first {
210+ VStack {
211+ textField
212+ Text ( error)
213+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
214+ }
215+ } else {
216+ textField
217+ }
218+ }
219+ }
220+
221+ @ViewBuilder
222+ var emailView : some View {
223+ let emailRegEx = " [A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+ \\ .[A-Za-z]{2,64} "
224+ let emailPatternValidator = PatternMatchingValidator < String > ( pattern: emailRegEx) { result in
225+ if result {
226+ return [ ]
227+ }
228+ return [ " Email is not correct " ]
229+ }
230+
231+ Controller (
232+ name: FormFieldName . email,
233+ defaultValue: " " ,
234+ rules: emailPatternValidator
235+ ) { field, fieldState, _ in
236+ let textField = TextField ( field. name. rawValue, text: field. value)
237+ . focused ( $focusField, equals: field. name)
238+ . keyboardType ( . emailAddress)
239+ . submitLabel ( . next)
240+
241+ if let error = fieldState. error. first {
242+ VStack {
243+ textField
244+ Text ( error)
245+ . font ( . system( size: 10 ) ) . foregroundColor ( . red)
246+ }
247+ } else {
248+ textField
249+ }
250+ }
251+ }
89252}
90253
91254extension View {
0 commit comments