IOS field validation - quick and easy

imageValidation of input fields is perhaps the most common task in mobile applications. Each application that takes the form of authorization and registration also has a number of input tools from the user, which give rise to the expected sadistic - sophisticated excitement of testers. An advanced, technically competent community of developers has learned how to deal effectively with them by embedding regular expressions in the source code.

As a rule, when you need to limit yourself to registration and authorization, you don’t need to make special efforts to achieve your goal - the code is transferred from one project to another, almost without any changes. And, most importantly, without increasing the man-hours for further maintenance of this code. But the world would not be so simple if it were not for the creative impulses of UI / UX designers who are inclined, contrary to the established tradition, logic and common sense, to invent new ways of interacting with the end user, placing on the form several, in their opinion, necessary controls, accessibility which depends on the Cartesian set of conditions for checking the validity of a large number of input fields and other control controls. Unfortunately, this situation can hardly be called rare.

The developer’s irritation grows in proportion to how often he has to violate DRY principles: on the one hand, professional pride does not allow compromises, and on the other, copy-paste code is the most effective way to avoid a long testing cycle - debugging. Monotonous copied code is much easier to maintain than ideologically verified unique "bike". The search for an alternative not only squanders the developer’s creativity, but also adds dependencies to the project.

At the same time, the iOS SDK presents some extremely underestimated features that easily scale to many tasks related not only to validation - declarative programming greatly simplifies the life of the developer. Of course, the author knows the implacable camp of loversfriends of using “undiluted” code, but since the author of the article began to develop professional interfaces by developing a graphical UI for MS DOS, there’s no great desire to waste time creating another perfect class, and if you can use it with equal value mouse - preference will be given to the mouse. Accordingly, it outlines how to minimize the amount of code in order to speed up and simplify the development process.

Disclaimer:
, . . , — .

A typical minimum task is as follows:

There are login and password input fields, and an authorization button. It is necessary that the authorization button changes state (isEnable) depending on what is contained in the input fields.

A slightly more advanced version of this task looks like this:

We have the email, password and phone number input fields, as well as two buttons - registration and request for an SMS code for the entered phone. The registration button should only be available when the correct data is entered in each of the fields. And the code request button is when the phone number field is valid.

Typical solution- creation of interdependent flags by combining the “if” and “switch” statements in one view controller. Difficulty will increase with the number of controls involved in the process. A much more advanced solution would be to create a state machine. The solution is excellent - but time-consuming, in addition, having a high entry threshold - and this is by no means what I want to implement to the lazy (AKA "true") developer.

Lema about a lazy developer
, , . (« » ), , .

The general idea of ​​the proposed is as follows:

There is a class for the input field and a class for the validation manager. The input field is inherited, as expected, from UITextField. Validation manager - inherited from UIControl, contains a collection of validated elements (it’s not at all necessary to be descendants of UITextField) and implements the “observer” pattern. In addition, he acts as a manager for other controls that must change their accessibility status when the state of validated elements changes. In other words, if the field contains an invalid email, the registration button should not be available.



The input fields are self-validating - they can answer the question whether the data contained in them is valid and change their state. Some developers prefer to use a separate validator class (and its descendants) to validate input fields, but this practice makes it impossible to fully enjoy the declarative and scalable portability of the code. It is important for us that the input field or any other validated control supports a simple interface of the form:

@objc protocol IValidatable : class {
    varisValid: Bool { get }
}

To add validation to your project, you need to add 4 files from the example located in the GitHub repository .

  • The DSTextField class implements an input field with an indication of the validation process.
  • The DSValidationManager class is an observer that provides the functionality we need.
  • Extension StringOptional + Utils - contains only one method that uses a regular expression to check whether the text of the current line is valid.
  • UIView + Layer is just a convenient way to add a frame of a given width to any descendant of UIView.

Strictly speaking, if you want to implement validation for any other control in the same way, then most likely you only need the DSValidationManager. The rest is used only for your convenience.

