アプリによってはユーザーの入力値をバリデーションする必要がある場合があります。今回はバリデーションを行うタイミングについて書きたいと思います。
今回は以下のようなアカウント情報入力画面を考えてみます。
- UITextField が画面に表示されている
- NavigationBar には「保存」ボタンが設置されている
- 入力値が「カラ」もしくは「空白文字」以外の場合を正常な入力とする
- UITextField への入力値が不正な値の場合には「保存」ボタンを無効化する
UITextField のデリゲートでは不十分?
UISearchBarDelegate には以下のような入力後に呼ばれるデリゲートメソッドがあります。
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
しかし、UITextFieldDelegate には上記のようなデリゲートメソッドはなく、入力直前に呼ばれる以下のようなものしかありません。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
textField:shouldChangeCharactersInRange:replacementString: デリゲートメソッドで入力後のテキストを知るには NSString のこのメソッドを使うと簡単です。
- (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement
実際には textField:shouldChangeCharactersInRange:replacementString: デリゲート内で以下のようにすれば入力後のテキストが分かります。
NSString *result = [textField.text stringByReplacingCharactersInRange:range withString:string]; // textField は UITextField のインスタンス
余談ですが、デリゲートで渡される情報をそのまま使うだけなのでこの NSString のメソッドを意識したデリゲートメソッドのデザインじゃないのかなと思いました。
これだけでは不十分
先に挙げた方法で入力後のテキストを生成して、それに対して正規表現等で入力値をバリデーションすれば OK と思っていたらそれでは不十分です。見落としがちなことがあります。
iOS 3系以降では Undo / Redo ができるようになりました。
空の UITextField に適当な文字を入力し、すぐにデバイスを振って Undo してみてみるとtextField:shouldChangeCharactersInRange:replacementString: デリゲートは呼ばれずに入力値が空になると思います。textField:shouldChangeCharactersInRange:replacementString: デリゲート内だけでバリデーションを行っていると無効化するべき条件でも保存ボタンが有効化されたままになってしまいます。
Undo に対応
Undo に対応するための方法として1つ考えられるのは NSUndoManager の通知を受け取ってその中で別途バリデーションを実行することです。この作業は結構面倒ですがもっと簡単な方法があります。
UITextField は UIControl のサブクラスなので以下のように UIControlEventEditingChanged イベントにアクションをセットしてやります。
[textField addTarget:self action:@selector(textFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged]; // textFieldEditingChanged: というメソッドを実装しているものとする
この例では UITextField のテキストに変更があったときに textFieldEditingChanged: アクションが呼ばれます。この方法だと Undo / Redo 時にも呼ばれますし、UITextField への入力後にも呼ばれるようになります。
まとめ
今回のようなシンプルな要求の場合には textField:shouldChangeCharactersInRange:replacementString: デリゲートを実装しなくても UIControlEventEditingChanged にアクションをセットするだけで十分に対応可能です。注意点として、UITextField にセットしたアクションは頻繁に呼ばれることになるので処理内容が重くならないように気を配る必要があります。