generated from StanfordBDHG/SwiftPackageTemplate
-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Introduce SpeziValidation ## ♻️ Current situation & Problem [SpeziAccount](https://github.com/StanfordSpezi/SpeziAccount) currently implements a lot of logic around input validation that can easily separated into its own, self-contained target. This PR factors out those validation-based components and moves them into a new target in SpeziViews: SpeziValidation. This includes models to build `ValidationRule`s and views like `VerifiableTextField` that automatically perform validation on their input (given it's configured in the environment) and display potential validation errors. Would recommend to visit the nicely structured documentation for a great overview of the different components :) ## ⚙️ Release Notes * Introduce SpeziValidation. A single-step solution to all client-side, input validation. ### Breaking Changes * The `onTapFocus` modifiers were replaced by a new, simple to use `focusOnTap()` modifier that doesn't require passing in a FocusState anymore. ## 📚 Documentation Components coming from SpeziAccount were already documented in great detail. This PR created the respective string catalog and structured all new files within a new SPM target. ## ✅ Testing Appropriate UI testing was added. ## 📝 Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
- Loading branch information
Showing
29 changed files
with
1,745 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
Sources/SpeziValidation/Configuration/ValidationDebounceDuration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import SwiftUI | ||
|
||
|
||
/// The debounce duration for Validation Engine | ||
struct ValidationDebounceDurationKey: EnvironmentKey { | ||
static let defaultValue: Duration = .seconds(0.5) | ||
} | ||
|
||
|
||
extension EnvironmentValues { | ||
/// The configurable debounce duration for input submission. | ||
/// | ||
/// Having a debounce like this, ensures that validation error messages don't get into the way when a user | ||
/// is actively typing into a text field. | ||
/// This duration is used to debounce repeated calls to ``ValidationEngine/submit(input:debounce:)`` where `debounce` is set to `true`. | ||
public var validationDebounce: Duration { | ||
get { | ||
self[ValidationDebounceDurationKey.self] | ||
} | ||
set { | ||
self[ValidationDebounceDurationKey.self] = newValue | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
Sources/SpeziValidation/Configuration/ValidationEngine+Configuration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// | ||
// This source file is part of the Stanford Spezi open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import SwiftUI | ||
|
||
|
||
extension ValidationEngine { | ||
/// The configuration of a ``ValidationEngine``. | ||
public struct Configuration: OptionSet, EnvironmentKey, Equatable { | ||
/// This configuration controls the behavior of the ``ValidationEngine/displayedValidationResults`` property. | ||
/// | ||
/// If ``ValidationEngine/submit(input:debounce:)`` is called with empty input and this option is set, then the | ||
/// ``ValidationEngine/displayedValidationResults`` will display no failed validations. However, | ||
/// ``ValidationEngine/displayedValidationResults`` will still display all validations if validation is done through a manual call to ``ValidationEngine/runValidation(input:)``. | ||
public static let hideFailedValidationOnEmptySubmit = Configuration(rawValue: 1 << 0) | ||
|
||
/// This configuration controls the behavior of the ``ValidationEngine/inputValid`` property. | ||
/// | ||
/// If this configuration is set, the Validation Engine will treat no input (a validation engine | ||
/// that was never run) as being valid. Otherwise, invalid. | ||
public static let considerNoInputAsValid = Configuration(rawValue: 1 << 1) | ||
|
||
/// Default value without any configuration options. | ||
public static let defaultValue: Configuration = [] | ||
|
||
|
||
public let rawValue: UInt | ||
|
||
|
||
public init(rawValue: UInt) { | ||
self.rawValue = rawValue | ||
} | ||
} | ||
} | ||
|
||
|
||
extension EnvironmentValues { | ||
/// Access the ``ValidationEngine/Configuration-swift.struct`` of a ValidationEngine through the environment. | ||
/// | ||
/// - Note: Supplying a value into the environment is always an additive change! | ||
public var validationConfiguration: ValidationEngine.Configuration { | ||
get { | ||
self[ValidationEngine.Configuration.self] | ||
} | ||
set { | ||
self[ValidationEngine.Configuration.self].formUnion(newValue) | ||
} | ||
} | ||
} |
116 changes: 116 additions & 0 deletions
116
Sources/SpeziValidation/Resources/Localizable.xcstrings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
{ | ||
"sourceLanguage" : "en", | ||
"strings" : { | ||
"VALIDATION_RULE_MINIMAL_EMAIL" : { | ||
"localizations" : { | ||
"de" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Diese E-Mail ist ungültig." | ||
} | ||
}, | ||
"en" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "The provided email is invalid." | ||
} | ||
}, | ||
"es" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "El correo electrónico proporcionado no es válido." | ||
} | ||
} | ||
} | ||
}, | ||
"VALIDATION_RULE_NON_EMPTY" : { | ||
"localizations" : { | ||
"de" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Diese Feld kann nicht leer sein." | ||
} | ||
}, | ||
"en" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "This field cannot be empty." | ||
} | ||
}, | ||
"es" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Este espacio no puede estar vacío." | ||
} | ||
} | ||
} | ||
}, | ||
"VALIDATION_RULE_PASSWORD_LENGTH %lld" : { | ||
"localizations" : { | ||
"de" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Dein Passwort muss mindestens %lld Zeichen lang sein." | ||
} | ||
}, | ||
"en" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Your password must be at least %lld characters long." | ||
} | ||
}, | ||
"es" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Tu contraseña debe tener al menos %lld caracteres." | ||
} | ||
} | ||
} | ||
}, | ||
"VALIDATION_RULE_UNICODE_LETTERS" : { | ||
"localizations" : { | ||
"de" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Du kannst nur Zeichen verwenden." | ||
} | ||
}, | ||
"en" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "You must only use letters." | ||
} | ||
}, | ||
"es" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Debes usar solo letras." | ||
} | ||
} | ||
} | ||
}, | ||
"VALIDATION_RULE_UNICODE_LETTERS_ASCII" : { | ||
"localizations" : { | ||
"de" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Du kannst nur Zeichen aus dem englischen Alphabet verwenden." | ||
} | ||
}, | ||
"en" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "You must only use standard English letters." | ||
} | ||
}, | ||
"es" : { | ||
"stringUnit" : { | ||
"state" : "translated", | ||
"value" : "Debes usar solo letras estándar en inglés." | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
"version" : "1.0" | ||
} |
6 changes: 6 additions & 0 deletions
6
Sources/SpeziValidation/Resources/Localizable.xcstrings.license
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
This source file is part of the Stanford Spezi open-source project | ||
|
||
SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
|
||
SPDX-License-Identifier: MIT |
93 changes: 93 additions & 0 deletions
93
Sources/SpeziValidation/SpeziValidation.docc/SpeziValidation.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# ``SpeziValidation`` | ||
|
||
Perform input validation and visualize it to the user. | ||
|
||
<!-- | ||
This source file is part of the Spezi open-source project | ||
SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
SPDX-License-Identifier: MIT | ||
--> | ||
|
||
## Overview | ||
|
||
`SpeziValidation` can be used to perform input validation on `String`-based inputs and provides easy-to-use | ||
mechanism to communicate validation feedback back to the user. | ||
The library is based on a rule-based approach using ``ValidationRule``s. | ||
|
||
### Performing Validation | ||
|
||
The only thing you have to do, is to set up the ``SwiftUI/View/validate(input:rules:)-5dac4`` modifier for your | ||
text input. | ||
Supply your input and validation rules. | ||
|
||
The below code example shows a basic validation setup. | ||
Note that we are using the ``VerifiableTextField`` to automatically visualize validation errors to the user. | ||
|
||
```swift | ||
@State var phrase: String = "" | ||
|
||
var body: some View { | ||
Form { | ||
VerifiableTextField("your favorite phrase", text: $phrase) | ||
.validate(input: phrase, rules: .nonEmpty) | ||
} | ||
} | ||
``` | ||
|
||
> Note: The inner views can access the ``ValidationEngine`` using the [Environment](https://developer.apple.com/documentation/swiftui/environment/init(_:)-8slkf) | ||
property wrapper. | ||
|
||
### Managing Validation | ||
|
||
Parent views can access the validation state of their child views using the ``ValidationState`` property wrapper | ||
and the ``SwiftUI/View/receiveValidation(in:)`` modifier. | ||
|
||
The code example below shows | ||
how you can use the validation state of your subview to perform final validation on a button press. | ||
|
||
```swift | ||
@ValidationState var validation | ||
|
||
var body: some View { | ||
Form { | ||
// all subviews that collect data ... | ||
|
||
Button("Submit") { | ||
guard validation.validateSubviews() else { | ||
return | ||
} | ||
|
||
// save data ... | ||
} | ||
} | ||
.receiveValidation(in: $validation) | ||
} | ||
``` | ||
|
||
## Topics | ||
|
||
### Performing Validation | ||
|
||
- ``ValidationRule`` | ||
- ``SwiftUI/View/validate(input:rules:)-5dac4`` | ||
- ``SwiftUI/View/validate(input:rules:)-9vks0`` | ||
|
||
### Managing Validation | ||
|
||
- ``ValidationState`` | ||
- ``SwiftUI/View/receiveValidation(in:)`` | ||
|
||
### Configuration | ||
|
||
- ``SwiftUI/EnvironmentValues/validationConfiguration`` | ||
- ``SwiftUI/EnvironmentValues/validationDebounce`` | ||
|
||
### Visualizing Validation | ||
|
||
- ``VerifiableTextField`` | ||
- ``ValidationResultsView`` | ||
- ``FailedValidationResult`` |
Oops, something went wrong.