Xamarin.iOS のマップ
マップは、最新のすべてのモバイル オペレーティング システムに一般的な機能です。 iOS では、Map Kit フレームワークを通じてネイティブにマップ サポートが提供されます。 Map Kit を使用すると、アプリケーションは豊富な対話型マップを簡単に追加できます。 これらのマップは、マップ上の位置をマークする注釈の追加や、任意の図形のグラフィックスのオーバーレイなど、さまざまな方法でカスタマイズできます。 Map Kit には、デバイスの現在位置を表示するためのサポートも組み込まれています。
マップの追加
アプリケーションにマップを追加するには、次に示すように、ビュー階層に MKMapView
インスタンスを追加します。
// map is an MKMapView declared as a class variable
map = new MKMapView (UIScreen.MainScreen.Bounds);
View = map;
MKMapView
はマップを表示する UIView
サブクラスです。 上記のコードを使用してマップを追加するだけで、対話型のマップが生成されます。
マップ スタイル
MKMapView
では、3 つの異なるスタイルのマップがサポートされています。 マップ スタイルを適用するには、MapType
プロパティを MKMapType
列挙型の値に設定するだけです。
map.MapType = MKMapType.Standard; //road map
map.MapType = MKMapType.Satellite;
map.MapType = MKMapType.Hybrid;
次のスクリーンショットは、使用可能なさまざまなマップ スタイルを示しています。
パンとズーム
MKMapView
には、次のようなマップの対話型機能のサポートが含まれています。
- ピンチ ジェスチャによるズーム
- パン ジェスチャを使用したパン
これらの機能は、MKMapView
インスタンスの ZoomEnabled
プロパティと ScrollEnabled
プロパティを設定するだけで有効または無効にできます。既定値は両方で true となっています。 たとえば、静的マップを表示するには、適切なプロパティを false に設定するだけです。
map.ZoomEnabled = false;
map.ScrollEnabled = false;
ユーザーの位置
ユーザーの操作に加えて、MKMapView
にはデバイスの位置を表示するためのサポートも組み込まれています。 これは、"コア ロケーション" フレームワークを使用して行われます。 ユーザーの位置にアクセスする前に、ユーザーにプロンプトを表示する必要があります。 これを行うには、CLLocationManager
のインスタンスを作成して RequestWhenInUseAuthorization
を呼び出します。
CLLocationManager locationManager = new CLLocationManager();
locationManager.RequestWhenInUseAuthorization();
//locationManager.RequestAlwaysAuthorization(); //requests permission for access to location data while running in the background
8.0 より前のバージョンの iOS では、RequestWhenInUseAuthorization
の呼び出しを試みるとエラーが発生することに注意してください。 8 より前のバージョンをサポートしたい場合は、その呼び出しを行う前に、必ず iOS のバージョンを確認してください。
ユーザーの位置にアクセスするには、Info.plist への変更も必要になります。 位置データに関連する次のキーを設定する必要があります。
- NSLocationWhenInUseUsageDescription: アプリでやり取りしている間にユーザーの場所にアクセスする場合。
- NSLocationAlwaysUsageDescription: アプリがバック グラウンドでユーザーの場所にアクセスする場合。
これらのキーを追加するには、Info.plist を開き、エディターの下部にある [ソース] を選択します。
Info.plist を更新し、ユーザーの位置にアクセスするためのアクセス許可をユーザーに求めたら、ShowsUserLocation
プロパティを true に設定することでマップ上にユーザーの場所を表示できます。
map.ShowsUserLocation = true;
注釈
MKMapView
では、注釈と呼ばれる画像をマップに表示することもできます。 これらは、カスタム イメージまたはさまざまな色のシステム定義ピンのいずれかです。 たとえば、次のスクリーンショットは、ピンとカスタム イメージの両方を含むマップを示しています。
注釈の追加
注釈自体には、次の 2 つの部分があります。
- 注釈のタイトルや位置など、注釈に関するモデル データを含む
MKAnnotation
オブジェクト。 - 注釈を表すイメージを含む
MKAnnotationView
。また、必要に応じて、ユーザーが注釈をタップしたときに表示されるコールアウトが含まれます。
Map Kit では、iOS 委任パターンを使用して注釈をマップに追加します。ここで、MKMapView
の Delegate
プロパティは MKMapViewDelegate
のインスタンスに設定されます。 注釈へ MKAnnotationView
を返すことを担当するのは、この委任の実装です。
注釈を追加するには、まず、MKMapView
インスタンスに対し AddAnnotations
を呼び出して注釈を追加します。
// add an annotation
map.AddAnnotations (new MKPointAnnotation (){
Title="MyAnnotation",
Coordinate = new CLLocationCoordinate2D (42.364260, -71.120824)
});
注釈の位置がマップ上に表示されたら、MKMapView
によりそのデリゲートの GetViewForAnnotation
メソッドが呼び出され、表示する MKAnnotationView
が取得されます。
たとえば、次のコードはシステム提供の MKPinAnnotationView
を返します。
string pId = "PinAnnotation";
public override MKAnnotationView GetViewForAnnotation (MKMapView mapView, NSObject annotation)
{
if (annotation is MKUserLocation)
return null;
// create pin annotation view
MKAnnotationView pinView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);
if (pinView == null)
pinView = new MKPinAnnotationView (annotation, pId);
((MKPinAnnotationView)pinView).PinColor = MKPinAnnotationColor.Red;
pinView.CanShowCallout = true;
return pinView;
}
注釈の再利用
メモリを節約するために、MKMapView
はテーブル セルの再利用と同様に、注釈ビューを再利用するためにプールできます。 プールからの注釈ビューの取得は、DequeueReusableAnnotation
への呼び出しで行われます。
MKAnnotationView pinView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);
コールアウトの表示
前述のように、注釈には必要に応じてコールアウトを表示できます。 コールアウトを表示するには、単に MKAnnotationView
上で CanShowCallout
を true に設定します。 これにより、次に示すように、注釈がタップされたときに注釈のタイトルが表示されます。
コールアウトのカスタマイズ
コールアウトは、次に示すように、左右のアクセサリ ビューを表示するようにカスタマイズすることもできます。
pinView.RightCalloutAccessoryView = UIButton.FromType (UIButtonType.DetailDisclosure);
pinView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile ("monkey.png"));
このコードの結果、次のコールアウトが表示されます。
ユーザーによる適切なアクセサリのタップを処理するには、次のように MKMapViewDelegate
で CalloutAccessoryControlTapped
メソッドを実装します。
public override void CalloutAccessoryControlTapped (MKMapView mapView, MKAnnotationView view, UIControl control)
{
...
}
オーバーレイ
マップ上にグラフィックスをレイヤー化するもう 1 つの方法は、オーバーレイを使用することです。 オーバーレイでは、マップが拡大縮小されたときに、マップと一緒に拡大縮小されるグラフィカル コンテンツの描画がサポートされます。 iOS では、次のようないくつかの種類のオーバーレイがサポートされています。
- 多角形 - マップ上の一部の領域を強調表示するために一般的に使用されます。
- ポリライン - ルートを表示するときによく表示されます。
- 円 - マップの円形領域を強調表示するために使用します。
さらに、カスタム オーバーレイを作成して、細かくカスタマイズされた描画コードを使用して任意のジオメトリを表示できます。 たとえば、気象レーダーは、カスタム オーバーレイの候補として適しています。
オーバーレイの追加
注釈と同様に、オーバーレイの追加には 2 つの部分が含まれます。
- オーバーレイのモデル オブジェクトを作成し、それを
MKMapView
に追加します。 MKMapViewDelegate
でオーバーレイのビューを作成します。
オーバーレイのモデルには、任意の MKShape
サブクラスを指定できます。 Xamarin.iOS には、ポリゴン、ポリライン、および円の MKShape
サブクラスが、それぞれ MKPolygon
、MKPolyline
および MKCircle
クラスを介して含まれています。
たとえば、次のコードを使用して、MKCircle
を追加します。
var circleOverlay = MKCircle.Circle (mapCenter, 1000);
map.AddOverlay (circleOverlay);
オーバーレイのビューは、MKMapViewDelegate
の GetViewForOverlay
によって返される MKOverlayView
インスタンスです。 各 MKShape
には、対応する MKOverlayView
があり、指定された図形を表示する方法を認識しています。 MKPolygon
には MKPolygonView
があります。 同様に、MKPolyline
は MKPolylineView
に対応し、MKCircle
には MKCircleView
があります。
たとえば、次のコードは、MKCircle
に対し MKCircleView
を返します。
public override MKOverlayView GetViewForOverlay (MKMapView mapView, NSObject overlay)
{
var circleOverlay = overlay as MKCircle;
var circleView = new MKCircleView (circleOverlay);
circleView.FillColor = UIColor.Blue;
return circleView;
}
これにより、次に示すように、マップ上に円が表示されます。
ローカル検索
iOS には、Map Kit のローカル検索 API が含まれています。これにより、指定した地理的リージョンの目的地を非同期で検索できます。
ローカル検索を実行するには、アプリケーションは次の手順に従う必要があります。
MKLocalSearchRequest
オブジェクトを作成します。MKLocalSearchRequest
からMKLocalSearch
オブジェクトを作成します。MKLocalSearch
オブジェクトでStart
メソッドを呼び出します。- コールバック内の
MKLocalSearchResponse
オブジェクトを取得します。
ローカル検索 API 自体は、ユーザー インターフェイスを提供しません。 マップを使用する必要もありません。 ただし、ローカル検索を実際に使用するには、アプリケーションで検索クエリを指定し結果を表示する何らかの方法を提供する必要があります。 さらに、結果には位置データが含まれるため、多くの場合、地図上に表示するのが理にかなっています。
ローカル検索 UI の追加
検索入力を受け入れる方法の 1 つは、 UISearchBar
の使用です。これは UISearchController
によって提供され、結果はテーブルに表示されます。
次のコードは、MapViewController
の ViewDidLoad
メソッドに UISearchController
(検索バー プロパティを持つ) を追加します。
//Creates an instance of a custom View Controller that holds the results
var searchResultsController = new SearchResultsViewController (map);
//Creates a search controller updater
var searchUpdater = new SearchResultsUpdator ();
searchUpdater.UpdateSearchResults += searchResultsController.Search;
//add the search controller
searchController = new UISearchController (searchResultsController) {
SearchResultsUpdater = searchUpdater
};
//format the search bar
searchController.SearchBar.SizeToFit ();
searchController.SearchBar.SearchBarStyle = UISearchBarStyle.Minimal;
searchController.SearchBar.Placeholder = "Enter a search query";
//the search bar is contained in the navigation bar, so it should be visible
searchController.HidesNavigationBarDuringPresentation = false;
//Ensure the searchResultsController is presented in the current View Controller
DefinesPresentationContext = true;
//Set the search bar in the navigation bar
NavigationItem.TitleView = searchController.SearchBar;
検索バー オブジェクトをユーザー インターフェイスに自分で組み込む必要があることに注意してください。 この例では、これをナビゲーション バーの TitleView に割り当てていますが、アプリケーションでナビゲーション コントローラーを使用しない場合は、別の場所を見つけて表示する必要があります。
このコード スニペットでは、検索結果を表示する別のカスタム ビュー コントローラー searchResultsController
を作成し、このオブジェクトを使用して検索コントローラー オブジェクトを作成しました。 また、ユーザーが検索バーを操作するとアクティブになる新しい検索アップデーターも作成しました。 キーストロークごとに検索に関する通知を受け取り、UI の更新を担当します。
このガイドで後ほど searchResultsController
と searchResultsUpdater
の両方を実装する方法について説明します。
これにより、次のように検索バーがマップ上に表示されます。
検索結果の表示
検索結果を表示するには、カスタム ビュー コントローラーを作成する必要があります。通常は UITableViewController
です。 上に示すように、searchResultsController
は作成時に searchController
のコンストラクターに渡されます。
次のコードは、このカスタム ビュー コントローラーを作成する方法の例です。
public class SearchResultsViewController : UITableViewController
{
static readonly string mapItemCellId = "mapItemCellId";
MKMapView map;
public List<MKMapItem> MapItems { get; set; }
public SearchResultsViewController (MKMapView map)
{
this.map = map;
MapItems = new List<MKMapItem> ();
}
public override nint RowsInSection (UITableView tableView, nint section)
{
return MapItems.Count;
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (mapItemCellId);
if (cell == null)
cell = new UITableViewCell ();
cell.TextLabel.Text = MapItems [indexPath.Row].Name;
return cell;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
// add item to map
CLLocationCoordinate2D coord = MapItems [indexPath.Row].Placemark.Location.Coordinate;
map.AddAnnotations (new MKPointAnnotation () {
Title = MapItems [indexPath.Row].Name,
Coordinate = coord
});
map.SetCenterCoordinate (coord, true);
DismissViewController (false, null);
}
public void Search (string forSearchString)
{
// create search request
var searchRequest = new MKLocalSearchRequest ();
searchRequest.NaturalLanguageQuery = forSearchString;
searchRequest.Region = new MKCoordinateRegion (map.UserLocation.Coordinate, new MKCoordinateSpan (0.25, 0.25));
// perform search
var localSearch = new MKLocalSearch (searchRequest);
localSearch.Start (delegate (MKLocalSearchResponse response, NSError error) {
if (response != null && error == null) {
this.MapItems = response.MapItems.ToList ();
this.TableView.ReloadData ();
} else {
Console.WriteLine ("local search error: {0}", error);
}
});
}
}
検索結果の更新
SearchResultsUpdater
は、searchController
の検索バーと検索結果の間のメディエーターとして機能します。
この例では、最初に SearchResultsViewController
で検索メソッドを作成する必要があります。 これを行うには、MKLocalSearch
オブジェクトを作成し、それを使用して MKLocalSearchRequest
の検索を発行する必要があります。結果は、MKLocalSearch
オブジェクトの Start
メソッドに渡されたコールバックで取得されます。 結果は、MKMapItem
オブジェクトの配列を含む MKLocalSearchResponse
オブジェクトで返されます。
public void Search (string forSearchString)
{
// create search request
var searchRequest = new MKLocalSearchRequest ();
searchRequest.NaturalLanguageQuery = forSearchString;
searchRequest.Region = new MKCoordinateRegion (map.UserLocation.Coordinate, new MKCoordinateSpan (0.25, 0.25));
// perform search
var localSearch = new MKLocalSearch (searchRequest);
localSearch.Start (delegate (MKLocalSearchResponse response, NSError error) {
if (response != null && error == null) {
this.MapItems = response.MapItems.ToList ();
this.TableView.ReloadData ();
} else {
Console.WriteLine ("local search error: {0}", error);
}
});
}
次に、MapViewController
では、UISearchResultsUpdating
のカスタム実装を作成します。この実装は、「ローカル検索 UI」セクションの searchController
の SearchResultsUpdater
プロパティに割り当てられます。
public class SearchResultsUpdator : UISearchResultsUpdating
{
public event Action<string> UpdateSearchResults = delegate {};
public override void UpdateSearchResultsForSearchController (UISearchController searchController)
{
this.UpdateSearchResults (searchController.SearchBar.Text);
}
}
上記の実装では、次に示すように、結果から項目が選択されたときにマップに注釈を追加します。
重要
UISearchController
は iOS 8 で実装されました。 これより前のデバイスをサポートする場合は、UISearchDisplayController
を使用する必要があります。
まとめ
この記事では、iOS 用の Map Kit フレームワークについて説明しました。 まず、MKMapView
クラスで対話型マップをアプリケーションに含める方法について説明しました。 次に、注釈とオーバーレイを使用してマップをさらにカスタマイズする方法を示しました。 最後に、iOS 6.1 で Map Kit に追加されたローカル検索機能について調べ、目的地に対して位置ベースのクエリを実行してマップに追加する方法を示しました。