はじめに
iPhone・iPad向けBing Mapsのコントロール、Bing Maps iOS Control の第2回目(iOS Controlについては最終回)です。前回 はコントロールを使用するための設定からユーザーの現在地を表示するところまで紹介しました。今回は主なコントロールの機能を順に紹介します。アプリは図1 のようになります。
図1 今回の目標
今回は、次の内容を行います。
地図の移動
逆ジオコーディング(経緯度から住所の取得)
地図の種類の変更
マーカーの表示
少しアプリらしくということで、共有機能をおまけでつけています。Twitterなどに投稿できればいいのですが、手短にできる内容として、表示した地図の地点をメールで送信する機能になっています。前回同様、初心者の方でも動作まではできるよう説明していますので、ぜひトライしてみてください。
ツールバーの追加
最初にアプリの画面を作ります。プロジェクトは前回の内容を参考に、自分の位置を表示するところまで作成してください。前回のプロジェクトを引き継いでも構いません。この記事でのプロジェクト名はGihyoSample2になっています。
今回はツールバーを追加します。プロジェクト名+ViewContoller.xibのファイルをクリックし、LibraryパレットからTool Bar をドラッグ&ドロップします(図2 ) 。既に配置してある地図コントロール(Map View)の大きさを調整して、Tool BarをMap Viewの下の位置に配置します。図中のドック をドラッグすると、オブジェクトの表示がアイコンからツリー表示に変わり階層構成がわかります。また、ドックの項目をクリックやドラッグによっても、画面上のパーツを選択や階層の移動が可能です。図3 のようになるよう画面を構成してください。
図2 Tool Barの追加
図3 アプリの画面構成
ツールバーのボタンも追加します。ツールバー上にBar Button Item をドラッグ&ドロップします(図4 ) 。Fixed Space Bar Button Item を追加すると右寄せの配置も可能です。
図4 Bar Button Itemの追加
Inspector パレットのAttributes タブをクリックして、各ボタンのタイトルを変更します。各ボタンをクリックして、Title を図5 のように変更します。
図5 ボタンのタイトルの変更
それぞれのボタンは次の機能を割り当てます。
Me: 現在地の表示
RevGeo: 逆ジオコーディング
Mode: 地図の種類の変更
Pin: マーカーの表示
Share: メールの送信
コードの編集
ボタンをタップしたとき各種処理ができるよう準備しておきましょう。ボタンのタップなどのイベントは、コントロール(Viewオブジェクト)から、別のオブジェクト(ターゲット )にメッセージを送信します(アクション ) 。ターゲットは、View Controllerです。そのためには、Viewとの結び付けを行う必要があります。
Xcodeウィンドウ右上のほうにあるEditorの中央ボタンをクリックすると、画面デザインを表示しながらコードも表示できます。xibファイルに対応するプロジェクト名+ViewController.hファイルが表示されたと思います。ここで、ツールバー上のボタンをクリックし、選択した状態でボタンを右クリックします。表示された項目にあるselectorの「○」部分をコードにドラッグ&ドロップします(図6 ) 。
図6 アクションの結びつけ
すると図7 のように名前を入力するダイアログが表示されます。ここにアクションの名前を入力してConnectボタンをクリックします。
図7 アクション名の入力
以上で、次のようにコードが挿入されます。また、プロジェクト名+ViewController.mファイルのほうにもメソッドが挿入されています。IBActionは、この結び付けで使用するキーワードです。
- ( IBAction ) meAction :( id ) sender ;
ほかのボタンも同様にして、コードを挿入してください。最終的に次のようになるよう名前を設定します。
- ( IBAction ) meAction :( id ) sender ;
- ( IBAction ) revGeoAction :( id ) sender ;
- ( IBAction ) modeAction :( id ) sender ;
- ( IBAction ) pinAction :( id ) sender ;
- ( IBAction ) shareAction :( id ) sender ;
地図の移動
最初は、地図を移動してみましょう。地図を移動するには、表示の中心となる経緯度と表示する範囲を指定します。
プロジェクト名+ViewController.mのファイルをクリックしてください。次のコードの部分に追記していきます。
- ( IBAction ) meAction :( id ) sender {
}
中心の経緯度と範囲(縦と横の経緯度の差)は、BMCoordinateRegion クラスで表します。それぞれの値は次のように記述します。BMCoordinateRegionオブジェクトを生成し、MapViewクラスのsetRegion メソッドに渡すとその地点が表示されます。
- ( IBAction ) meAction :( id ) sender {
BMCoordinateRegion region ;
region . center . latitude = 35.68918 ;
region . center . longitude = 139.6916 ;
region . span . latitudeDelta = 0.01 ;
region . span . longitudeDelta = 0.01 ;
[ self . mapView setRegion : region animated : YES ];
}
ここまでを実行してみましょう(図8 ) 。Meボタンで地図の移動ができたでしょうか。
図8 地図の移動
Meボタンは、現在地に移動するためのボタンでしたので、上記のコードを編集します。ユーザーの位置情報は、Map ViewのuserLocation プロパティ(BMUserLocation クラス型)から参照できます。さらにプロパティをたどってBMUserLocationクラスのlocation プロパティ(CLLocation クラス型)のcoordinate、verticalAccuracy、horizontalAccuracy プロパティから経緯度と縦方向と横方向の精度(メートル単位)が取得できます。
BMCoordinateRegionで表す範囲は度単位でしたので、メートルから度に変換してBMCoordinateRegionオブジェクトを返すBMCoordinateRegionMakeWithDistance を使用します。書き換えたコードは次のようになります。
- ( IBAction ) meAction :( id ) sender {
BMCoordinateRegion region ;
region = BMCoordinateRegionMakeWithDistance (
self . mapView . userLocation . location . coordinate ,
self . mapView . userLocation . location . verticalAccuracy ,
self . mapView . userLocation . location . horizontalAccuracy );
[ self . mapView setRegion : region animated : YES ];
}
コードで見ると簡単ですね。ここまでを実行してみましょう(図9 ) 。
図9 現在地の表示
逆ジオコーディング
次は、逆ジオコーディングを行ってみましょう。アプリのRevGeoボタンをタップすると逆ジオコーディングを行い、その場所の住所を表示するようにします。あらかじめ明かしておきますと日本におけるBing Mapsの逆ジオコーディングの精度は今のところよくありません。都道府県レベルの取得しかできません。今後の改善に期待しましょう。
逆ジオコーディングはBMReverseGeocoder クラスを用います。ViewControllerクラスから使用できるようプロジェクト名+ViewController.hファイルを編集します。コードを次のように変更してください。
@interface GihyoSample2ViewController : UIViewController < BMMapViewDelegate , >BMReverseGeocoderDelegate > {
@private
IBOutlet BMMapView * mapView_ ;
BMReverseGeocoder * reverseGeocoder_ ;
}
@property ( nonatomic , retain ) IBOutlet BMMapView * mapView ;
@property ( nonatomic , retain ) BMReverseGeocoder * reverseGeocoder ;
①上記コードではBMReverseGeocoderDelgate を追記しています。ViewControllerクラスをデリゲート先に指定し逆ジオコーディング結果を受け取る処理を行います。BMReverseGeocoderDelgateには、実装すべきメソッドが定義されています。
②③また、BMReverseGeocoder型のインスタンス変数reverseGeocoderも宣言しています。@propertyを使用してアクセサメソッドの自動生成も指定し、アクセサメソッド(プロパティ)経由でコードからアクセスするようにします。次はこれらの実装を記述しましょう。
プロジェクト名+ViewController.mファイルに移動します。まず、アクセサメソッドの生成の指定です。既にある@synthesizeの次行に以下のコードを追記します。
@synthesize mapView = mapView_ ;
@synthesize reverseGeocoder = reverseGeocoder_ ;
続いて、コードの最後、@endの前に次のメソッドを記述します。didFindEntity とdidFailWithError メソッドが、逆ジオコーディング結果を受け取るメソッドです。名前の通り、成功した場合と失敗した場合でわかれています。
-( void ) reverseGeocoder :( BMReverseGeocoder *) geocoder didFindEntity :( BMEntity *) entity {
}
-( void ) reverseGeocoder :( BMReverseGeocoder *) geocoder didFailWithError :( NSError *) error {
}
イベント処理
以上で、逆ジオコーディングの準備が整いました。次はボタンをタップしたときの処理をrevGeoAction メソッドに記述します。ボタンタップ時にはBMReverseGeocoderオブジェクトを生成し、表示されている地図の中心の経緯度を使用して、逆ジオコーディング処理を呼び出します。
- ( IBAction ) revGeoAction :( id ) sender {
self . reverseGeocoder = [[ BMReverseGeocoder alloc ] initWithCoordinate : self . mapView . region . center ];
self . reverseGeocoder . delegate = self ;
[ self . reverseGeocoder start ];
}
逆ジオコーディングの成功・失敗によってdidFindEntityまたはdidFailWithErrorメソッドが呼ばれます。成功した場合は、その地点の住所を、失敗した場合はエラーメッセージをアラート表示します。
逆ジオコーディングに成功した場合、BMEntity オブジェクトを受け取ります。BMEntityオブジェクトは住所などの情報を持っています。ここでは、アラートのタイトルにadminDistrict (都道府県など広域な行政区域の名前)プロパティの値を、メッセージにformattedAddress (完全な住所)プロパティの値を表示します。
コードは次のようになります。アラートの表示にはUIAlertView オブジェクトを生成して行います。
-( void ) reverseGeocoder :( BMReverseGeocoder *) geocoder didFindEntity :( BMEntity *) entity {
UIAlertView * alert = [[[ UIAlertView alloc ] initWithTitle :[ entity adminDistrict ]
message :[ entity formattedAddress ]
delegate : self
cancelButtonTitle :@ "Ok"
otherButtonTitles : nil ] autorelease ];
[ alert show ];
self . reverseGeocoder = nil ;
}
次は失敗した場合です。アラートにエラーメッセージを表示するようにしています。
-( void ) reverseGeocoder :( BMReverseGeocoder *) geocoder didFailWithError :( NSError *) error {
UIAlertView * alert = [[[ UIAlertView alloc ] initWithTitle :@ "Error"
message :[ error localizedDescription ]
delegate : self
cancelButtonTitle :@ "Ok"
otherButtonTitles : nil ] autorelease ];
[ alert show ];
self . reverseGeocoder = nil ;
}
ここまでを実行してみましょう。結果は図10 のようになります。ただしく住所が取得できたでしょうか。日本の場合、取得できないか、adminDistrict・formattedAddressプロパティとも都道府県名になることが多いかと思います。
図10 逆ジオコーディング結果の表示
地図の種類の変更
次は、地図の種類の変更です。現在、Bing Maps iOS Controlでは次の3種類の地図を選択できます。
Modeボタンをタップするたびに種類を切り替えるようコードを記述します。
地図の種類は、Map ViewのmapMode プロパティで参照・設定を行います。コードを次のように編集しましょう。現在表示している種類を取得して、それに応じて次の地図の種類を設定します。
- ( IBAction ) modeAction :( id ) sender {
switch ( self . mapView . mapMode ) {
case BMMapModeRoad :
self . mapView . mapMode = BMMapModeAerial ;
break ;
case BMMapModeAerial :
self . mapView . mapMode = BMMapModeAerialWithLabels ;
break ;
default :
self . mapView . mapMode = BMMapModeRoad ;
}
}
実行結果は、図11 のようになります。図は、ラベル付き航空写真を表示したところです。
図11 ラベル付き航空写真
マーカーの表示
地図コントロールの機能の紹介の最後は、地点を表すマーカー(プッシュピン)の表示です。処理を簡単にするためPinボタンをタップすると、表示している地図の中心にマーカーを表示するという動作にします。
マーカーを地図上に追加するには、まずBMEntity オブジェクトを生成し、Map ViewのaddMaker メソッドを使用してマーカーを追加します。pinActionメソッドの内容は次のようになります。
- ( IBAction ) pinAction :( id ) sender {
BMEntity * entity = [[[ BMEntity alloc ] initWithCoordinate : self . mapView . region . center
bingAddressDictionary : nil ] autorelease ];
[ self . mapView addMarker : entity ];
}
BMEntityオブジェクトは逆ジオコーディングのときにもdidFindEntityメソッドで受け取り使用していました。今回は自分で生成します。上記コードでは、region.centerプロパティを使用して、地図の中心位置を設定しています。
マーカーの表示はこれだけでは完了しません。マーカーを表示するためのBMMarkerView (Marker View )オブジェクトも生成する必要があります。マーカーを表示するとき、Map Viewはマーカーに対応するMarker Viewを要求します。Marker Viewを生成するメソッドは、デリゲートメソッドとしてありますので、そのメソッドを実装しましょう。次のviewForMarker メソッドが、addMakerメソッドを呼んだときに呼ばれます。コードは、以下のように追記してください。
- ( BMMarkerView *) mapView :( BMMapView *) mapView viewForMarker :( id < BMMarker >) marker {
static NSString * pinIdentifier = @ "PinIdentifier" ;
BMMarkerView * pinView = [ self . mapView dequeueReusableMarkerViewWithIdentifier : pinIdentifier ];
if (! pinView ) {
pinView = [[ BMPushpinView alloc ] initWithMarker : marker
reuseIdentifier : pinIdentifier ];
} else {
pinView . marker = marker ;
}
return pinView ;
}
①パフォーマンスの問題から、Marker Viewを毎回 生成するのではなく、既に作成したものがあればそれを使用します。dequeueReusableMarkerViewWithIdentifier メソッドを使用して、指定したIDのMarker Viewを得ます。
②Marker Viewがない場合は、新しく生成します。BMMarkerViewクラスではなく、派生クラスであるBM PushpinView クラスのオブジェクトを実際には使います。BMPushipinViewの初期化のメソッドで表示するマーカー(marker変数)の指定もしています。
ここまでを実行してみましょう。Pinボタンをタップするたびに、地図の中心にマーカーが追加されます(図12 ) 。
図12 マーカーの表示
マーカーを削除する場合は、Map ViewのremoveMarkers メソッドを使用するとすべてのマーカーを地図上から削除します。次のようにpinActionを書き換えることで、地図上にはひとつだけマーカーが表示されるようになります。
- ( IBAction ) pinAction :( id ) sender {
[ self . mapView removeMarkers : self . mapView . markers ];
BMEntity * entity = [[[ BMEntity alloc ] initWithCoordinate : self . mapView . region . center
bingAddressDictionary : nil ] autorelease ];
[ self . mapView addMarker : entity ];
}
地図の共有
最後におまけ機能としてメールの送信処理を加えます。処理を簡単にするため、アプリ画面で表示している地図の中心を示すURLを送るという内容にします。ただ、そのURLへアクセスした時にPCとスマートフォンの両対応を考えると悩ましい問題があります。確実に同じように地図を表示する方法はないため、今回はPCで閲覧を想定します。Bing Mapsでは、経緯度を検索するとその地点が表示されますのでそのURLを作成しメールの本文とします。
フレームワークの追加
まずメール機能を使用するために必要なフレームワークを追加します。TARGETSのプロジェクト名をクリックし、続いてBuild Phases タブをクリックします。Link Binary With Libraries の項目を展開します(図13 ) 。+ 部分をクリックして「MessageUI.framework」を追加します(図14 ) 。
図13 Link Binary With Librariesの表示
図14 MessageUI.framewrokの追加
ヘッダーの編集
次は、プロジェクト名+ViewController.hファイルを編集します。コードの冒頭部分を次のように、#importとDelegateの記述を追加します。
@interface GihyoSample2ViewController : UIViewController < BMMapViewDelegate , BMReverseGeocoderDelegate , MFMailComposeViewControllerDelegate > {
@private
IBOutlet BMMapView * mapView_ ;
BMReverseGeocoder * reverseGeocoder_ ;
}
メールの送信
それでは、shareActionメソッドの内容を記述します。コードは次のようになります。
- ( IBAction ) shareAction :( id ) sender {
if (![ MFMailComposeViewController canSendMail ]) {
return ;
}
double lat = self . mapView . region . center . latitude ;
double lng = self . mapView . region . center . longitude ;
NSString * body = [ NSString stringWithFormat :@ "http://www.bing.com/maps/?q=%f,%f" , lat , lng ];
MFMailComposeViewController * ctl = [[[ MFMailComposeViewController alloc ] init ] autorelease ];
ctl . mailComposeDelegate = self ;
[ ctl setMessageBody : body isHTML : NO ];
[ self presentModalViewController : ctl animated : YES ];
}
①メール関連の処理は、MFMailComposeViewController クラスを使用します。最初にメールが送信可能かcanSendMail メソッドでチェックします。
②メール本文となるBing MapsへのURLを作成します。
③MFMailComposeViewControllerオブジェクトを作成し、メール本文を設定し、メール送信の画面を表示します。
メール送信後の成功・失敗の結果を受け取るデリゲート メソッドがあります。こちらも実装しましょう。追記するdidFinishWithResult メソッドの内容は次のようになります。エラーの表示と、メール送信画面を閉じる処理を記述しています。
- ( void ) mailComposeController :( MFMailComposeViewController *) controller didFinishWithResult :( MFMailComposeResult ) result error :( NSError *) error {
if ( error != nil ) {
UIAlertView * alert = [[[ UIAlertView alloc ] initWithTitle :@ "Error"
message :[ error localizedDescription ]
delegate : self
cancelButtonTitle :@ "OK"
otherButtonTitles : nil ] autorelease ];
[ alert show ];
}
[ self dismissModalViewControllerAnimated : YES ];
}
以上が、メール送信処理でした。実行しShareボタンをクリックすると図15 のようにメール送信画面が表示されます。iPhoneシミュレーターの場合、実際にメール送信はできません。
図15 メールの送信
おわりに
今回はここまでです。いかがでしたでしょうか。Bing Maps iOS Controlを利用したiPhone・iPad開発について、前回 と今回の2回にわたってお届けしました。
Bing Maps iOS Controlの機能はいかがでしたか。連載で使用していない細かな機能はありますが、主要な機能は紹介しています。まだまだ地図コントロールとしてもの足りない部分はありますが、地図コントロールの選択肢のひとつとして見て頂けるとよいかと思います。また、Bing Maps iOS Controlだけでなく、Webアプリ向けのBing Maps AJAX Control という選択肢もあります。Bing Maps AJAX Controlは、iPhoneのSafariなどスマートフォンのWebブラウザーにも対応しており、モバイル開発でも利用できます。Bing Maps AJAX Controlについては、第13回 からの記事を参照してください。
iPadについては連載で触れていませんが、Bing Maps iOS ControlはもちろんiPadにも対応しています。プロジェクトの作成時にiPadを選択する、または作成後にiOS Application TargetのDevicesをiPadまたはUniversalを選択することで、iPad向けのxibファイルが生成され、iPadのアプリ開発が可能です。
また、連載ではiPhoneシミュレーターのみを使用しましたが、実機で動作させるため、およびアプリの配布には、iOS Developer Programへの登録も必要となります。詳しくはほかの連載 に任せます 。ぜひチャレンジして素敵な地図アプリを公開してください。