File tree Expand file tree Collapse file tree
Sources/ValidatorUI/Classes/SUI Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -7,6 +7,14 @@ import SwiftUI
77import ValidatorCore
88
99public extension View {
10+ /// Creates a view validation modifier.
11+ ///
12+ /// - Parameters:
13+ /// - item: The binding item to validate.
14+ /// - rules: The array of validation rules to apply to the item’s value.
15+ /// - content: A custom parameter attribute that constructs an error view from closures.
16+ ///
17+ /// - Returns: A modified view.
1018 func validate< T, ErrorView: View > (
1119 item: Binding < T > ,
1220 rules: [ any IValidationRule < T > ] ,
@@ -20,4 +28,23 @@ public extension View {
2028 )
2129 )
2230 }
31+
32+ /// Creates a view validation modifier.
33+ ///
34+ /// - Parameters:
35+ /// - validationContainer: The container to validate.
36+ /// - content: A custom parameter attribute that constructs an error view from closures.
37+ ///
38+ /// - Returns: A modified view.
39+ func validate< ErrorView: View > (
40+ validationContainer: any IFormValidationContainer ,
41+ @ViewBuilder content: @escaping ( [ any IValidationError ] ) -> ErrorView
42+ ) -> some View {
43+ modifier (
44+ FormValidationViewModifier (
45+ validationContainer: validationContainer,
46+ content: content
47+ )
48+ )
49+ }
2350}
Original file line number Diff line number Diff line change 1+ //
2+ // Validator
3+ // Copyright © 2023 Space Code. All rights reserved.
4+ //
5+
6+ import Combine
7+ import Foundation
8+ import ValidatorCore
9+
10+ public typealias ValidationPublisher = AnyPublisher < ValidationResult , Never >
11+
12+ // MARK: - FormField
13+
14+ @propertyWrapper
15+ public final class FormField < Value> : IFormField {
16+ // MARK: Properties
17+
18+ @Published
19+ /// The value to validate.
20+ private var value : Value
21+
22+ /// The validation.
23+ private let validator : IValidator
24+
25+ /// The validation rules.
26+ private let rules : [ any IValidationRule < Value > ]
27+
28+ public var wrappedValue : Value {
29+ get { value }
30+ set { value = newValue }
31+ }
32+
33+ // MARK: Initialization
34+
35+ public init (
36+ wrappedValue: Value ,
37+ validator: IValidator = Validator ( ) ,
38+ rules: [ any IValidationRule < Value > ]
39+ ) {
40+ value = wrappedValue
41+ self . validator = validator
42+ self . rules = rules
43+ }
44+
45+ // MARK: IFormField
46+
47+ public func validate( manager: some IFormFieldManager ) -> any IFormValidationContainer {
48+ let subject = CurrentValueSubject < Value , Never > ( value)
49+
50+ let publisher = $value
51+ . receive ( on: RunLoop . main)
52+ . handleEvents ( receiveOutput: { subject. send ( $0) } )
53+ . map { self . validator. validate ( input: $0, rules: self . rules) }
54+ . eraseToAnyPublisher ( )
55+
56+ let container = FormValidationContainter (
57+ value: subject,
58+ publisher: publisher,
59+ validator: validator,
60+ rules: rules
61+ )
62+
63+ manager. append ( validator: container)
64+
65+ return container
66+ }
67+ }
Original file line number Diff line number Diff line change 1+ //
2+ // Validator
3+ // Copyright © 2023 Space Code. All rights reserved.
4+ //
5+
6+ import Combine
7+ import Foundation
8+ import ValidatorCore
9+
10+ /// A type that represents a field on a form.
11+ public protocol IFormField {
12+ /// Performs field validation.
13+ ///
14+ /// - Note: Create a form validation container that keeps track of the validation.
15+ ///
16+ /// - Parameter manager: The form field manager.
17+ ///
18+ /// - Returns: A validation container.
19+ func validate( manager: some IFormFieldManager ) -> any IFormValidationContainer
20+ }
Original file line number Diff line number Diff line change 1+ //
2+ // Validator
3+ // Copyright © 2023 Space Code. All rights reserved.
4+ //
5+
6+ import Combine
7+ import Foundation
8+ import ValidatorCore
9+
10+ public final class FormFieldManager : IFormFieldManager {
11+ // MARK: Properties
12+
13+ @Published public var isValid = true
14+
15+ private var validators : [ any IFormValidationContainer ] = [ ]
16+
17+ // MARK: Initialization
18+
19+ public init ( ) { }
20+
21+ // MARK: IFormFieldManager
22+
23+ public func append( validator: some IFormValidationContainer ) {
24+ validators. append ( validator)
25+ validate ( )
26+ }
27+
28+ public func validate( ) {
29+ // swiftlint:disable:next contains_over_filter_is_empty
30+ isValid = validators
31+ . filter { $0. validate ( ) != . valid }
32+ . isEmpty
33+ }
34+ }
Original file line number Diff line number Diff line change 1+ //
2+ // Validator
3+ // Copyright © 2023 Space Code. All rights reserved.
4+ //
5+
6+ import Combine
7+ import Foundation
8+ import ValidatorCore
9+
10+ /// A type that manages the validation state of a form.
11+ public protocol IFormFieldManager : ObservableObject {
12+ /// A Boolean value that indicates whether all fields on a form are valid or not.
13+ var isValid : Bool { get }
14+
15+ /// Appends a new validator to the manager.
16+ ///
17+ /// - Parameter validator: The validation container that encompasses required validation logic.
18+ func append( validator: some IFormValidationContainer )
19+ }
Original file line number Diff line number Diff line change 1+ //
2+ // Validator
3+ // Copyright © 2023 Space Code. All rights reserved.
4+ //
5+
6+ import Combine
7+ import Foundation
8+ import ValidatorCore
9+
10+ public struct FormValidationContainter < T> : IFormValidationContainer {
11+ // MARK: Properties
12+
13+ public var value : FormValidatorValueSubject < T >
14+ public let publisher : ValidationPublisher
15+ public let validator : IValidator
16+ public let rules : [ any IValidationRule < T > ]
17+
18+ // MARK: Initialization
19+
20+ public init (
21+ value: FormValidatorValueSubject < T > ,
22+ publisher: ValidationPublisher ,
23+ validator: IValidator ,
24+ rules: [ any IValidationRule < T > ]
25+ ) {
26+ self . value = value
27+ self . publisher = publisher
28+ self . validator = validator
29+ self . rules = rules
30+ }
31+
32+ // MARK: IFormValidationContainer
33+
34+ public func validate( ) -> ValidationResult {
35+ validator. validate ( input: value. value, rules: rules)
36+ }
37+ }
Original file line number Diff line number Diff line change 1+ //
2+ // Validator
3+ // Copyright © 2023 Space Code. All rights reserved.
4+ //
5+
6+ import Combine
7+ import Foundation
8+ import ValidatorCore
9+
10+ public typealias FormValidatorValueSubject < Value> = CurrentValueSubject < Value , Never >
11+
12+ // MARK: - IFormValidationContainer
13+
14+ /// A container for form validation logic.
15+ public protocol IFormValidationContainer < Value> {
16+ associatedtype Value
17+
18+ /// The value subject used for form validation.
19+ var value : FormValidatorValueSubject < Value > { get }
20+
21+ /// The publisher responsible for emitting validation events.
22+ var publisher : ValidationPublisher { get }
23+
24+ /// The validator associated with this validation container.
25+ var validator : IValidator { get }
26+
27+ /// An array of validation rules to apply to the form field.
28+ var rules : [ any IValidationRule < Value > ] { get }
29+
30+ /// Performs form field validation.
31+ ///
32+ /// - Returns: The result of the validation process.
33+ func validate( ) -> ValidationResult
34+ }
Original file line number Diff line number Diff line change 1+ //
2+ // Validator
3+ // Copyright © 2023 Space Code. All rights reserved.
4+ //
5+
6+ import SwiftUI
7+ import ValidatorCore
8+
9+ public struct FormValidationViewModifier < ErrorView: View > : ViewModifier {
10+ // MARK: Properties
11+
12+ /// The result of the validation.
13+ @State private var validationResult : ValidationResult = . valid
14+
15+ /// A container for form validation logic.
16+ private let validationContainer : any IFormValidationContainer
17+
18+ /// A custom parameter attribute that constructs views from closures.
19+ @ViewBuilder private let content : ( [ any IValidationError ] ) -> ErrorView
20+
21+ // MARK: Initialization
22+
23+ public init (
24+ validationContainer: any IFormValidationContainer ,
25+ @ViewBuilder content: @escaping ( [ any IValidationError ] ) -> ErrorView
26+ ) {
27+ self . validationContainer = validationContainer
28+ self . content = content
29+ }
30+
31+ // MARK: ViewModifier
32+
33+ public func body( content: Content ) -> some View {
34+ VStack ( alignment: . leading) {
35+ content
36+ validationMessageView
37+ } . onReceive ( validationContainer. publisher) { result in
38+ self . validationResult = result
39+ }
40+ }
41+
42+ // MARK: Private
43+
44+ private var validationMessageView : some View {
45+ switch validationResult {
46+ case . valid:
47+ return EmptyView ( ) . eraseToAnyView ( )
48+ case let . invalid( errors) :
49+ return content ( errors) . eraseToAnyView ( )
50+ }
51+ }
52+ }
You can’t perform that action at this time.
0 commit comments