Kesinの知見置き場

知見を共有していきたいじゃないですか

MapKitでのピンの追加・削除、直線の描画

iOSアプリの入門書などでMapKitを使って地図を表示する方法は広く紹介されていますが、地図にピンを追加したり、削除したりする方法があまり載っていなかったので紹介したいと思います。


執筆時ではXcode4.4.1でARCを使うコードとなっています。Objective-CXcodeの進化は速いのでこのバージョンより新しい(古い)Xcodeではこの記事の通りにやっても動かない可能性があります。ご注意ください。

ピンの追加

例として渋谷駅にピンを置いてみます

annotation.title = @"渋谷駅";
annotation.subtitle = @"駅";
annotation.coordinate = CLLocationCoordinate2DMake(35.658611, 139.701111);
[mapView addAnnotation:annotation];


ピンをタップするとtitle, subtitleがバルーンに表示されます

ピンのバルーンにボタンを追加する

ピンをタップすると表示されるバルーンの本当の名称はコールアウトと呼ばれていて、自分でカスタムすることができます。コールアウトの左側や右側にボタンを追加するのは簡単です。

//アノテーションビューが作られたときのデリゲート。addAnotationするときに呼ばれる
- (void)mapView:(MKMapView*)mapView didAddAnnotationViews:(NSArray*)views{
    // アノテーションビューを取得する
    for (MKAnnotationView* annotationView in views) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
        // コールアウトの左側のアクセサリビューにボタンを追加する
        annotationView.leftCalloutAccessoryView = button;
    }
}

コールアウトに追加したボタンがタップされたときの処理はmapCiew: annotationView: calloutAccessoryControlTappedというデリゲートメソッドに記述します。簡単な例としてタップされたピンの情報をログに表示してみましょう。引数のannotationViewがタップされたピンを表しています。

-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
    NSLog(@"title: %@", view.annotation.title);
    NSLog(@"subtitle: %@", view.annotation.subtitle);
    NSLog(@"coord: %f, %f", view.annotation.coordinate.latitude, view.annotation.coordinate.longitude);
}


ちゃんとタップしたアノテーションの情報がログに出力されてますね。

ピンの削除

ピンを削除するにはマップビューのremoveAnnotationsの引数にアノテーションの配列を渡すか、removeAnnotationの引数に1つのアノテーションを渡して呼び出します。
全てのアノテーションを削除するなら

[mapView removeAnnotations: mapView.annotations]

これだけです。
アノテーションを1つ1つ個別に削除したい場合というのは、UI的にそれぞれのアノテーションをタップした場合などになると思います。例えば先ほどコールアウトに追加したボタンをタップしたときに削除したいのなら、先ほどのコード中のNSLogの代わりにremoveAnnotationを呼び出します

-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
    [mapView removeAnnotation:view.annotation];
}

ピン同士を結ぶ直線を描画する

MapKitでは地図上に図形を描画することもできます。簡単な例としてピン同士を結ぶ直線を描画してみます。
手順としては

  1. viewForOverlayで直線の色、太さなどを指定する
  2. 直線を引きたい座標を指定してMKPolylineオブジェクトを作成する
  3. mapViewのaddOverlayにMKPolylineオブジェクトを渡す

まずは基本的な方法としてアノテーションは使わずに直線だけを描画してみます。

//Polylineの設定
- (MKOverlayView *)mapView:(MKMapView *)mapView
            viewForOverlay:(id<MKOverlay>)overlay {
    MKPolylineView *view = [[MKPolylineView alloc] initWithOverlay:overlay];
    view.strokeColor = [UIColor blueColor];
    view.lineWidth = 5.0;
    return view;
}

//基本的な直線の描画方法。通る座標の数だけ配列を作る
CLLocationCoordinate2D coords[3];
coords[0] = CLLocationCoordinate2DMake(35.658611, 139.701111);      //渋谷駅
coords[1] = CLLocationCoordinate2DMake(35.658944, 139.703417);      //ヒカリエ
coords[2] = CLLocationCoordinate2DMake(35.670167, 139.702694);      //原宿駅
MKPolyline *line = [MKPolyline polylineWithCoordinates:coords count:3];
[mapView addOverlay:line];


coordsに追加した順番通り、渋谷駅→ヒカリエ→原宿駅を結ぶ直線が描画されています。


次はアノテーションに含まれる座標を使用する方法。mapView.annotations.countで取得したアノテーションの数だけ配列を用意し、for文の中でアノテーションの座標をコピーして入れてあげます。(というかObjective-Cって変数を使って配列の宣言できるんですね。試してみるまでできないと思っていました^^;)

//追加したannotationを利用する
int count = mapView.annotations.count;
CLLocationCoordinate2D coords[count];
for(int i=0; i<count; i++){
    id<MKAnnotation> annotation = [mapView.annotations objectAtIndex:i];
    coords[i] = CLLocationCoordinate2DMake(annotation.coordinate.latitude, annotation.coordinate.longitude);
}
MKPolyline *line = [MKPolyline polylineWithCoordinates:coords count:count];
[mapView addOverlay:line];

で、できると思いきや実はできません!線の描かれ方が違ってしまっています。

アノテーションを追加した順番である渋谷駅→ヒカリエ→原宿駅の順に線が引かれて欲しかったのに、順番がズレてしまっています。

上手くいかない理由ですが、どうもmapView.annotationsで取得できるNSArrayの中身のMKAnnotationは追加した順番通りではなくランダムらしく、順番が毎回固定されてないようです。なので、とんでもない直線が引かれてしまう可能性があるわけです。
解決策としてはめんどくさいですが、アノテーションを追加するときに独自のNSMuttableArrayとかに保存しておいて、それを使用するという方法しかないかなぁ。


次の記事では地図に追加したピンに合わせて地図の表示領域を自動調節する方法を紹介します。