MKMapKitでとにかくピンを突き立てるサンプル

日々、企業の思惑に右往左往している読者諸賢の皆様、こんばんは。これは、[Objective-C Advent Calendar 2012](http://qiita.com/advent-calendar/2012/objective-c)の13日目の記事です。まさか、MKMapKitについての記事を出そうとしたその日の内にGoogle Maps APIが公開されるとは……こんなのってないよ、あんまりだよ。
せっかくなのでGoogle Mapsに差し替えようかと認証キーの申請はしてみましたが、まだまだ配布まで時間がかかるみたいなのでこのまま行きます。サービスという特性上、いつ使えなくなるかもわかりませんし。いずれ、Apple Mapsだって改善されるでしょうし。泣いてないですし。

##フレームワークの追加

プロジェクトに、`CoreLocation.framework`と`MapKit.framework`を追加してください。

##アノテーションの表示

アノテーションというのは、マップに刺さった赤いピンです。押すと黒い吹き出しが出ます。

#####アノテーションの用意
アノテーションのモデルそのものを表すクラスはありません。MKAnnotationプロトコルを満たしたクラスを自前で用意する必要があります。

```objectivec
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@interface CustomAnnotation : NSObject <MKAnnotation>
@property (readwrite, nonatomic) CLLocationCoordinate2D coordinate; // required
@property (readwrite, nonatomic, strong) NSString* title; // optional
@property (readwrite, nonatomic, strong) NSString* subtitle; // ditto
@property (readwrite, nonatomic, strong) NSString* sample; // for example
@end

@implementation CustomAnnotation
@synthesize coordinate, title, subtitle, sample;
@end
```

座標`coordinate`のみ必須で、`title`と`subtitle`を任意で付けることができます。更に、今回は後で使うために`sample`というプロパティを設けました。

#####マップの用意

MKMapViewを適当なUIViewに載せ、`IBOutlet`と`delegate`を適当なUIViewControllerに繋ぎます。
更に、その適当なUIViewControllerにMKMapViewDelegateプロトコルを追加しておきます。必須メソッドはありません。

#####アノテーションをマップに追加

MKMapViewクラスの`addAnnotation:`メソッドないし`addAnnotations:`メソッドを使います。

```objectivec
@interface MapViewController : UIViewController <MKMapViewDelegate>
@end

@implementation MapViewController {
    IBOutlet MKMapView* __weak map_; // connect IBOutlet and delegate in Interface Builder
}

// class MapViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // move to the middle of between Tokyo Tower and Tokyo Skytree
    MKCoordinateSpan span = MKCoordinateSpanMake(0.1, 0.1);
    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(35.682736, 139.779722);
    [map_ setRegion:MKCoordinateRegionMake(center, span) animated:NO];
    
    // Tokyo Tower
    CustomAnnotation* tt = [[CustomAnnotation alloc] init];
    tt.coordinate = CLLocationCoordinate2DMake(35.655333, 139.748611);
    tt.title = @"Tokyo Tower";
    tt.subtitle = @"opening in Dec 1958";
    tt.sample = @"35.655, 139.748";
    
    // Tokyo Skytree
    CustomAnnotation* st = [[CustomAnnotation alloc] init];
    st.coordinate = CLLocationCoordinate2DMake(35.710139, 139.810833);
    st.title = @"Tokyo Skytree";
    st.subtitle = @"opening in May 2012";
    st.sample = @"35.710, 139.810";

    // add annotations to map
    [map_ addAnnotations:@[tt, st]];
}
```

東京タワーとスカイツリーのアノテーションを追加しました。それに加えて、確認しやすくするために`setRegion:animated:`メソッドで初期表示の位置をタワーとツリーの中間地点にしています。

以上で、マップ上にピンが表示されるかと思います。

##コールアウトの拡張

MKAnnotationクラスを擁するMKAnnotationViewクラスは、`  leftCalloutAccessoryView`プロパティと`  rightCalloutAccessoryView`プロパティを持ち、それぞれに適当なUIViewを設定することでちょっとしたアクションを追加することができます。
コールアウトにボタンを追加し、それを押すとボタンが消えて追加情報が表示される、というのをやってみましょう。

#####詳細ボタンの追加

まず最初に、マップにアノテーションが追加されたときに呼ばれる`mapView:didAddAnnotationViews:`メソッドをオーバーライドし、コールアウトに詳細ボタン（青くて丸いアレ）を追加します。

```objectivec
// class MapViewController
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
    // add detail disclosure button to callout
    [views enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL* stop) {
        ((MKAnnotationView*)obj).rightCalloutAccessoryView
            = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
    }];
}
```

`enumerateObjectsUsingBlock:`メソッドを使っているのは、個人的な趣味です。

#####ボタンを押したときの処理

次に、その追加したボタンが押されたときに呼ばれる`mapView:annotationView:calloutAccessoryControlTapped:`メソッドをオーバーライドし、ボタンをビューに差し替える処理を書きます。

```objectivec
// class MapViewController
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {    
    // create right accessory view
    UILabel* sample = [[UILabel alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 32.f)];
    sample.backgroundColor = [UIColor clearColor];
    sample.font = [UIFont fontWithName:@"Helvetica" size: 13];
    sample.text = ((CustomAnnotation*)view.annotation).sample;
    sample.textColor = [UIColor whiteColor];
    
    // add view to callout
    view.rightCalloutAccessoryView = nil; // ??
    view.rightCalloutAccessoryView = sample;
}
```

最初のほうで追加しておいた`sample`プロパティから表示させる文字列を拾ってきています。こういう使い方をすることはあんまり無いとは思いますが、一応できますよ、ということで。

（アクセサリに一度`nil`を入れないとボタンが消えないのが謎です。`nil`でないとreleaseされない？？）

#####ビューを外す

最後に、フォーカスが外れたときに呼ばれる`mapView:didDeselectAnnotationView:`メソッドをオーバーライドし、ビューをボタンに戻します。

```objectivec
// class MapViewController
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view {
    view.rightCalloutAccessoryView
        = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
```

以上で、コールアウトが少し賑やかになったのではないかと思います。

#####補足

コールアウトのアクセサリビューは、かなりスペースが制限されてしまいます。詳細情報などを表示させたいときは、下手にアクセサリをいじるより大人しくページ遷移させたほうが良いです。
また、iPadであれば、コールアウト自体をポップオーバーに差し替える選択肢もありますね。

##終わりに

もういくつか取り上げたいこともありましたが、長くなってしまったのでこのあたりにしておこうと思います。
Apple Maps先生の次回作にご期待ください。

参考

* [MKAnnotation Protocol](http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKAnnotation_Protocol/Reference/Reference.html)
* [MKAnnotationView Class](http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKAnnotationView_Class/Reference/Reference.html)
* [MKMapViewDelegate Protocol](http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapViewDelegate_Protocol/MKMapViewDelegate/MKMapViewDelegate.html)
* [MKMapView Class](http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html)