UIPickerVew によるメニュー選択 (1/3)不思議篇2012年11月10日 12:11

これでも、60秒タイマーとして動作する(左側)
不思議に思って、試作プロジェクトを作成してピッカーを組み込んでいろいろと実験してみた(写真右側)。プログラミングやデバッグの参考になれば幸いである。

  

タイマーの設定方法
分の数値 0がグレーにしてあるように、この位置で停止することは想定されていないし、普通に操作している限りまずはこのようにはならない。しかし、1 --> 0 方向にゆっくり戻しながら、素早くタップを繰り返し 0の位置でそっと指を離すと再現できる。最初は難しいが、慣れるとすぐにできるようになるから興味のある方は実際に試しててもらいたい (なお、逆方向 59 --> 0 に移動しながらはできない) 。

勝手にロジックを推測してみる
ドラムを回転して 0の位置で指を離した場合はつぎの行に強制的に移動させる。この辺は基本的にはだれが考えても同じのはず。

// 停止時のデリゲート
- (void) pickerView:(UIPickerView*)pickerView didSelectRow:(NSInteger)row
 inComponent:(NSInteger)component
{
    nowIndex_ = row;   
    // ドラムの表示 0の位置を index=0 と想定
    if (nowIndex_ == 0) {
        // 次の行に移動させる
        nowIndex_++;
        [picker_ selectRow:nowIndex_ inComponent:component animated:YES];
    }
}
これで、実際に試してみると意図した動作をすることがわかる。タイマーと同様にエンドレスのドラムを実現しないと逆方向は確かめられないないが、これだけでも解決すべき問題点の再現には十分だろう。

さらに推測をふくらます
この状態でタイマーをスタートしてから、キャンセルして戻ってくると 0 --> 1になっている。あるいはこの状態でアプリを終了してから再起動しても 0 --> 1に設定されている。多分タイマーを起動した時や、プログラムがバッググラウンドに遷移する際に呼ばれるデリゲードなどに、下記のようなコードが追加されているのだろう。

// 試作の場合は inComponent:0
[picker_ selectRow:nowIndex_ inComponent:1 animateed:NO]
これでも実用上は問題ないが問題点は未解決のままである。

(以下、 (2/3) 試行錯誤篇、(3/3)解決篇へ)

UI 操作と競合しないスレッドによるタイマー表示2012年07月21日 11:22

画面にUIPickerと、NSTimerを使ったタイマーを配置した場合、カウント中にピッカーを回転させるとその間カウンターの表示が停止する。これは、NSTimer、UIViewの表示がともにメインスレッドで動作するからです。
これを回避するには、タイマーのカウント部分を別のスレッドにすることですが、カウンターの更新部分はUI にアクセスすることになる( ...メインスレッドに用意したメソッドを呼び出す)あまりすっきりしない。iOS 4 から導入されたGCDならメインのUI にアクセスできるから扱いやすい。これを直接利用してもよいが、NSOperationQue もGCDが使われるようになっているからこれを使ってテストしてみたが、動作は快適である。

// タイマーの更新(テストコード)
- (void) countUpdate
{
 NSOperationQueue *queue = [[[NSOperationQueue alloc]init] autorelease];
 [queue addOperationWithBlock:^{

  while (1) {
   NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
   [mainQueue addOperationWithBlock:^{
    // カウントアップ
    count_++;
    // 再生時間を更新
    countLabel_.text = [NSString stringWithFormat:@"%d", count_];
   }];
   [NSThread sleepForTimeInterval:1.0f];
  }
 }];
}

参考:
 詳解 iOS 5 プログラミング

CALayerを使わない、UIViewアニメの停止/再開2012年07月20日 10:25

UIViewのアニメーションはサンプル例も多く作りやすいが、アニメーションの停止、再開をコントロールしたい場合、CALayerを扱ううことになって面倒になるようである。
目的にもよるが、例えばボタン等のUI部品の点滅アニメーションの場合は、CALayerを扱わなくても容易に実現できた。

// 点滅アニメーション
-(void) flashAnimation:(BOOL)start
{
 [UIView beginAnimations:nil context:NULL];
 [UIView setAnimationDuration:0.3f];
 [UIView setAnimationCurve:UIViewAnimationCurveLinear];

 if (start) {
  // -点滅開始-
  [UIView setAnimationDuration:0.8f];
  [UIView setAnimationRepeatCount:1000];
  [UIView setAnimationRepeatAutoreverses:YES];
  [button_ setAlpha:0.5f];
 } else {
  // -点滅停止-
  [UIView setAnimationBeginsFromCurrentState:YES];
  [UIView setAnimationRepeatCount:1];
  [button_ setAlpha:1.0f];
 }
 [UIView commitAnimations];
}

参考サイト:
http://stackoverflow.com/questions/6893181/how-to-flash-a-button-on-ui-thread