-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
440 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.build | ||
.index-build | ||
DerivedData | ||
/.previous-build | ||
xcuserdata | ||
.DS_Store | ||
*~ | ||
\#* | ||
.\#* | ||
.*.sw[nop] | ||
*.xcscmblueprint | ||
/default.profraw | ||
*.xcodeproj | ||
Utilities/Docker/*.tar.gz | ||
.swiftpm | ||
Package.resolved | ||
/build | ||
*.pyc | ||
.docc-build | ||
.vscode | ||
Utilities/InstalledSwiftPMConfiguration/config.json | ||
.devcontainer |
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,23 @@ | ||
included: | ||
- Sources | ||
|
||
disabled_rules: | ||
- line_length | ||
- identifier_name | ||
- type_name | ||
- file_length | ||
- type_body_length | ||
- function_body_length | ||
|
||
|
||
nesting: | ||
type_level: 4 | ||
|
||
|
||
custom_rules: | ||
trojan_source: | ||
regex: "[\u202A\u202B\u202D\u202E\u2066\u2067\u2068\u202C\u2069]" | ||
severity: error | ||
message: "Source should not contain characters that may be used in reordering attacks. https://trojansource.codes/trojan-source.pdf" | ||
|
||
reporter: "xcode" |
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,26 @@ | ||
// swift-tools-version: 6.0 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Formify", | ||
platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16), .watchOS(.v9)], | ||
products: [ | ||
.library( | ||
name: "Formify", | ||
targets: ["Formify"]), | ||
], | ||
dependencies: [ | ||
.package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", from: "0.57.0") | ||
], | ||
targets: [ | ||
.target( | ||
name: "Formify", | ||
plugins: [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLintPlugins")] | ||
), | ||
.testTarget( | ||
name: "FormifyTests", | ||
dependencies: ["Formify"] | ||
), | ||
] | ||
) |
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,185 @@ | ||
# 📃 Formify - Swift library for fast and easy form and input validation | ||
|
||
Formify is a Swift library designed for easy form and input validation. With Formify, you can easily validate all your | ||
TextField or TextEditor elements without needing to add any special changes or modifiers. | ||
|
||
### Key facts: | ||
|
||
* **👌 Ease of use:** It is very easy to implement Formify into your views and validate inputs - even in existing ones. | ||
* **🏎️ Speed:** With no special magic or any ObservableObjects and subscribers, the library is very fast | ||
* **📐 Size:** Very small footprint | ||
* **🚀 Performance:** Minimal performance impact | ||
|
||
## Requirements | ||
|
||
* XCode 16 | ||
* Swift 6 | ||
* iOS 16.0 / macOS 13.0 / tvOS 16.0 / watchOS 9.0 | ||
|
||
## Installation | ||
|
||
### Swift Package Manager | ||
|
||
Add the following to the Package.swift of your Swift package: | ||
|
||
``` | ||
dependencies: [ | ||
.package(url: "https://github.com/sanzaru/formify.git", from: "0.0.1") | ||
] | ||
``` | ||
|
||
### XCode | ||
|
||
Add the following package to your project: | ||
|
||
https://github.com/sanzaru/formify.git | ||
|
||
## Usage | ||
|
||
Formify uses `FormifyField` objects with `FormifyOperator` operators for input management and validation. | ||
All `FormifyField` objects come with a `value` attribute of type String. This value can be used as a binding inside a | ||
`TextField` or `TextEditor` view. | ||
|
||
You can check the validity by simply checking the `isValid` attribute or the `errors` array of the field. | ||
|
||
The simplest form of validation would be to declare a state variable inside a view, use the `value` of the | ||
`FormifyField` inside a TextField, and check the `isValid` attribute: | ||
|
||
```swift | ||
... | ||
|
||
@State private var formField = FormifyField(operators: [.required, .pattern(/[A-Za-z ]+/)]) | ||
|
||
... | ||
|
||
TextField("", text: $formField.value) | ||
.textFieldStyle(.plain) | ||
|
||
... | ||
|
||
Button { } label: { | ||
Text("Submit") | ||
} | ||
.disabled(!formField.isValid) | ||
|
||
... | ||
|
||
``` | ||
|
||
> [!NOTE] | ||
> You can also pass the `FormifyField` as a `@Binding` into views or use the object inside an `@ObservableObject` as a | ||
`@Published` variable. See the example for more information. | ||
|
||
|
||
## Operators | ||
|
||
| Name | Description | Example | | ||
| --- | --- | --- | | ||
| .required | If set, the field becomes required and cannot be left empty. | ```.required``` | | ||
| .minLength(Int) | If set, the value must be longer than the provided length. | ```.minLength(10)``` | | ||
| .maxLength(Int) | If set, the value must be shorter than the provided length. | ```.maxLength(10)``` | | ||
| .pattern(RegEx) | If set, the value must match the provided regular expression. | ```.pattern(/[a-zA-Z]/)``` | | ||
|
||
|
||
## Example | ||
|
||
The following example shows a simple view with a form containing three fields: a name, an email address, and a custom | ||
value. The name and email fields are required and validated against specific patterns. The name field also has minimum | ||
and maximum length validation, while the custom field is only required without additional validation: | ||
|
||
```swift | ||
import SwiftUI | ||
import Formify | ||
|
||
struct ContentView: View { | ||
struct FormFields { | ||
var name = FormifyField(operators: [.required, .minLength(10), .maxLength(20), .pattern(/[A-Za-z ]+/)]) | ||
var email = FormifyField("[email protected]", operators: [.pattern(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/)]) | ||
var custom = FormifyField("Preset value", operators: [.required]) | ||
|
||
var isValid: Bool { | ||
name.isValid && email.isValid && custom.isValid | ||
} | ||
} | ||
@State private var formFields = FormFields() | ||
|
||
var body: some View { | ||
NavigationStack { | ||
Form { | ||
// Name text field | ||
VStack(alignment: .leading) { | ||
Text("Name*") | ||
.foregroundColor(.teal) | ||
|
||
TextField("John Doe", text: $formFields.name.value) | ||
.textFieldStyle(.plain) | ||
.modifier(FormValidationErrorWrapperModifier(formField: $formFields.name)) | ||
} | ||
|
||
// Email text field | ||
VStack(alignment: .leading) { | ||
Text("Email*") | ||
.foregroundColor(.teal) | ||
|
||
TextField("[email protected]", text: $formFields.email.value) | ||
.textFieldStyle(.plain) | ||
.modifier(FormValidationErrorWrapperModifier(formField: $formFields.email)) | ||
} | ||
|
||
// Custom text field | ||
VStack(alignment: .leading) { | ||
Text("Custom") | ||
.foregroundColor(.teal) | ||
|
||
TextField("Some value", text: $formFields.custom.value) | ||
.textFieldStyle(.plain) | ||
.modifier(FormValidationErrorWrapperModifier(formField: $formFields.custom)) | ||
} | ||
} | ||
.navigationTitle("Example Form") | ||
.toolbar { | ||
ToolbarItem(placement: .topBarTrailing) { | ||
Button { print("Submit") } label: { | ||
Text("Submit") | ||
} | ||
.disabled(!formFields.isValid) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Additionally, the following example shows a simple ViewModifier that wraps all errors and displays a message underneath | ||
the TextField: | ||
|
||
```swift | ||
import SwiftUI | ||
import Formify | ||
|
||
struct FormValidationErrorWrapperModifier: ViewModifier { | ||
@Binding var formField: FormifyField | ||
|
||
func body(content: Content) -> some View { | ||
VStack(alignment: .leading) { | ||
content | ||
|
||
let errors = formField.errors | ||
if !errors.isEmpty, formField.isTouched { | ||
ForEach(errors, id: \.self) { error in | ||
Group { | ||
switch error { | ||
case .pattern: Text("Invalid pattern") | ||
case .required: Text("Required") | ||
case .minLength(let length): Text("Min length \(length) / \(formField.minLength ?? 0)") | ||
case .maxLength(let length): Text("Max length \(length) / \(formField.maxLength ?? 0)") | ||
} | ||
} | ||
.font(.caption) | ||
.foregroundColor(.red) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` |
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,15 @@ | ||
// | ||
// FormifyError.swift | ||
// Formify | ||
// | ||
// This file is part of the Formify Swift library: https://github.com/sanzaru/formify | ||
// Created by Martin Albrecht on 15.10.24. | ||
// Licensed under Apache License v2.0 | ||
// | ||
|
||
public enum FormifyError: Error, Hashable { | ||
case required | ||
case pattern | ||
case minLength(Int) | ||
case maxLength(Int) | ||
} |
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,84 @@ | ||
// | ||
// FormifyField.swift | ||
// | ||
// This file is part of the Formify Swift library: https://github.com/sanzaru/formify | ||
// Created by Martin Albrecht on 15.10.24. | ||
// Licensed under Apache License v2.0 | ||
// | ||
|
||
import Foundation | ||
|
||
public struct FormifyField { | ||
public var value: String { | ||
didSet { | ||
if value != oldValue { | ||
if !value.isEmpty { | ||
isTouched = true | ||
} | ||
|
||
validate() | ||
} | ||
} | ||
} | ||
|
||
public private(set) var errors = [FormifyError]() | ||
public private(set) var isRequired = true | ||
public private(set) var isTouched = false | ||
public private(set) var minLength: Int? | ||
public private(set) var maxLength: Int? | ||
public private(set) var pattern: Regex<Substring>? | ||
|
||
public var isValid: Bool { | ||
errors.isEmpty | ||
} | ||
|
||
public init(_ initialValue: String = "", operators: [FormifyOperator] = []) { | ||
self.value = initialValue | ||
|
||
operators.forEach { | ||
switch $0 { | ||
case .required: | ||
isRequired = true | ||
case .minLength(let length): | ||
minLength = length | ||
case .maxLength(let length): | ||
maxLength = length | ||
case .pattern(let regex): | ||
pattern = regex | ||
} | ||
} | ||
} | ||
} | ||
|
||
// MARK: Validation | ||
|
||
extension FormifyField { | ||
private mutating func validate() { | ||
errors = [] | ||
|
||
if isRequired && value.isEmpty { | ||
errors.append(.required) | ||
return | ||
} | ||
|
||
if let minLength, value.count < minLength { | ||
errors.append(.minLength(value.count)) | ||
} | ||
|
||
if let maxLength, value.count > maxLength { | ||
errors.append(.maxLength(value.count)) | ||
} | ||
|
||
if let pattern, (try? pattern.wholeMatch(in: value)) == nil { | ||
errors.append(.pattern) | ||
} | ||
} | ||
} | ||
|
||
// MARK: Collection | ||
|
||
public extension Collection where Element == FormifyField { | ||
var isValid: Bool { | ||
filter({ $0.errors.isEmpty }).isEmpty | ||
} | ||
} |
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,15 @@ | ||
// | ||
// FormifyOperator.swift | ||
// Formify | ||
// | ||
// This file is part of the Formify Swift library: https://github.com/sanzaru/formify | ||
// Created by Martin Albrecht on 15.10.24. | ||
// Licensed under Apache License v2.0 | ||
// | ||
|
||
public enum FormifyOperator { | ||
case required | ||
case pattern(Regex<Substring>) | ||
case minLength(Int) | ||
case maxLength(Int) | ||
} |
Oops, something went wrong.