2010年3月16日火曜日

MKMapViewでユーザーの現在地を表示している場合、MKMapView作成直後に破棄するとクラッシュする場合の対処

MKMapViewでユーザーの現在地を表示している場合、作成した直後に破棄するとクラッシュする場合があるとわかった。

トレースを見ると「MKDotBounceAnimation animationDidStop:finished」で問題が出ているようだ。この段階では地図上には何のアノテーションも追加していない。

つまり、どうやらMKMapView組み込みのユーザー現在地表示用のアノテーションのアニメーションに原因があるようだ。地図でユーザーの現在地を表示すると例のあの青いやつがぴょんぴょんハネるアニメーションがあるが、多分あれだ。

その後しつこくテストした結果、ハネるアニメーションだけでなく、誤差範囲を示す円を描画するアニメーションでも問題が出る場合があった。

他にも困っている人がいないか調べたが、AppleのDeveloper Forumにも、サイト上にもいた。
OmegaDelta >> Blog Archive >> MKDotBounceAnimation animationDidStop bug

上記のサイトではいくつか対処方法が提案されているので、それらをすべて試してみた。

1.アニメーションが落ち着くまで待ってから破棄する
確かにクラッシュする確率は減ったが、それでもクラッシュしないわけではない。そもそも、開発者の都合でユーザーを待たせるわけにはいかない。

2.MKDotBounceAnimationのanimationDidStop:finishedの実装を書き換える
1の方法と比べてすぐに破棄できるメリットがあったが、これでも完璧にクラッシュを防ぐことはできなかった。

3.一度MKMapViewを作ったら2度と破棄しない
当たり前だが、これは完璧だった。

しかし、メモリの節約のためにどうしてもMKMapViewを破棄しなければならない場合はどうするのか?試行錯誤の末にたどり着いた結論は、MKMapView組み込みのユーザー現在地表示用のアノテーションを使わないことだ。

「MKMapView.showUserLocation = NO;」はもちろん効果がある。ユーザー位置を表示する必要がないならこれが一番簡単だ。

しかし、ユーザー位置を表示する必要がある場合はMKMapViewDelegate.viewForAnnotationを使ってMKUserLocationをカスタムのものと入れ替えるのが一つの方法だ。

ViewControllerの初期化メソッドやIBで「MKMapView.showUserLocation = YES;」をしておく。

そしてViewControllerなどのMKMapViewDelegateに、次のようにviewForAnnotationメソッドを追加しておく。

- (MKAnnotationView *)mapView:(MKMapView *)_mapView viewForAnnotation:(id )_annotation {
 
 //annotationがユーザー位置表示用の場合
 if ([_annotation isKindOfClass:[MKUserLocation class]]) {
  
  NSLog(@"viewForAnnotation() annotationがMKUserLocationクラス");
  
  //ユーザー位置表示用MKAnnotationView作成
  if (!ulocView) {
   UIImage *img = [UIImage imageNamed:@"userlocation.png"];
   ulocView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"CustomUserLocation"];
   ulocView.image = img;
  }
  
  return ulocView;
  
 } else {
  NSLog(@"viewForAnnotation() 普通のの〜");
  return nil;
 }
 
}

すると、地図が読み込まれてユーザー位置を表示しようとした時に、カスタムのMKAnnotationViewがMKUserLocationの代わりとして追加される。

しかし、このままでは問題がある。ユーザー位置が自動的には更新されないのだ。この事を考えると、どうやらMKMapViewのユーザー位置表示は、アプリケーションと関係なく独立してCLLocationManagerを使って位置更新を行っているらしい。

そこでまずViewControllerなどのMKMapViewDelegateに次のようなメソッドを追加しておく。GMGeocodedAnnotationはMKAnnotationプロトコルを実装した適当なクラスで構わない。

- (void)moveUserLocationAnnotationView:(CLLocationCoordinate2D)coord {
 if (ulocView) {
  NSLog(@"ユーザー位置表示用MKAnnotationView移動 lat=%f, lng=%f", coord.latitude, coord.longitude);
  GMGeocodedAnnotation *anno = ulocView.annotation;
  anno.coordinate = coord;
 }
}

そして、アプリケーションで使っているCLLocationManagerで呼び出されるdidUpdateToLocationでこのメソッドを呼び出すようにしておけばいい。

MKUserLocationはハネたり点滅したり色々面白い効果があるが、もしそうしたアニメーションが必要ならMKAnnotationViewをさらに改造すればよいだろう。


この問題は早く解決されるべきだと思うが、実際にやってみると独自のユーザー位置表示も悪くない、からまぁいいかぁ(^^);