Giter Club home page Giter Club logo

swiftuiformvalidator's Introduction

Gray shape shifter

SwiftUIFormValidator

Simple and clean approach to form validation



SwiftUIFormValidator

Introducing a clean, simple, and customizable approach to declarative form validation in SwiftUI. With our solution, you can easily validate user input and provide feedback in real-time, all while maintaining a sleek and intuitive user interface.

Discuss it on Product Hunt ๐Ÿฆ„

SwiftUINavigator Demo


Form Screenshot

Table of contents

โšก Usage

Basic Setup

  // 1 

import FormValidator

class ExampleForm: ObservableObject {
    // 2
    @Published
    var manager = FormManager(validationType: .immediate)

    // 3
    @FormField(validator: NonEmptyValidator(message: "This field is required!"))
    var firstName: String = ""

    // 4
    lazy var firstNameValidation = _firstName.validation(manager: manager)
}

struct ContentView: View {
    @ObservedObject var form = ExampleForm()
    @State var isSaveDisabled = true

    var body: some View {
        TextField("First Name", text: $form.firstName)
                .validation(form.firstNameValidation) // 5
    }

  private func validateForm() {
    let valid = form.manager.triggerValidation()
    // Validation result.
    print("Form valid: \(valid)")

    // Another way for checking validation.
    print("Is all valid: \(form.manager.isAllValid())")

    // Check if all fields have text. This is not validation check.
    print("Is all filled: \(form.manager.isAllFilled())")

    // Get an array with all validation messages.
    print("All validation messages array: \(form.manager.validationMessages)")

    // Get error messages as string, separated with a new line.
    print("All validation messages string: \(form.manager.errorsDescription())")
  }
}
  • Import FormValidator.
import FormValidator
  • Declare FormManager
@Published
var manager = FormManager(validationType: .immediate)

FormManager is a powerful class for controlling form validation in your application. It allows you to trigger validation and listen to changes in the form, and gives you the flexibility to control how and when validation is shown to the user.

  • Specify the data type and validation rules for the input field
@FormField(validator: NonEmptyValidator(message: "This field is required!"))
var firstName: String = ""

The annotation @FormField along with a validator can be used to declare the field to be validated:

  • To specify validation for a TextField in a view, use .validation() modifier.
TextField("First Name", text: $form.firstName)
        .validation(form.firstNameValidation)

Congratulations! Your field has now been validated. It's concise and simple!

Validators

Validator Description
CompositeValidator To combine multiple validators together into a single validator.
CountValidator.swift To validate whether a string is matching a spcecific length
NonEmptyValidator To validate whether a string is empty or blank
EmailValidator To validate if an email is valid
DateValidator To validate if a date falls within a range of dates
PatternValidator To validate if a pattern is matched
InlineValidator To validate if a condition is valid inline
PasswordValidator To validate if a password is valid
PasswordMatchValidator To validate if two password fields match each other and/or match a specific pattern
PrefixValidator To validate if a text has a prefix
SuffixValidator To validate if a text has a suffix

Custom Validators

To add custom validation logic to your form, you can conform to the Validatable or StringValidator protocols depending on the type of data you want to validate.

The Validatable protocol is used to validate any type of data, such as numbers, strings or dates.

The StringValidator protocol is used to validate string data, such as email addresses or passwords.

By adding custom validators that conform to these protocols, you can create highly specific and tailored validation logic that meets the unique needs of your form and its users.

Customizing the error view

To create a customized error view, simply add the custom view to validation modifier. This will allow you to tailor the error messages to your specific needs and provide a more user-friendly experience.

TextField("City", text: $form.city)
        .validation(form.cityValidation) { message in
            Text(message.uppercased())
                    .foregroundColor(.red)
                    .font(.system(size: 14))
        }

Inline Validation

If you need to perform quick validations, the InlineValidator can be a useful validator. It allows you to define your validation logic directly in the code, making it easy to write and maintain. Simply add @FormField(inlineValidator:) annotation and define your validation rules inline to quickly and efficiently validate the input field.

@FormField(inlineValidator: { value in
    guard value > 0 else {
        return "Age can not be โ‰ค 0"
    }
    guard value <= 50 else {
        return "Age can not be > 50"
    }
    return nil
})
var age: Int = 0

Validation Types

When using FormManager, you have the flexibility to choose between three different validation types based on your needs.

  • If you want to immediately validate user input as they are entering it, use FormManager(validationType: .immediate).

  • If you want to validate the entire form after the user has finished entering all the data, use FormManager(validationType: .deferred).

