Edited at

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

More than 5 years have passed since last update.

日々、企業の思惑に右往左往している読者諸賢の皆様、こんばんは。これは、Objective-C Advent Calendar 2012の13日目の記事です。まさか、MKMapKitについての記事を出そうとしたその日の内にGoogle Maps APIが公開されるとは……こんなのってないよ、あんまりだよ。

せっかくなのでGoogle Mapsに差し替えようかと認証キーの申請はしてみましたが、まだまだ配布まで時間がかかるみたいなのでこのまま行きます。サービスという特性上、いつ使えなくなるかもわかりませんし。いずれ、Apple Mapsだって改善されるでしょうし。泣いてないですし。


フレームワークの追加

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


アノテーションの表示

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


アノテーションの用意

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

#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のみ必須で、titlesubtitleを任意で付けることができます。更に、今回は後で使うためにsampleというプロパティを設けました。


マップの用意

MKMapViewを適当なUIViewに載せ、IBOutletdelegateを適当なUIViewControllerに繋ぎます。

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


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

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

@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:メソッドをオーバーライドし、コールアウトに詳細ボタン(青くて丸いアレ)を追加します。

// 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:メソッドをオーバーライドし、ボタンをビューに差し替える処理を書きます。

// 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:メソッドをオーバーライドし、ビューをボタンに戻します。

// class MapViewController

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

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


補足

コールアウトのアクセサリビューは、かなりスペースが制限されてしまいます。詳細情報などを表示させたいときは、下手にアクセサリをいじるより大人しくページ遷移させたほうが良いです。

また、iPadであれば、コールアウト自体をポップオーバーに差し替える選択肢もありますね。


終わりに

もういくつか取り上げたいこともありましたが、長くなってしまったのでこのあたりにしておこうと思います。

Apple Maps先生の次回作にご期待ください。

参考