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 サブクラスです。 上記のコードを使用してマップを追加するだけで、対話型のマップが生成されます。

A sample map

マップ スタイル

MKMapView では、3 つの異なるスタイルのマップがサポートされています。 マップ スタイルを適用するには、MapType プロパティを MKMapType 列挙型の値に設定するだけです。

map.MapType = MKMapType.Standard; //road map
map.MapType = MKMapType.Satellite;
map.MapType = MKMapType.Hybrid;

次のスクリーンショットは、使用可能なさまざまなマップ スタイルを示しています。

This screenshot show the different map styles that are available

パンとズーム

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;

The allow location access alert

注釈

MKMapView では、注釈と呼ばれる画像をマップに表示することもできます。 これらは、カスタム イメージまたはさまざまな色のシステム定義ピンのいずれかです。 たとえば、次のスクリーンショットは、ピンとカスタム イメージの両方を含むマップを示しています。

This screenshot shows a map with a both a pin and a custom image

注釈の追加

注釈自体には、次の 2 つの部分があります。

  • 注釈のタイトルや位置など、注釈に関するモデル データを含む MKAnnotation オブジェクト。
  • 注釈を表すイメージを含む MKAnnotationView。また、必要に応じて、ユーザーが注釈をタップしたときに表示されるコールアウトが含まれます。

Map Kit では、iOS 委任パターンを使用して注釈をマップに追加します。ここで、MKMapViewDelegate プロパティは 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 に設定します。 これにより、次に示すように、注釈がタップされたときに注釈のタイトルが表示されます。

The annotations title being displayed

コールアウトのカスタマイズ

コールアウトは、次に示すように、左右のアクセサリ ビューを表示するようにカスタマイズすることもできます。

pinView.RightCalloutAccessoryView = UIButton.FromType (UIButtonType.DetailDisclosure);
pinView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile ("monkey.png"));

このコードの結果、次のコールアウトが表示されます。

An example callout

ユーザーによる適切なアクセサリのタップを処理するには、次のように MKMapViewDelegateCalloutAccessoryControlTapped メソッドを実装します。

public override void CalloutAccessoryControlTapped (MKMapView mapView, MKAnnotationView view, UIControl control)
{
    ...
}

オーバーレイ

マップ上にグラフィックスをレイヤー化するもう 1 つの方法は、オーバーレイを使用することです。 オーバーレイでは、マップが拡大縮小されたときに、マップと一緒に拡大縮小されるグラフィカル コンテンツの描画がサポートされます。 iOS では、次のようないくつかの種類のオーバーレイがサポートされています。

  • 多角形 - マップ上の一部の領域を強調表示するために一般的に使用されます。
  • ポリライン - ルートを表示するときによく表示されます。
  • 円 - マップの円形領域を強調表示するために使用します。

さらに、カスタム オーバーレイを作成して、細かくカスタマイズされた描画コードを使用して任意のジオメトリを表示できます。 たとえば、気象レーダーは、カスタム オーバーレイの候補として適しています。

オーバーレイの追加

注釈と同様に、オーバーレイの追加には 2 つの部分が含まれます。

  • オーバーレイのモデル オブジェクトを作成し、それを MKMapView に追加します。
  • MKMapViewDelegate でオーバーレイのビューを作成します。

オーバーレイのモデルには、任意の MKShape サブクラスを指定できます。 Xamarin.iOS には、ポリゴン、ポリライン、および円の MKShape サブクラスが、それぞれ MKPolygonMKPolyline および MKCircle クラスを介して含まれています。

たとえば、次のコードを使用して、MKCircle を追加します。

var circleOverlay = MKCircle.Circle (mapCenter, 1000);
map.AddOverlay (circleOverlay);

オーバーレイのビューは、MKMapViewDelegateGetViewForOverlay によって返される MKOverlayView インスタンスです。 各 MKShape には、対応する MKOverlayView があり、指定された図形を表示する方法を認識しています。 MKPolygon には MKPolygonViewがあります。 同様に、MKPolylineMKPolylineView に対応し、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;
}

これにより、次に示すように、マップ上に円が表示されます。

A circle displayed on the map

iOS には、Map Kit のローカル検索 API が含まれています。これにより、指定した地理的リージョンの目的地を非同期で検索できます。

ローカル検索を実行するには、アプリケーションは次の手順に従う必要があります。

  1. MKLocalSearchRequest オブジェクトを作成します。
  2. MKLocalSearchRequest から MKLocalSearch オブジェクトを作成します。
  3. MKLocalSearch オブジェクトで Start メソッドを呼び出します。
  4. コールバック内の MKLocalSearchResponse オブジェクトを取得します。

ローカル検索 API 自体は、ユーザー インターフェイスを提供しません。 マップを使用する必要もありません。 ただし、ローカル検索を実際に使用するには、アプリケーションで検索クエリを指定し結果を表示する何らかの方法を提供する必要があります。 さらに、結果には位置データが含まれるため、多くの場合、地図上に表示するのが理にかなっています。

ローカル検索 UI の追加

検索入力を受け入れる方法の 1 つは、 UISearchBar の使用です。これは UISearchController によって提供され、結果はテーブルに表示されます。

次のコードは、MapViewControllerViewDidLoad メソッドに 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 の更新を担当します。 このガイドで後ほど searchResultsControllersearchResultsUpdater の両方を実装する方法について説明します。

これにより、次のように検索バーがマップ上に表示されます。

A search bar displayed over the map

検索結果の表示

検索結果を表示するには、カスタム ビュー コントローラーを作成する必要があります。通常は 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」セクションの searchControllerSearchResultsUpdater プロパティに割り当てられます。

public class SearchResultsUpdator : UISearchResultsUpdating
{
    public event Action<string> UpdateSearchResults = delegate {};

    public override void UpdateSearchResultsForSearchController (UISearchController searchController)
    {
        this.UpdateSearchResults (searchController.SearchBar.Text);
    }
}

上記の実装では、次に示すように、結果から項目が選択されたときにマップに注釈を追加します。

An annotation added to the map when an item is selected from the results

重要

UISearchController は iOS 8 で実装されました。 これより前のデバイスをサポートする場合は、UISearchDisplayController を使用する必要があります。

まとめ

この記事では、iOS 用の MapKit フレームワークについて説明しました。 まず、MKMapView クラスで対話型マップをアプリケーションに含める方法について説明しました。 次に、注釈とオーバーレイを使用してマップをさらにカスタマイズする方法を示しました。 最後に、iOS 6.1 で Map Kit に追加されたローカル検索機能について調べ、目的地に対して位置ベースのクエリを実行してマップに追加する方法を示しました。