The validation process is shown in the video (gif).


As you can see, before the input starts, the field is in default state, and validation occurs as data is entered. If you leave the field before the validation is completed, an inscription appears on the field notifying of the error. The registration button will be unavailable until both fields become valid.

A similar situation develops on the second screen (which becomes available only after activation of registration). But on the second screen, the validation of fields and buttons occurs independently - the registration button becomes available only if all fields have failed. At the same time, the SMS send button is already available when the field with the phone number is valid.

And now the trick:The entire project contains only one class for the view controller, the same one that Xcode created when creating the project from the template. It is completely empty. But even it is not used. This is not difficult to verify with the help of the dispatcher.



An attentive reader noticed that to the right of the responder is an Object from the standard Xcode component palette. For it, the Class parameter is overridden by the DSValidationManager class . The same trick was done for input fields, with the only difference being that the DSTextField class is used there .

Now all our programming comes down to the following simple steps:

  1. Associate the verifiedControls collection from the ValidationManager with input fields.


  2. Associate the managedControls collection from ValidationManager with the button you want to manage.


  3. Associate input fields in the opposite direction with the ValidationManager, making it a delegate.

  4. In the input field, set a regular expression for validation and an error message, as well as other custom and standard properties through the standard Xcode manager. In principle, everything except the regular expression can be left "as is". It only requires you to use the keyboard, and then, just to copy-paste the formula from tyrnet. If the error message is missing, then it simply will not be displayed on the screen.


The DSValidationManager code is ugly primitive.

import UIKit

@objc protocol IValidationManager : class {
    func verificated()
}

@objc protocol IValidatable : class {
    var isValid: Bool { get }
}

class DSValidationManager : UIControl, IValidationManager
{
    @IBOutlet var verifiedControls: [UIControl]?
    @IBOutlet var managedControls: [UIControl]?

    private (set) var valid : Bool = false {
        didSet {
            self.sendActions(for: .valueChanged)
        }
    }
    
    overrideinit(frame: CGRect) {
        super.init(frame: frame)
        self.verificated()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.verificated()
    }

    func verificated() {
        self.checkVerifiedControls()
        self.controlsAction()
    }

    private func checkVerifiedControls() {
        guard let list:[IValidatable] = self.verifiedControls?.filter({$0 isIValidatable}) as? [IValidatable]
            else { return }
        self.valid = list.filter({!$0.isValid}).count==0
    }
    
    private func controls Action() {
        guard let list = self.managedControls else { return }
        for item in list {
            item.isEnabled = self.valid
        }
    }
}

As you can see, he receives a notification from one collection, and acts on another.

An observer makes it a property that we did not even consider in this example. You can drag the connection from the ValidationManager object directly to the controller, add an event there similar to clicking a button, and process the manager’s state directly in the controller. If you really want it. This can be useful if you should not just lock the control, but also do some useful work (for example, play a march or show AlertView).

Of all the TextEdit code, it should be noted only that the field must take care to set the " isValid " property correctly , and pull the " verified () method"From his delegate. However, note that the input field does not use a reference to the manager object, but a collection. This allows you to bind an input field to several managers.

Accordingly, the method will need to be pulled by each of the delegates.

    var isValid: Bool {
        return self.text.verification(self.expression, required:self.valueRequired)
    }

    @IBOutlet var validateDelegates: [IValidationManager]?

    private func handleDelegateAction(_ sender: UITextField) {
        guard let list = self.validateDelegates else { return }

        for validateDelegate in list {
            validateDelegate.verificated()
        }
    }

Now, if you need to make up a new form with input fields, you don’t even need to set custom property values ​​- it will be enough to copy the required Runtime attributes in bulk and apply them to a new field (do not forget to just replace the class name).


Generally speaking, the trick using the “object” in the form header can be used much more widely than just for field validation - it reacts with MVP to MVVM without any Rx. Its most common use is network sharing and notification of changes in the CoreData model.

All Articles