使ってみよう! Bing API/SDK

第24回地図アプリで始めるiPhone・iPadアプリ開発(2)

はじめに

iPhone・iPad向けBing Mapsのコントロール、Bing Maps iOS Controlの第2回目(iOS Controlについては最終回)です。前回はコントロールを使用するための設定からユーザーの現在地を表示するところまで紹介しました。今回は主なコントロールの機能を順に紹介します。アプリは図1のようになります。

図1 今回の目標
図1 今回の目標

今回は、次の内容を行います。

  • 地図の移動
  • 逆ジオコーディング(経緯度から住所の取得)
  • 地図の種類の変更
  • マーカーの表示

少しアプリらしくということで、共有機能をおまけでつけています。Twitterなどに投稿できればいいのですが、手短にできる内容として、表示した地図の地点をメールで送信する機能になっています。前回同様、初心者の方でも動作まではできるよう説明していますので、ぜひトライしてみてください。

ツールバーの追加

最初にアプリの画面を作ります。プロジェクトは前回の内容を参考に、自分の位置を表示するところまで作成してください。前回のプロジェクトを引き継いでも構いません。この記事でのプロジェクト名はGihyoSample2になっています。

今回はツールバーを追加します。プロジェクト名+ViewContoller.xibのファイルをクリックし、LibraryパレットからTool Barをドラッグ&ドロップします図2⁠。既に配置してある地図コントロール(Map View)の大きさを調整して、Tool BarをMap Viewの下の位置に配置します。図中のドックをドラッグすると、オブジェクトの表示がアイコンからツリー表示に変わり階層構成がわかります。また、ドックの項目をクリックやドラッグによっても、画面上のパーツを選択や階層の移動が可能です。図3のようになるよう画面を構成してください。

図2 Tool Barの追加
図2 Tool Barの追加
図3 アプリの画面構成
図3 アプリの画面構成

ツールバーのボタンも追加します。ツールバー上にBar Button Itemをドラッグ&ドロップします図4⁠。Fixed Space Bar Button Itemを追加すると右寄せの配置も可能です。

図4 Bar Button Itemの追加
図4 Bar Button Itemの追加

InspectorパレットのAttributesタブをクリックして、各ボタンのタイトルを変更します。各ボタンをクリックして、Title図5のように変更します。

図5 ボタンのタイトルの変更
図5 ボタンのタイトルの変更

それぞれのボタンは次の機能を割り当てます。

  • Me: 現在地の表示
  • RevGeo: 逆ジオコーディング
  • Mode: 地図の種類の変更
  • Pin: マーカーの表示
  • Share: メールの送信

コードの編集

ボタンをタップしたとき各種処理ができるよう準備しておきましょう。ボタンのタップなどのイベントは、コントロール(Viewオブジェクト)から、別のオブジェクトターゲットにメッセージを送信しますアクション⁠。ターゲットは、View Controllerです。そのためには、Viewとの結び付けを行う必要があります。

Xcodeウィンドウ右上のほうにあるEditorの中央ボタンをクリックすると、画面デザインを表示しながらコードも表示できます。xibファイルに対応するプロジェクト名+ViewController.hファイルが表示されたと思います。ここで、ツールバー上のボタンをクリックし、選択した状態でボタンを右クリックします。表示された項目にあるselectorの「○」部分をコードにドラッグ&ドロップします図6⁠。

図6 アクションの結びつけ
図6 アクションの結びつけ

すると図7のように名前を入力するダイアログが表示されます。ここにアクションの名前を入力してConnectボタンをクリックします。

図7 アクション名の入力
図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 地図の移動
図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 現在地の表示
図9 現在地の表示

逆ジオコーディング

次は、逆ジオコーディングを行ってみましょう。アプリのRevGeoボタンをタップすると逆ジオコーディングを行い、その場所の住所を表示するようにします。あらかじめ明かしておきますと日本におけるBing Mapsの逆ジオコーディングの精度は今のところよくありません。都道府県レベルの取得しかできません。今後の改善に期待しましょう。

逆ジオコーディングはBMReverseGeocoderクラスを用います。ViewControllerクラスから使用できるようプロジェクト名+ViewController.hファイルを編集します。コードを次のように変更してください。

#import <UIKit/UIKit.h>
#import "BingMaps/BingMaps.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の前に次のメソッドを記述します。didFindEntitydidFailWithErrorメソッドが、逆ジオコーディング結果を受け取るメソッドです。名前の通り、成功した場合と失敗した場合でわかれています。

-(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 逆ジオコーディング結果の表示
図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:
    //case BMMapModeAerialWithLabels:
      self.mapView.mapMode = BMMapModeRoad;
  }
}

実行結果は、図11のようになります。図は、ラベル付き航空写真を表示したところです。

図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プロパティを使用して、地図の中心位置を設定しています。

マーカーの表示はこれだけでは完了しません。マーカーを表示するためのBMMarkerViewMarker 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クラスではなく、派生クラスであるBMPushpinViewクラスのオブジェクトを実際には使います。BMPushipinViewの初期化のメソッドで表示するマーカー(marker変数)の指定もしています。

ここまでを実行してみましょう。Pinボタンをタップするたびに、地図の中心にマーカーが追加されます図12⁠。

図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の表示
図13 Link Binary With Librariesの表示
図14 MessageUI.framewrokの追加
図14 MessageUI.framewrokの追加

ヘッダーの編集

次は、プロジェクト名+ViewController.hファイルを編集します。コードの冒頭部分を次のように、#importとDelegateの記述を追加します。

#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import "BingMaps/BingMaps.h"

@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 メールの送信
図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への登録も必要となります。詳しくはほかの連載任せます。ぜひチャレンジして素敵な地図アプリを公開してください。

おすすめ記事

記事・ニュース一覧