To initiate validation in this scenario, you need to call FormManager.triggerValidation(). This will trigger validation for all input fields in the form, based on the validation rules you have defined. Make sure to call this method at an appropriate time, such as when the user submits the form or when they have finished entering all the required data.

  • If you want to perform validation silently in the background without displaying any error messages to the user, use FormManager(validationType: .silent).

After triggering validation with FormManager.triggerValidation(), you can access the validation messages using FormManager.validationMessages(). It is important to note that displaying these messages to the user is your responsibility as the developer. You can choose to display the messages in a variety of ways, such as displaying them in a modal window. The goal is to provide clear and helpful feedback to the user so they can correct any errors and successfully submit the form.

Triggering Validation

To initiate form validation at any time, you can simply call the FormManager.triggerValidation() method.

React to Validation Change

If you need to react to changes in the validation status of your form, you can use the FormManager.$allValid and FormManager.$validationMessages properties.

The FormManager.$allValid property is a boolean value that indicates whether all fields in the form currently contain valid data. You can observe changes to this property to perform actions such as enabling or disabling the form's submit button based on its validation status. The FormManager.$validationMessages property is an array of error messages.

By reacting to these validation status changes, you can create a more dynamic and responsive user experience that helps guide the user towards successfully submitting the form.

.onReceive(form.manager.$allValid) { isValid in
    self.isSaveDisabled = !isValid
}
        .onReceive(form.manager.$validationMessages) { messages in
            print(messages)
        }

๐ŸŽ‰ Installation

Swift Package Manager

Xcode Projects

Select File -> Swift Packages -> Add Package Dependency and enter https://github.com/Open-Bytes/SwiftUIFormValidator.git.

Swift Package Manager Projects

You can add SwiftUIFormValidator as a package dependency in your Package.swift file:

let package = Package(
        //...
        dependencies: [
            .package(url: "https://github.com/Open-Bytes/SwiftUIFormValidator.git", .upToNextMajor(from: "1.0.0"))
        ]
        //...
)

From there, refer to SwiftUIFormValidator in target dependencies:

targets: [
    .target(
            name: "YourLibrary",
            dependencies: [
                .product(name: "FormValidator", package: "SwiftUIFormValidator"),
            ]
            //...
    ),
    // ...
]

Then simply import SwiftUIFormValidator wherever youโ€™d like to use the library.

CocoaPods

Use the following entry in your Podfile:

pod 'SwiftUIFormValidator'

Then run pod install.

Accio

Accio is a dependency manager based on SwiftPM which can build frameworks for iOS/macOS/tvOS/watchOS. Therefore the integration steps of SwiftUIFormValidator are exactly the same as described above. Once your Package.swift file is configured, run accio update instead of swift package update.

Don't forget to add import FormValidator to use the framework.

Carthage

Carthage users can point to this repository and use generated SwiftUIFormValidator framework.

Make the following entry in your Cartfile:

github "ShabanKamell/SwiftUIFormValidator"

Then run carthage update.

If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained over at Carthage.

Credit

Validation with SwiftUI & Combine

๐Ÿ‘ Contribution

We welcome all Pull Requests (PRs) to help improve this library and appreciate any contributions towards making it better. To ensure a smooth contribution process, please follow these guidelines:

  • Ensure that your PR addresses an existing issue or proposes a new feature that is aligned with the library's goals.
  • Before submitting a PR, please check to make sure that your changes do not introduce any new bugs or conflicts with existing code.
  • Include a clear and concise description of your changes in your PR, along with any relevant documentation or tests.
  • Be responsive to feedback and review comments from the maintainers and community members.

Thank you for your interest in contributing to our library and helping us make it better!

Changelog

Look at Changelog for release notes.

License

click to reveal License
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

swiftuiformvalidator's People

Contributors

shabankamell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

swiftuiformvalidator's Issues

Validating a picker

Hi!
I am trying to validate a picker and am a little stuck. I use the following inside the form

    @FormField(inlineValidator: { value in
        guard value < 0 || value > 1 else { return "Error" }
        return nil
    })
    @Published var percent: Float = 0
    lazy var percentValidation = _percent.validation(manager: manager, disableValidation: {
        self.amount != 0
    })

and the picker is defined as follows:

 Picker("", selection: self.$value) {
        ForEach(percentages, id: \.self) {
            Text($0, format: .percent).tag($0)
        }
    }
 .validate(validationContainer)

The problem is the Published inside the Formfield property wrapper. Without it the picker does not get updated and with it, I cannot use the validation, as the wrappedValues do not match.

