UITextFieldで文字数制限(非フリック入力対応?)

UITextFieldで文字数制限をかけてみようと検索したところ、
UITextFieldDelegateの

  • (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;

を使えば良い。
UITextFieldの入力制限を実装する - プログラマでありたい


と思って実装してみたんですが、
iPhone日本語キーボードのフリック入力では、設定した文字数まで入力できたんですが、
フリック入力をしないケータイ打ち(ボタン連打)の方法で入力しようとすると、
最後の文字が正しく入力できないということが判明。

たとえば最後の文字に「ん」を入力したい場合、
フリック入力だと、「わ」を押しながら上にフリックすると入力できるんだけど、
ケータイ打ちの場合は「わ」→「を」→「ん」と入力しようとすると
「わ」以降の入力を受け付けてくれません。


このとき、shouldChangeCharactersInRange:のrangeの値を出力してみたところ
locationが最大文字数+1,rangeが0になっていて、ケータイ打ちの場合、iPhoneの内部処理的には
「1文字追加して、前の1文字消す」的な処理をしてるかもしれませんね。(想像)
フリック入力をしない人のためにどうしようかと考えてみたんですが、
UITextFieldにUIControlEventEditingChangedのアクションをつける方法で対応を考えました。
UITextField でのバリデーションのタイミング (フェンリル | デベロッパーズブログ)


ただ、上記の方法を使う場合、UITextFieldの編集中に呼び出されるメソッド内で
UITextFieldの文字を変更してしまうと、ずっとUIControlEventEditingChangedを呼び出しつづける
無限ループが発生してしまいます。

なので、UITextField内の文字を変更するときは、 変更する直前に以下の方法をとる必要があります。

  1. addTarget:した対象を外してあげる
  2. テキストを追加
  3. 再度addTarget:し直す


コードとして書くとこんな感じになります。
今回の場合はtextField_というUITextFIeldに対し、文字数の入力制限をかけています。
今回の制限文字数は8文字にしています。
制限したい文字数を変えたい場合は、textFieldEditingChanged:の
maxLengthの値を適宜変更してください。

- (void)viewDidLoad
{
    [super viewDidLoad];
   
    [textField_ addTarget:self action:@selector(textFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged];
}

-(IBAction)textFieldEditingChanged:(id)sender {
    int maxLength = 8;
    UITextField *txt = (UITextField *)sender;
    NSString *str = txt.text;
    if ([str length] >= maxLength) {
        [textField_ removeTarget:self action:@selector(textFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged];
        textField_.text = [str substringToIndex:maxLength];
        [textField_ addTarget:self action:@selector(textFieldEditingChanged:) forControlEvents:UIControlEventEditingChanged];
    }
}

なんとかこれで動くようにはなったのですが、
このばあい9文字以降を入れようとすると、「文字は表示されないけど、予測変換が出てしまう」
という問題が出てしまいますがね・・・。

2011/9/13 追記
実はこれでもケータイ打ちには対応できなかったので、
結局のところUITextFieldDelegateで「確定」が押されたときに
テキストを切るように設定しました。

// メッセージ入力終了処理
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    NSString *str = nameTextField_.text;
    int maxLength = 8;
    if ([str length] >= maxLength) {
        nameTextField_.text = [str substringToIndex:maxLength];
    }
    
    [textField resignFirstResponder];
    return YES;
}