日々、企業の思惑に右往左往している読者諸賢の皆様、こんばんは。これは、Objective-C Advent Calendar 2012の13日目の記事です。まさか、MKMapKitについての記事を出そうとしたその日の内にGoogle Maps APIが公開されるとは……こんなのってないよ、あんまりだよ。
せっかくなのでGoogle Mapsに差し替えようかと認証キーの申請はしてみましたが、まだまだ配布まで時間がかかるみたいなのでこのまま行きます。サービスという特性上、いつ使えなくなるかもわかりませんし。いずれ、Apple Mapsだって改善されるでしょうし。泣いてないですし。
##フレームワークの追加
プロジェクトに、CoreLocation.framework
とMapKit.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
のみ必須で、title
とsubtitle
を任意で付けることができます。更に、今回は後で使うためにsample
というプロパティを設けました。
#####マップの用意
MKMapViewを適当なUIViewに載せ、IBOutlet
とdelegate
を適当な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先生の次回作にご期待ください。
参考