Hi error messages not showing

I added an array of validations how is it:

lazy var firstNameValidation: ValidationContainer = {
        let validators: [StringValidator] = [
            CountValidator(count: 6, type: .greaterThanOrEquals),
            PrefixValidator(prefix: "st.")
        ]
        return $username.allValid(validators: validators, form: form)
    }()

and run manual trigger validation, then the message error not show

form.triggerValidation()

my form
@published var form = FormValidation(validationType: .immediate)

only show error when value changed

The following build commands failed: PhaseScriptExecution

Hi, I'm facing an error while using the SwiftUIFormValidator library during Azure DevOp Pipeline building process.

I'm currently developing an app using Kotlin MultiPlatform Mobile.

I'm using SwiftUIFormValidator for form validations in iOS.

I'm able to build it successfully in local machine but not in Azure DevOps Pipeline.

Here's the error log.

Running script '[CP-User] Build shared' โ–ธ Processing SwiftUIFormValidator-Info.plist ** BUILD FAILED **

The following build commands failed: PhaseScriptExecution [CP-User]\ Build\ shared /Users/runner/Library/Developer/Xcode/DerivedData/iosApp-dongzyrxhgtfzxexzqjllvyjwgcu/Build/Intermediates.noindex/Pods.build/Release-iphoneos/shared.build/Script-4552119A071AC6BAB7327E6434237EC3.sh (in target 'shared' from project 'Pods')

Kindly let me know the solution for the above error. Thank you.

Best practice for to create a validation that needs a value at run-time

I'm migrating to the 1.0 version of the library and I'm a bit stuck. I have a couple of validations that previously used inline validations. For example, I have a form that requires the user to enter the email twice and compare to make sure they are the same. It appears that I may need to follow the pattern setup with PasswordFormField and create a custom Form Field and a custom Validatable. That seems like a lot of work.

Am I missing something?

Excessive Validation Triggering on Input Change

Description:

Hi, I am facing an issue where the validation function is being triggered multiple times upon any change in the input field. Specifically, my print statement within the inline validation function is being executed 8 times with every change in the input. I have conducted further debugging to ensure that the issue is not on my end. I checked for any multiple initializations or other common pitfalls that might lead to the validation function being triggered excessively. Despite these checks, the issue persists, and the validation function continues to be called 8 times on every input change. I have also ensured that there are no other dependencies or external factors within my project that could be causing this behavior.

Code snippet:

final class SerialNumberFormManager: ObservableObject {
    @Published var manager = FormManager(validationType: .immediate)
    
    @FormField(inlineValidator: { value in
        print(value)
        return nil
    })
    var serialNumber: String =  ""
    
    lazy var serialNumberValidation = _serialNumber.validation(manager: manager)
}

Display issue with custom checkbox that uses a inline validation

I have a custom toggle style that displays an empty circle when not enabled and a checked circle when enabled.

struct CheckToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        Button {
            configuration.isOn.toggle()
        } label: {
            Label {
                configuration.label
            } icon: {
                Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
                    .foregroundColor(configuration.isOn ? .accentColor : .secondary)
                    .accessibility(label: Text(configuration.isOn ? "Checked" : "Unchecked"))
                    .imageScale(.large)
            }
        }
        .buttonStyle(PlainButtonStyle())
    }
}

This used to work fine, but after upgrading to the 1.0 version, the checkmark no longer displays when enabled. You can easily test this out in the Example Form by adding the following in ExampleForm:

 @FormField(inlineValidator: { value in
        return value ? nil : "You must acknowledge"
    })
    var ack: Bool = false
    lazy var ackValidation = _ack.validation(manager: manager)

And the following in one of the sections of ContentView:

            Toggle(isOn: $form.ack) {
                Text("Random Acknowledgement")
            }
            .validation(form.ackValidation)
            .toggleStyle(CheckToggleStyle())

This worked fine when using versions prior to 1.0. Any ideas?

README example should show working code

The code and explanation for the first example on the README page is as follows:

  // 1 
import FormValidator

class ExampleForm: ObservableObject {
    // 2
    @Published
    var manager = FormManager(validationType: .immediate)

    // 3
    @FormField(validator: NonEmptyValidator(message: "This field is required!"))
    var firstName: String = ""
}

struct ContentView: View {
    @ObservedObject var form = ExampleForm()
    @State var isSaveDisabled = true

    var body: some View {
        Section(header: Text("Required Fields Validation")) {
            TextField("First Name", text: $form.firstName)
                    .validation(form.firstNameValidation) // WHAT IS THIS?
        }
    }
}

