前回は地図上にピンを落とす方法を紹介した。ピンをタップすると、吹き出しが表示される。今回は、この吹き出しをカスタマイズする方法を紹介しよう。これを使うことで、詳細情報などを表示させることができるだろう。

アノテーションビュー

地図上に表示する、ピンのようなものをアノテーションと呼ぶ、ということは前回説明した。アノテーションを表すMKAnnotationプロトコロルは、表示する情報を表すモデルとなるものである。

これに対して、実際に画面上に表示されるビューを表すクラスがある。MKAnnotationViewクラスだ。これはUIViewのサブクラスになっている。画面への表示をカスタマイズしたいときは、このアノテーションビュークラスを使うことになる。

アノテーションビューのカスタマイズの仕方は、2通りある。1つは、MKAnnotationViewのプロパティを使う方法だ。このクラスには、表示する画像を指定するためのimageプロパティがある。

List 1.

@property (nonatomic, retain) UIImage* image;

このプロパティに画像を設定すれば、任意のものを表示することができる。

また、アノテーションビューはタップすると黒い吹き出しを表示する。これをコールアウトと呼ぶ。コールアウトには、アクセサリビューを追加することができる。これには、leftCalloutAccessoryViewおよびrightCalloutAccessoryViewプロパティを使う。

List 2.

@property (retain, nonatomic) UIView* leftCalloutAccessoryView;
@property (retain, nonatomic) UIView* rightCalloutAccessoryView;

これらを使うことで、コールアウトにボタンなどを追加できる。実際の使用例は、この後で紹介しよう。

アノテーションビューをカスタマイズするもう1つの方法は、UIAnnotationViewのサブクラスを作る方法だ。このクラスはUIViewのサブクラスとなるので、drawRect:メソッドを上書きすれば、自由に描画を行うことができるようになる。

標準で用意されているピンを表示するアノテーションビューも、UIPinAnnotationViewというサブクラスで実現されている。ちなみにこのクラスには、ピンの色を変更するためのpinColorプロパティが用意されている。

List 3.

@property (nonatomic) MKPinAnnotationColor pinColor;

ピンの色は、赤、緑、紫が用意されている。

アノテーションビューの追加

続いて、アノテーションビューの追加について説明しよう。

アノテーションビューは、普通のUIViewのように、アプリケーション側でインスタンス化してaddSubview:する、という風には使わない。マップビューにアノテーションを追加すると、自動的にアノテーションビューも追加されるのだ。これをカスタマイズするには、デリゲートメソッドを使う。

まず、マップにアノテーションを追加して、画面上にそれが表示されようとすると、デリゲートメソッドのmapView:viewForAnnotation:が呼び出される。

List 4.

- (MKAnnotationView*)mapView:(MKMapView*)mapView
        viewForAnnotation:(id <MKAnnotation>)annotation;

もしMKAnnotationViewのサブクラスを使いたいならば、ここでインスタンス化をして、返してやればいい。ちなみに、このメソッドはアノテーションを追加したらすぐ呼ばれる訳ではない。あくまで、アノテーションが画面上に登場するときにだけ呼ばれるのだ。これにより、多数のアノテーションを登録したとしても、作成されるアノテーションビューの数を抑えることができる。さらに、一度作成したアノテーションビューを再利用するために、MKAnnotationViewにはdequeueReusableAnnotationViewWithIdentifier:というメソッドがある。一度作成したビューをプールしておけるのだ。この仕組みは、UITableViewCellに近い。

続いて、アノテーションビューの追加が終わると、mapView:didAddAnnotationViews:が呼ばれる。

List 5.

- (void)mapView:(MKMapView*)mapView didAddAnnotationViews:(NSArray*)views;

このメソッドでは、追加されたアノテーションビューを取り出す事ができる。既存のMKAnnotationViewをカスタマイズしたい場合は、このメソッドを使うのがいいだろう。

スポットの詳細表示

では、実際にアノテーションビューのカスタマイズを行ってみよう。前回までのソースコードを拡張する形で行う。ここでは、アノテーションビューに、スポットの詳細を表示するためのボタンを追加してみる。サブクラスは作らずに、UIAnnotationViewをそのまま使おう。

まず、mapView:didAddAnnotationViews:メソッドを実装する。このメソッドの中でUIAnnotationViewを取り出し、rightCalloutAccessoryViewにボタンを設定する。

List 6.

- (void)mapView:(MKMapView*)mapView didAddAnnotationViews:(NSArray*)views
{
    // アノテーションビューを取得する
    for (MKAnnotationView* annotationView in views) {
        // アノテーションがSpotLogAnnotationの場合
        if ([annotationView.annotation isKindOfClass:[SpotLogAnnotation class]]) {
            // ボタンを作成する
            UIButton*   button;
            button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];

            // コールアウトのアクセサリビューを設定する
            annotationView.rightCalloutAccessoryView = button;
        }
    }
}

ここでは、追加されたアノテーションビューがスポットに対するものであるかどうかを調べる必要がある。そのために、UIAnnotationViewのannotationプロパティを使っている。ここから、そのビューに関連づけられているアノテーションを取り出す事ができる。

スポットのためのビューであったら、ボタンを作成して追加している。ボタンは、UIButtonTypeDetailDisclosureを使っている。詳細情報の表示のために使われるボタンだ。

ここで作成したボタンには、特にターゲットもアクションも設定していないことに注目してほしい。実は、アクセサリビューとしてUIControlのサブクラスを設定すると、それに対するタップアクションをデリゲートメソッドを使って受け取ることができるのだ。そのために用意されているのが、mapView:annotationView:calloutAccessoryControlTapped:メソッドだ。別にこのメソッドを使わずに自分でターゲットを設定しても良いのだが、これを使うとアノテーションビューを同時に取得できるというメリットがある。これは、同時にアノテーションも取得できることを意味する。便利なので、こちらを使ってみた。

List 7.

- (void)mapView:(MKMapView*)mapView
        annotationView:(MKAnnotationView*)view
        calloutAccessoryControlTapped:(UIControl*)control
{
    // ログを取得する
    NSDictionary*   logDict;
    logDict = ((SpotLogAnnotation*)view.annotation).logDict;

    // メッセージを作成する
    NSMutableString*    message;
    message = [NSMutableString string];
    [message appendFormat:@"Date: %@\n", [logDict objectForKey:@"date"]];
    [message appendFormat:@"Lat: %@\n", [logDict objectForKey:@"latitude"]];
    [message appendFormat:@"Lon: %@\n", [logDict objectForKey:@"longitude"]];

    // アラートを表示する
    UIAlertView*    alertView;
    alertView = [[UIAlertView alloc] initWithTitle:@"Log Info" message:message
            delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:NULL];
    [alertView autorelease];
    [alertView show];
}

ここでは、まずログ情報を取得している。そのために、アノテーションビューから得られるアノテーションを使っているのが分かるだろう。そこに含まれる情報を、アラートビューを使って表示してみた。

これにより、次のようにスポットの日付および緯度経度情報を表示できるようになった。

詳細情報を表示するにはアラートビューを使う以外に、ビューコントローラを使って詳細画面を作る方法もあるだろう。これで、地図上の情報に対していろいろとアクセスするユーザインタフェースを付け加えることができるだろう。

ここまでのソースコード: SpotLog-3.zip