Skip to content

Commit 596ae17

Browse files
committed
Implement ValidationViewModifier
1 parent f468c0f commit 596ae17

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// Validator
3+
// Copyright © 2023 Space Code. All rights reserved.
4+
//
5+
6+
import SwiftUI
7+
8+
extension View {
9+
func eraseToAnyView() -> AnyView {
10+
AnyView(self)
11+
}
12+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//
2+
// Validator
3+
// Copyright © 2023 Space Code. All rights reserved.
4+
//
5+
6+
import SwiftUI
7+
import ValidatorCore
8+
9+
/// A validation view modifier.
10+
///
11+
/// The validation view modifier automatically tracks validation errors,
12+
/// uses the content view builder to construct an error view, and displays
13+
/// it to the user.
14+
///
15+
/// ```
16+
/// struct ContentView: View {
17+
/// @State private var text: String = "Text"
18+
///
19+
/// var body: some View {
20+
/// VStack {
21+
/// TextField("Title", text: $text)
22+
/// .modifier(
23+
/// ValidationViewModifier(
24+
/// item: $text,
25+
/// rules: [
26+
/// LengthValidationRule(max: 10, error: "The error message"),
27+
/// ],
28+
/// content: { errors in
29+
/// Text(errors.map { $0.message }.joined(separator: " "))
30+
/// }
31+
/// )
32+
/// )
33+
/// Spacer()
34+
/// }
35+
/// .padding()
36+
/// }
37+
/// }
38+
/// ```
39+
public struct ValidationViewModifier<T, ErrorView: View>: ViewModifier {
40+
// MARK: Properties
41+
42+
/// The result of the validation.
43+
@State private var validationResult: ValidationResult = .valid
44+
45+
/// The binding item to validate.
46+
@Binding private var item: T
47+
48+
/// A custom parameter attribute that constructs views from closures.
49+
@ViewBuilder private let content: ([any IValidationError]) -> ErrorView
50+
51+
/// The array of validation rules to apply to the item's value.
52+
public let rules: [any IValidationRule<T>]
53+
54+
/// Creates a new instance of the `ValidationViewModifier`.
55+
///
56+
/// - Parameters:
57+
/// - item: The binding item to validate.
58+
/// - rules: The array of validation rules to apply to the item's value.
59+
/// - content: A custom parameter attribute that constructs an error view from closures.
60+
public init(
61+
item: Binding<T>,
62+
rules: [any IValidationRule<T>],
63+
@ViewBuilder content: @escaping ([any IValidationError]) -> ErrorView
64+
) {
65+
_item = item
66+
self.rules = rules
67+
self.content = content
68+
}
69+
70+
// MARK: ViewModifier
71+
72+
public func body(content: Content) -> some View {
73+
VStack(alignment: .leading) {
74+
content
75+
.validation($item, rules: rules) { result in
76+
DispatchQueue.main.async {
77+
self.validationResult = result
78+
}
79+
}
80+
validationMessageView
81+
}
82+
}
83+
84+
// MARK: Private
85+
86+
private var validationMessageView: some View {
87+
switch validationResult {
88+
case .valid:
89+
return EmptyView().eraseToAnyView()
90+
case let .invalid(errors):
91+
return content(errors).eraseToAnyView()
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)