iOS用アプリでパスワード入力UIを作ってるのですが、無効なパスワードを入力した時のフィードバックをどうしようか考えて、Mac OSX Lionのログイン画面のように、パスワード入力フィールドが「イヤイヤ」って感じで左右に揺れる感じにしようと思いました。いっそこれくらいCocoaの標準メソッドとしてあってくれても良さそうなものでしたが、UITextfieldのリファレンスを見る限りはなさそうだったので自分で実装してみました。さして高度なことしてるワケでもないですし、あんまりエレガントなソースでないかも知れないですが晒しておきます。もっとこうすりゃいいんじゃね?的なコメントも歓迎。
2011.10.27追記:
s1tnkさんにアドバイスいただいて大幅な簡略化に成功したので、更新版を貼ります。
//パスワードが不適切だった場合に入力欄を揺らす
-(void)shakePasscodeField {
[UIView setAnimationRepeatAutoreverses:TRUE]; //最初の位置に戻る
[UIView animateWithDuration:.05
animations:^(void) {
passcode.center = CGPointMake(passcode.center.x – 10, passcode.center.y);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:.05 animations:^(void) {
passcode.center = CGPointMake(passcode.center.x + 10, passcode.center.y);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:.05 animations:^(void) {
passcode.center = CGPointMake(passcode.center.x – 10, passcode.center.y);
}
completion:^(BOOL finish) {
[UIView animateWithDuration:.05 animations:^(void) {
passcode.center = CGPointMake(passcode.center.x + 10, passcode.center.y);
}];
}];
}];
}];
}
1メソッドで完結できたのでほとんど説明は不要だと思います。passcodeというのがテキスト欄の名前です。
旧バージョンとの違いは、
- setAnimationRpeatAutoreversesの存在を教わり、左に行って戻る、右に行って戻るの2フェーズで表現できた
- completion引数に次の処理をブロックで丸ごと角ことでアニメーションを連鎖記述できるようになった
- 結果として2フェーズx2回を1つのメソッドに完結させ、状態変数を一切なくせた
- durationは往復なので時間を倍の0.05にした
という辺りです。振動回数のカスタマイズはやりにくくなっちゃいましたが。
実際の動作はこんな感じ(IE9かiOSのSafari位しかインラインで再生できないかも)。
動画がインラインで再生されない人は、こちらからMP4をダウンロードして再生して下さい(44KB)。
■以下旧バージョン
前提として、.hファイル当たりで、
IBOutlet UITextField *passcode;
INT gakuburuCounter;
BOOL isFirstLoopForShakePasscodeField;
という3つのオブジェクト、変数を宣言してあるとします。
んで、.mファイルでメソッドを2つ。
//パスワードが不適切だった場合に入力欄を揺らす
-(void)shakePasscodeField {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:.1];
[UIView setAnimationDidStopSelector:@selector(shakePasscodeFieldLoop)];
switch (gakuburuCounter % 3) {
case 0:
//左へ動くフェーズ
[UIView setAnimationDuration:.025];
passcode.center = CGPointMake(passcode.center.x -10, passcode.center.y);
break;
case 1:
//左位置から右へ動くフェーズ
[UIView setAnimationDuration:.05];
passcode.center = CGPointMake(passcode.center.x +20, passcode.center.y);
break;
case 2:
//右位置から初期位置へ戻るフェーズ
[UIView setAnimationDuration:.025];
passcode.center = CGPointMake(pa
sscode.center.x -10, passcode.center.y);
break;
default:
break;
}
[UIView commitAnimations];
}
-(void)shakePasscodeFieldLoop {
if (isFirstLoopForShakePasscodeField == TRUE) {
gakuburuCounter = -1;
isFirstLoopForShakePasscodeField = FALSE;
}
if (gakuburuCounter < 5) {
gakuburuCounter++;
[self shakePasscodeField];
}
}
こんな感じ。
#つーか、そろそろコードをカラーリングしてくれるライブラリを導入した方がいいんだろうなぁ…
パスワードを検証してNGだったら
isFirstLoopForShakePasscodeField = TRUE;
[self shakePasscodeFieldLoop]
って感じで呼びます。
簡単に補足しておくと、テキストフィールドが定位置から左に10ピクセル、右に20ピクセル、左に10ピクセルと移動するのを1つのセットとします。ただしこれを単純に連続して呼ぶと、前のアニメーションが終わらないうちに次が実行されてしまい、正しく移動してくれないので、1つのアニメーションが終わる度に次のステップのアニメーションを実行する仕組みが必要でした。そこでそれを管理するメタメソッド(そんな言葉あるのか?)としてshakePasscodeFieldLoopを作りました。isFirstLoopForShakePasscodeFieldフラグがTRUEの状態で呼ばれると、ループカウンタgakuburuCounterをリセット(ここでは-1)して、フラグをFALSEにします。そしてgakuburuCounterをカウントアップしながらshakePasscodeFieldを呼び出します。呼ばれたshakePasscodeFieldではgakuburuCounterを3で割った答えを使って順に3つの動作パターンを実行し、アニメーションが完了した後でshakePasscodeFieldLoopを呼びます。このルートで呼ばれたshakePasscodeFieldLoopはisFirstLoopForShakePasscodeFieldがFALSEなのでカウンターはリセットせず、カウントアップだけして再度isFirstLoopForShakePasscodeを呼びます。これで、gakuburuCounterが-1~5にカウントアップする過程で、各フェーズが順に2ループ実行されます。
カスタマイズですが、-10とか20ってのが移動ピクセル数です。左に10、右に20、左に10って感じで辻褄を合わせます。つまり、振幅を倍にするなら、-20、40、-20とかするワケです。
shakePasscodeFieldLoop内の5を8にするとブルブルが3回になります。以降3を足すと1セット増加です。
shakePasscodeField内のUIView setAnimationDurationの値がブルブルの速度です。case 1のところは移動距離が倍なので所用時間も倍にしておくのがポイントです。