2017年3月29日水曜日

バリデーション&エラー表示付きの入力フォームを作ってみる

このエントリーをはてなブックマークに追加

概要

animation

この記事では、AndroidのTextInputLayoutのように、エラー表示付きの入力フォームを作る方法について説明します。ついでに、入力値のバリデーションも行います。

サンプル

Text-Input-Layout@githubに動作するプロジェクトがあります。

仕組み

TextInputView

image1

タイトルラベル、必須マークラベル、テキストフィールド、エラーラベルを持つカスタムビューとTextInputViewを用意します。

クラス全体をIBDesignable、title, isRequired, placeholder, errorMessageを@IBInspectableとし、Interface Builderから値を入力できるようにします。 また、computed propertyとして、isValidatedを宣言します。isValidatedを呼ぶと、バリデーションを行い、必要に応じてエラーを表示します。 これにより、呼び出し元は、TextInputValidationTypeを最初に設定すれば、バリデーションロジックやエラーの表示・非表示を気にする必要はありません。

enum TextInputValidationType: Int {
    case none
    case email
}

@IBDesignable
class TextInputView: UIView {
    @IBOutlet fileprivate weak var titleLabel: UILabel!
    @IBOutlet fileprivate weak var titleViewWidthConstraint: NSLayoutConstraint!
    @IBOutlet fileprivate weak var requiredLabel: UILabel!
    @IBOutlet weak var textField: UITextField!
    @IBOutlet fileprivate weak var errorLabel: UILabel!

    @IBInspectable var title: String = "" {
        didSet {
            titleLabel?.text = title
            titleLabel?.sizeToFit()
            titleViewWidthConstraint?.constant = titleLabel.frame.width
        }
    }
    @IBInspectable var isRequired: Bool = true {
        didSet {
            requiredLabel?.isHidden = !isRequired
        }
    }
    @IBInspectable var placeholder: String = "" {
        didSet {
            textField?.placeholder = placeholder
        }
    }
    @IBInspectable var errorMessage: String = "" {
        didSet {
            errorLabel?.text = errorMessage
        }
    }

    var validationType: TextInputValidationType = .none
    var text: String {
        return textField?.text ?? ""
    }
    var isValidated: Bool {
        errorLabel.isHidden = true
        switch validationType {
        case .email:
            errorLabel.isHidden = isEmail(text: text)
        default:
            break
        }

        if text.isEmpty {
            errorLabel.isHidden = !isRequired
        }

        return errorLabel.isHidden
    }
}

TextInputViewの使い方

Interface Builder上でUIViewを追加し、ClassをTextInputViewに設定します。 そうすると、IBInspectableを設定したプロパティをInterface Builder上から操作できることがわかります。isRequiredをOn(true)にすると、(Required)が表示されていることがわかります。

image2

逆にisRequiredをOff(false)にすると、(Required)が非表示になったことがわかります。

image3

呼び出し元では、validationTypeやtextFieldのキーボードタイプを指定します。 また、isValidatedを呼び出すことで、バリデーション条件をクリアしているかを知ることができます。 このように、TextInputView側にバリデーションやエラー表示の処理を任せることで、呼び出し元は、スッキリとしたコードとなります。 (ちなみに、IBInspectableに、enum型を指定することができれば、より簡易に書けそうですが、現状はできません。)

class ViewController: UIViewController {
    @IBOutlet fileprivate weak var emailTextInputView: TextInputView!
    @IBOutlet fileprivate var textInputViews: [TextInputView]!
    @IBOutlet fileprivate weak var resultLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        emailTextInputView.validationType = .email
        emailTextInputView.textField.keyboardType = .emailAddress
        emailTextInputView.textField.returnKeyType = .done
    }

    @IBAction private func onValidationButtonClick(_ sender: UIButton) {
        let areValidated = !textInputViews.map({ $0.isValidated }).contains(false)
        resultLabel.text = areValidated ? "Validated :)" : "Invalidated :("
    }
}

サンプル

Text-Input-Layout@githubに動作するプロジェクトがあります。