重い処理をするときにインジゲータを表示する

ちょっと前にインジゲーターを表示するのにほんの少しだけ悩んだことがありました。 今日の@iphone_dev_jpで、同じ事で悩んでそうな人がいたので書いてみます。 例として、ボタンが押されたときにUIImagePickerをmodalViewで出すことを考えてみます。 UIImagePickerは初回起動に2〜3秒ぐらいかかる嫌な子です。

普通の人がやりそうな書き方

1
-(void)tapButton:(UIButton *)sender{ //indicator は UIActivityIndicatorView が入っているとします。 [indicator startAnimating]; UIImagePicker *picker = [[UIImagePickerController alloc] init]; [self presentModalViewController:_picker animated:YES]; [indicator stopAnimating]; }

これはアウトです。indicatorが表示されることなく、2〜3秒待たされます

正しい書き方

まずは結論からということで。 幾つか方法はありますが、自分が愛用しているのはこれです。

1
-(void)tapButton:(UIButton *)sender{ //indicator は UIActivityIndicatorView が入っているとします。 [indicator startAnimating]; [self performSelector:@selector(modalImagePicker) withObject:nil afterDelay:0.1]; } -(void)modalImagePicker{ UIImagePicker *picker = [[UIImagePickerController alloc] init]; [self presentModalViewController:_picker animated:YES]; [indicator stopAnimating]; }

こう書くことで、Indicatorがきちんと表示されます。

なぜ処理を分けなければいけないか

処理を別関数にしてperformSelector:withObject:afterDelay:でよんでますね。 これは一度システムに処理をしてもらうために行なっています。 View関連の処理は全般的に関数を呼んだ時にすぐ発生するものではありません。 例えばaddSubViewをしたときに、すぐには画面は更新されません。 後で追加するよ!みたいなフラグを立てて、システムに処理が戻ったときに実行されます。 [indicator startAnimating]も同じで、呼ばれてもすぐにインジゲーターは出ません。 システムに処理を一度戻さないと表示されないんです。 しかし、そのシステムが処理するに重い処理が来てしまったらインジゲータを回すことなく重い処理に入ってしまいます。 そこで、performSelector:withObject:afterDelay:を使っているわけです。 この関数は単純にx秒後に関数を実行するというだけのものですが、そのx秒間の間にシステムに処理が戻ります。 (戻らなかったらそのアプリ重すぎです) なので、重い処理をするときにはきちんとindicatorが表示されるというわけです。 つまり、ダメな例

  1. IndicatorをstartAnimatingする
  2. 重い処理をする
  3. IndicatorをstopAnimatingする
  4. システムが処理をする

いい例

  1. IndicatorをstartAnimatingする
  2. システムが処理をする
  3. 重い処理をする
  4. IndicatorをstopAnimatingする

ということです。
Indicatorがあるかないかで体感的な速度はかなり変わるので、積極的に使うといいと思います。