iOS5でのUITextFieldやUITextViewでのキーボード処理の注意点と対策




iOS5でのUIKeyboardWillShowNotificationの挙動が変わっていて
(まだ詳しく調べてないので、現状だけかもしれないですけど)
作り方によっては問題が発生してしまうという事があったので書いてみました。


UITextFieldUITextViewでキーボードの表示・非表示時にViewの設置場所によっては
キーボードに隠れてしまうケースがあります。
以下のように設置してTextFieldを選択するとキーボードが表示した際に隠れてしまいます。


こういった現象を回避するためにNSNotificationCenterUIKeyboardWillShowNotification
通知処理を行いキーボードのサイズ分TextFieldやTextViewの位置を調整したりします。

例えば以下の場合は通知処理を受け取ってTextFieldの位置をキーボードで隠れてしまう分
上にあげて、キーボードの決定ボタンで下に下げる処理を行っています。
TextFieldのY座標は266で定義してあります。

【キーボード通知定義】

    NSNotificationCenter *notification = [NSNotificationCenter defaultCenter];
    // キーボード表示時
    [notification addObserver:self selector:@selector(keyboardWillShow:) 
						 name: UIKeyboardWillShowNotification object:nil];
    // キーボード非表示時
	[notification addObserver:self selector:@selector(keyboardWillHide:) 
						 name: UIKeyboardWillHideNotification object:nil];

【キーボード通知受け取り】

/*
 *  キーボード表示
 */