That code contains:

.validation(form.firstNameValidation)

But form.firstNameValidation does not exist in the example!

So either there's a typo and this is meant to be .validation(form.firstName), or additional code is needed, in which case your example is misleading and it's not as "concise and simple" as it's made out to be.

The example should show working code.

Feature Request - Display multiple error messages for a field one at a time

Hi,

This is a great library by the way but I am just trying to work out the following, consider the following validation

    lazy var firstNameValidation: ValidationContainer = {
        let validators: [StringValidator] = [
            PrefixValidator(prefix: "mr."),
            CountValidator(count: 6, type: .greaterThanOrEquals)
        ]
        return $firstName.allValid(validators: validators, form: form)
    }()

Based on the sequence of the validation in the array. We should display the error message that is associated with the PrefixValidator first, then if it is satisfied we should then display the error message of the second CountValidator.

At the moment the implementation of func allValid( is setting the error message to be only the required one.

errorMessage: errorMessage().orIfEmpty(form.messages.required),

I think for allValid we should let it workout which error message needs to be displayed. To be honest I tried to modify the public static func create( which is relevant to allValid to set the error message dynamically as we are looping thru the validators but it was slightly tricky.

If there is a simpler way that I am not aware of to do display multiple error messages for the one field (1 at a time) please shed some light.

Thank you.

Invalid Exclude - 2 files not found

Invalid Exclude '/Users/me/Library/Developer/Xcode/DerivedData/snip/SourcePackages/checkouts/
SwiftUIFormValidator/Sources/Examples': File not found.

Invalid Exclude '/Users/me/Library/Developer/Xcode/DerivedData/snip/SourcePackages/
checkouts/SwiftUIFormValidator/Sources/Tests': File not found.

I've added the package and I'm getting these warnings...

I'm using Xcode 13.2.1

I think it's just that the Sources folder isn't required..
Tests and Examples appear in the parent folder.

Best regards,

Jules.

Validation fails in Swiftui long form

Hello, firstly I would like to thank you and say really nice work.

I came up to something really odd. I created a form in SwiftUI for iPhone that has 10 inputs and it takes more space than the screen size so 3-4 input are not visible and you need to scroll to see them.

The issue:
If I try to edit an existing record and change the first input and try to save, the triggerValidation fails. if I scroll down to see all inputs and then try triggerValidation again it works.

What I tried to verify this:
.onReceive(formInfo.form.$validationMessages) { messages in print(messages) } .onReceive(formInfo.form.$allValid) { isValid in print("isValid", isValid) }

I can see that all validation messages are empty and validation fails. With the isValid print I am able to see when the validation is changed to true. And as I scroll down it goes to true as soon as the last input appears on screen.

How to get localized validation messages?

I'm trying to setup a multilingual app with Localizable.strings. So far, the UI elements in SwiftUI all translate as expected. How do we get the validation messages to translate accordingly?

Validation error message size

Great framework - thanks for providing this.

Two issues:

  1. how can the error message size be increased
  2. how can the password validation be improved

for #1, after the textfield font size is increased, the error message size is very small, hard to read - how can this be increased ?

for #2, some custom hacks have been done to fix the typical logic for password and confirm password - such as:

  1. validate password that matches a regex - show that it fails when it does
  2. validate confirm password, and once validated, then compare the two

so far, this is not performing properly ...

attached below is the screen shot example of the issues of font sizes:

Screenshot 2023-05-08 at 8 03 45 AM

OR Validation

Is there any way to create an OR validation (enter field A or field B). Currently all validators are AND conjunctions.

View Handling it's own validation UI

Hi There

Thanks for sharing a great project. Would it be possible to allow a view to handle its own validation UI via a binding instead of putting an error message underneath view via the viewmodifier? For my use case I would like to change the text colour of the input and apply a red stroke around it in addition to showing the error message underneath.

Thanks!

Validators require published strings, which stops Decimal validation.

I'm trying to validate a decimal, however all the validators require strings.... (apart from the date validator)

image

My textfield requires a decimal....

            TextField(
                "My text",
                value: $viewModel.amount,
                format: .currency(code: Locale.current.currencyCode ?? "")
            )
            .keyboardType(.decimalPad)

Could the validators be changed to use generics perhaps ?

The validators also require a published variable.

I'd like to validate the the value is positive or negative, depending on state. e.g. income or expense.
Also that the value isn't zero.

I'm not sure how to proceed, any suggestions?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.