Skip to content

Commit 1256f82

Browse files
authored
Update README
1 parent 05f63a4 commit 1256f82

1 file changed

Lines changed: 23 additions & 101 deletions

File tree

README.md

Lines changed: 23 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,15 @@ struct MyField: View {
3636
let title: String
3737
let text: Binding<String>
3838
// Failed rules come from FormField during whole form validation.
39-
let failedRules: [TextValidationRule]
39+
let failedRules: [ValidationRule]
4040

4141
var body: some View {
4242
VStack(alignment: .leading) {
4343
TextField(title, text: text)
4444
.background(Color.white)
4545
// Display error.
46-
if failedRules.isEmpty == false {
47-
// Show error for first failed rule.
48-
Text(failedRules[0].errorMessage)
46+
if let errorMessage = failedRules.first?.message, errorMessage.isEmpty == false {
47+
Text(errorMessage)
4948
.foregroundColor(.red)
5049
}
5150
}
@@ -60,12 +59,12 @@ struct ContentView: View {
6059

6160
var body: some View {
6261
FormView( First failed field
63-
validate: .never, // Form will be validated on user action.
62+
validate: [.manual], // Form will be validated on user action.
6463
hideError: .onValueChanged // Error for field wil be hidden on field value change.
6564
) { proxy in
6665
FormField(
6766
value: $name,
68-
rules: [TextValidationRule.notEmpty(message: "Name field should no be empty")]
67+
rules: [ValidationRule.notEmpty(conditions: [.manual], message: "Name field should no be empty")]
6968
) { failedRules in
7069
MyField(title: "Name", text: $name, failedRules: failedRules)
7170
}
@@ -81,10 +80,11 @@ struct ContentView: View {
8180

8281
## Documentation
8382
### Valildation Behaviour
84-
Form validated at one of three specific times:
83+
The form can be validated under one or multiple conditions simultaneously, such as:
8584
* `onFieldValueChanged` - each field validated on it's value changed.
85+
* `onFieldFocus` - each field validated on focus gain.
8686
* `onFieldFocusLost` - each field validated on focus lost.
87-
* `never` - on call `proxy.validate()`. Default behaviour. First failed field is focused automatically.
87+
* `manual` - on call `proxy.validate()`. Default behaviour. First failed field is focused automatically.
8888

8989
### Error Hiding Behaviour
9090
Error for each field gets hidden at one of three specific times:
@@ -93,123 +93,45 @@ Error for each field gets hidden at one of three specific times:
9393
* `onFucusLost` - field with error lost focus.
9494

9595
### Custom Validation Rules
96-
One of two ways:
97-
1. Adopt protocol `ValidationRule`:
98-
```swift
99-
public protocol ValidationRule {
100-
associatedtype Value
101-
102-
func check(value: Value) -> Bool
103-
}
104-
```
10596

106-
2. Extend `TextValidationRule`:
97+
Extend `ValidationRule`:
10798
```swift
108-
extension TextValidationRule {
99+
extension ValidationRule {
109100
static var myRule: Self {
110-
TextValidationRule(message: "Text should not be empty") { value in
111-
value.isEmpty == false
101+
Self.custom(conditions: [.manual, .onFieldValueChanged, .onFieldFocus]) {
102+
return ($0.isEmpty == false, "Text should not be empty")
112103
}
113104
}
114105
}
115106
```
116107

117-
A banch of predefind rules for text validation is available via `TextValidationRule`:
108+
A banch of predefind rules for text validation is available via `ValidationRule`:
118109
* notEmpty - value not empty.
119110
* digitsOnly - value contains only digits.
120111
* lettersOnly - value contains only letters.
121112
* email - is valid email.
122113
* minLenght/maxLenth - value length greate/less.
123114
* regex - evaluate regular expresstion.
124-
* equalTo - value equal to another value. Useful for password confirmation.
125115
* etc...
126116

127-
### Outer Validation Rules
128-
If you need to display validation errors from external services (e.g., a backend), follow these steps:
129-
1. Create an `OuterValidationRule` enum:
117+
### External Validation Rules
118+
If you need to display validation errors from external services (e.g., a backend) use `ValidationRule.external`:
130119
```swift
131-
enum OuterValidationRule {
132-
case duplicateName
133-
134-
var message: String {
135-
switch self {
136-
case .duplicateName:
137-
return "This name already exists"
138-
}
120+
ValidationRule.external { [weak self] in
121+
guard let self else {
122+
return (true, "")
139123
}
140-
}
141-
```
142124

143-
2. Update the text field component:
144-
```swift
145-
struct TextInputField: View {
146-
let title: LocalizedStringKey
147-
@Binding var text: String
148-
let failedRules: [TextValidationRule]
149-
@Binding var outerRules: [OuterValidationRule]
150-
151-
var body: some View {
152-
VStack(alignment: .leading) {
153-
TextField(title, text: $text)
154-
.background(Color.white)
155-
if let errorMessage = getErrorMessage() {
156-
Text(errorMessage)
157-
.font(.system(size: 12, weight: .semibold))
158-
.foregroundColor(.red)
159-
}
160-
Spacer()
161-
}
162-
.frame(height: 50)
163-
.onChange(of: text) { _ in
164-
outerRules = []
165-
}
166-
}
167-
168-
private func getErrorMessage() -> String? {
169-
if let message = failedRules.first?.message {
170-
return message
171-
} else if let message = outerRules.first?.message {
172-
return message
173-
} else {
174-
return nil
175-
}
176-
}
177-
178-
init(
179-
title: LocalizedStringKey,
180-
text: Binding<String>,
181-
failedRules: [TextValidationRule],
182-
outerRules: Binding<[OuterValidationRule]> = .constant([])
183-
) {
184-
self.title = title
185-
self._text = text
186-
self.failedRules = failedRules
187-
self._outerRules = outerRules
188-
}
125+
return await self.availabilityCheckAsync($0)
189126
}
190-
```
191-
3. Update the text field initialization in your view:
192-
```swift
193-
TextInputField(
194-
title: "Name",
195-
text: $viewModel.name,
196-
failedRules: failedRules,
197-
outerRules: $viewModel.nameOuterRules
198-
)
199-
```
200127

201-
4. In your ViewModel, declare a `@Published` property of type `OuterValidationRule` and update its rules as needed:
202-
```swift
203-
class ContentViewModel: ObservableObject {
204-
@Published var nameOuterRules: [OuterValidationRule] = []
205-
206-
func applyNameOuterRules() {
207-
nameOuterRules = [.duplicateName]
208-
}
128+
private func availabilityCheckAsync(_ value: String) async -> (Bool, String) {
129+
let isAvailable = try await ...
130+
131+
return (isAvailable, "Not available")
209132
}
210133
```
211134

212-
213135
### Implementation Details
214136
FormView doesn't use any external dependencies.
215137

0 commit comments

Comments
 (0)