- (void)keyboardWillShow:(NSNotification *)notificatioin
{
    // ここでTextFieldやTextViewの位置を変更する
    NSLog(@"keyboardWillShow");
    
    // キーボードに合わせたアニメーション処理
    [UIView animateWithDuration:0.3f
                     animations:^{
                         
                         // TextFieldのframe
                         CGRect viewRect  = uiTextField.frame;
                         
                         // キーボードのサイズを取得
                         CGRect keybord;
                         [[notificatioin.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keybord];
                         
                         // TextFieldの位置から(元のY座標+TextViewの高さ) - (Viewの高さ - キーボード)分、上げる(マイナスする)
                         viewRect.origin.y -= ((266.0f+viewRect.size.height)-(self.view.frame.size.height - keybord.size.height));
                         uiTextField.frame = viewRect;
                     }];
}

/*
 *  キーボード非表示
 */
- (void)keyboardWillHide:(NSNotification *)notificatioin
{
    // ここでTextFieldやTextViewの位置を戻す
    NSLog(@"keyboardWillHide");    
    
    // キーボードに合わせたアニメーション処理
    [UIView animateWithDuration:0.3f
                     animations:^{
                         
                         // TextFieldのframe
                         CGRect viewRect  = uiTextField.frame;
                         
                         // TextFieldのY座標を元の位置に定義
                         viewRect.origin.y = 266.0f;
                         uiTextField.frame = viewRect;
                     }];
}




上記の処理を実行してみると


こんな感じでTextFieldがキーボードに合わせて上下に移動します。
何かここだけ見るといい感じに見えます。

実は、ここまでの状態であれば特に問題はありません。
日本語用キーボードが表示された時に問題が発生します。
この問題はiOS4までの状態では起きずに、iOS5だと発生します。

ではキーボードの変更処理を行ってみます。


キーボード変更ボタンを押すたびにTextFieldが上へあがって行ってしまいました。
どんどん上にあがる?
という事は、もしかしたらUIKeyboardWillShowNotification
何度も呼び出されている可能性があります。

その場合はNSLog(@"keyboardWillShow");
何度も表示されているはずなので、さっそくコンソールを確認。

2011-10-23 15:58:41.828 KeybordSample[927:f803] keyboardWillShow
2011-10-23 16:08:18.768 KeybordSample[927:f803] keyboardWillShow
2011-10-23 16:08:20.730 KeybordSample[927:f803] keyboardWillShow
予想通り「英語用表示時、日本語用キーボード変換時、英語用キーボード変更時」の各3回分
keyboardWillShowが表示されていました。
iOS4まではUIKeyboardWillShowNotificationの通知は
キーボードが非表示の状態から表示された状態の場合のみに通知される仕組みでした。
ところがiOS5では、この通知がキーボードを変更する際にも通知されるようになっています。

変更と言っても、キーボード自体のサイズが変わらない状態だと通知はされません。
日本語キーボードはiOS5より拡張され上に入力候補が表示されるようになりました。
その分、iOS4までの統一だったキーボードのサイズよりも高さが変わります。

このように入力候補の有る無しの切り替えで
UIKeyboardWillShowNotificationが何度も通知されるようになりました。
先ほどのパターンだと「無し→有り→無し」の変更だったため、三回呼び出しがあったわけです。

iOS4までだとUIKeyboardWillShowNotificationは通知受け取りのkeyboardWillShow:メソッド内で
今のTextFieldのY座標からキーボードの高さで隠れてしまう分を引けば問題ありませんでした。
というのも非表示からの一度の通知のみですから、TextFieldのY座標は必ず初期値である266だったからです。

ですが、このiOS5からの通知変更によりTextFieldのY座標はすでに上記処理で
上に上げられた位置にいる可能性がある事を考慮しなければならなくなりました。
※いきなり日本語用キーボードが表示される時は二回通知がくるようです。


原因の切り出しはできたのでkeyboardWillShow:メソッド内の
TextFieldへの処理を以下のような形で対応してみました。
※どちらのパターンでも対処できると思います

【パターン1】

/*
 *  キーボード表示
 */
- (void)keyboardWillShow:(NSNotification *)notificatioin
{
    // ここでTextFieldやTextViewの位置を変更する
    NSLog(@"keyboardWillShow");
    
    // キーボードに合わせたアニメーション処理
    [UIView animateWithDuration:0.3f
                     animations:^{
                         
                         // TextFieldのframe
                         CGRect viewRect  = uiTextField.frame;
                         
                         // 追加 常にY座標を初期値にする
                         viewRect.origin.y = 266.0f;
                         
                         // キーボードのサイズを取得
                         CGRect keybord;
                         [[notificatioin.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keybord];
                                                         
                         // TextFieldの位置から(元のY座標+TextViewの高さ) - (Viewの高さ - キーボード)分、上げる(マイナスする)
                         viewRect.origin.y -=  ((266.0f+viewRect.size.height)-(self.view.frame.size.height - keybord.size.height));
                         uiTextField.frame = viewRect;
                     }];
}

【パターン2】

/*
 *  キーボード表示
 */
/*
 *  キーボード表示
 */
- (void)keyboardWillShow:(NSNotification *)notificatioin
{
    // ここでTextFieldやTextViewの位置を変更する
    NSLog(@"keyboardWillShow");
    
    // キーボードに合わせたアニメーション処理
    [UIView animateWithDuration:0.3f
                     animations:^{
                         
                         // TextFieldのframe
                         CGRect viewRect  = uiTextField.frame;
                         
                         // キーボードのサイズを取得
                         CGRect keybord;
                         [[notificatioin.userInfo valueForKey:UIKeyboardBoundsUserInfoKey] getValue: &keybord];
                         
                         // TextFieldの位置から(元のY座標+TextViewの高さ) - (Viewの高さ - キーボード)分、上げる(マイナスする)
                         //viewRect.origin.y -= ((266.0f+viewRect.size.height)-(self.view.frame.size.height - keybord.size.height));
                         
                         //Viewの高さ - キーボードの高さ - TextViewの高さを引いて、その位置にする
                         viewRect.origin.y = self.view.frame.size.height - keybord.size.height - viewRect.size.height;
                         uiTextField.frame = viewRect;
                     }];
}


【パターン1】では、常にTextFieldのY座標を初期値に変更し処理を行っています。
【パターン2】では、TextFieldのY座標にViewの高さ-から、キーボードの高さとTextViewの高さを引き
 キーボードの真上にくるようにしています。


上手く表示されるようになりましたね。

今回のような状態になっているアプリを最近よく見かけるので
UIKeyboardWillShowNotificationを利用する際は気をつけてください。

今回のキーボードの仕様変更で、固定値でキーボードのギリギリ上に
UITextFieldUITextViewを設置しているような場合は
日本語用キーボードの時に隠れてしまうので注意してください。

固定値で処理していたような場合はiOS5からUIKeyboardWillChangeFrameNotificationという
通知イベントが追加されているようなので、そっちで動的に処理を行うのが良いと思います。

では、今回はこの辺りで終わります。






Shareしていただけると励みになりますので
良ければよろしくお願いします