Карты в Xamarin.iOS

Карты — это общая функция во всех современных мобильных операционных системах. IOS предлагает поддержку сопоставления в собственном коде с помощью платформы Map Kit. С помощью комплекта карт приложения могут легко добавлять расширенные интерактивные карты. Эти карты можно настроить различными способами, например добавление заметок для пометки расположений на карте и перекладывание графики произвольных фигур. Пакет карт даже имеет встроенную поддержку для отображения текущего расположения устройства.

Добавление карты

Добавление карты в приложение выполняется путем добавления экземпляра MKMapView в иерархию представлений, как показано ниже:

// map is an MKMapView declared as a class variable
map = new MKMapView (UIScreen.MainScreen.Bounds);
View = map;

MKMapViewUIView— подкласс, отображающий карту. Простое добавление карты с помощью приведенного выше кода создает интерактивную карту:

Пример карты

Стиль карты

MKMapView поддерживает 3 различных стиля карт. Чтобы применить стиль карты, просто задайте MapType для свойства значение из MKMapType перечисления:

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

На следующем снимку экрана показаны различные стили карт, доступные:

Снимок экрана: различные стили карт, доступные

Сдвиг и масштабирование

MKMapView включает поддержку функций интерактивности карты, таких как:

  • Масштабирование с помощью жеста сцепления
  • Сдвиг с помощью жеста сдвига

Эти функции можно включить или отключить, просто задав ZoomEnabled и ScrollEnabled свойства экземпляра MKMapView , где значение по умолчанию верно для обоих. Например, чтобы отобразить статическую карту, просто задайте для соответствующих свойств значение 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

Обратите внимание, что в версиях iOS до 8.0 попытка вызова RequestWhenInUseAuthorization приведет к ошибке. Перед этим вызовом обязательно проверьте версию iOS, если вы планируете поддерживать версии до 8.

Для доступа к расположению пользователя также требуются изменения в Info.plist. Необходимо задать следующие ключи, связанные с данными о расположении:

  • NSLocationWhenInUseUsageDescription для доступа к расположению пользователя во время его взаимодействия с приложением.
  • NSLocationAlwaysUsageDescription для доступа приложения к расположению пользователя в фоновом режиме.

Эти ключи можно добавить, открыв Info.plist и выбрав источник в нижней части редактора.

После обновления Info.plist и запроса пользователя на доступ к его расположению можно отобразить расположение пользователя на карте, установив ShowsUserLocation для свойства значение true:

map.ShowsUserLocation = true;

Оповещение о доступе к расположению

Заметки

MKMapView также поддерживает отображение изображений, известных как заметки, на карте. Это могут быть пользовательские изображения или системные закрепления различных цветов. Например, на следующем снимка экрана показана карта с закреплением и пользовательским изображением:

Снимок экрана: карта с закреплением и пользовательским изображением

Добавление заметки

Сама заметка состоит из двух частей:

  • Объект MKAnnotation , содержащий данные модели о заметке, например название и расположение заметки.
  • Объект MKAnnotationView , содержащий изображение для отображения и при необходимости выноски, отображаемой при касании заметки пользователем.

Map Kit использует шаблон делегирования MKMapView iOS для добавления примечаний к карте, где Delegate свойство объекта задано экземпляромMKMapViewDelegate. Это реализация этого делегата, которая отвечает за возврат MKAnnotationView заметки.

Чтобы добавить заметку, сначала добавляется заметка путем вызова AddAnnotations экземпляра MKMapView :

// 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);

Отображение выносок

Как упоминалось ранее, заметка может при необходимости отображать выноску. Чтобы отобразить выноску, просто установите CanShowCallout значение true на объекте MKAnnotationView. Это приводит к отображению заголовка заметки при нажатии заметки, как показано ниже.

Отображаемое название заметок

Настройка выноски

Выноска также можно настроить для отображения представлений слева и справа, как показано ниже:

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

Этот код приводит к следующему выноске:

Пример выноски

Чтобы обработать правую аксессуару, просто реализуйте CalloutAccessoryControlTapped метод в следующих MKMapViewDelegateэлементах:

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

Наложения

Другим способом слоя графики на карте является использование наложений. Наложения позволяют рисовать графическое содержимое, которое масштабируется вместе с картой. IOS обеспечивает поддержку нескольких типов наложений, в том числе:

  • Многоугольники — обычно используются для выделения некоторых регионов на карте.
  • Polylines - Часто видно при отображении маршрута.
  • Круги — используются для выделения круговой области карты.

Кроме того, пользовательские наложения можно создать для отображения произвольных геометрий с детализированный настраиваемый код рисования. Например, погодный радар будет хорошим кандидатом для пользовательского наложения.

Добавление наложения

Аналогично заметкам, добавление наложения включает в себя 2 части:

  • Создание объекта модели для наложения и его добавление в MKMapView него.
  • Создание представления для наложения в элементе MKMapViewDelegate .

Модель для наложения может быть любым MKShape подклассом. Xamarin.iOS включает MKShape подклассы для многоугольников, полилайнов и кругов, через MKPolygonMKPolyline классы соответственноMKCircle.

Например, следующий код используется для добавления MKCircle:

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

Представление для наложения — это MKOverlayView экземпляр, возвращаемый GetViewForOverlay в объекте MKMapViewDelegate. Каждый MKShape имеет соответствующий MKOverlayView , который знает, как отобразить заданную фигуру. Для MKPolygon этого есть MKPolygonView. Аналогично, MKPolyline соответствует и для MKCircle этого естьMKCircleViewMKPolylineView.

Например, следующий код возвращает MKCircleView значение для MKCircle:

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 включает локальный API поиска с пакетом map, который позволяет асинхронно искать точки интереса в указанном географическом регионе.

Чтобы выполнить локальный поиск, приложение должно выполнить следующие действия:

  1. Создайте объект MKLocalSearchRequest.
  2. MKLocalSearch Создайте объект из MKLocalSearchRequest объекта .
  3. Start Вызовите метод объектаMKLocalSearch.
  4. MKLocalSearchResponse Получение объекта в обратном вызове.

Сам API локального поиска не предоставляет пользовательского интерфейса. Он даже не требует использования карты. Однако для практического использования локального поиска приложение должно предоставить какой-то способ указать поисковый запрос и отобразить результаты. Кроме того, так как результаты будут содержать данные о расположении, они часто будут отображаться на карте.

Добавление пользовательского интерфейса локального поиска

Один из способов принять входные данные поиска — с UISearchBarпомощью функции, предоставленной и UISearchController отображающей результаты в таблице.

Следующий код добавляет UISearchController (которое имеет свойство панели поиска) в ViewDidLoad методе MapViewController:

//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 который отображает результаты поиска, а затем мы использовали этот объект для создания объекта контроллера поиска. Мы также создали новый обработчик обновления поиска, который становится активным при взаимодействии пользователя с строкой поиска. Он получает уведомления о поиске с каждым нажатием клавиш и отвечает за обновление пользовательского интерфейса. Мы рассмотрим, как реализовать как в этом руководстве, searchResultsUpdater так searchResultsController и далее.

Это приводит к отображению панели поиска на карте, как показано ниже:

Панель поиска, отображаемая на карте

Отображение результатов поиска

Чтобы отобразить результаты поиска, необходимо создать пользовательский контроллер представления; обычно .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, результаты извлекаются в обратном вызове, переданном Start методу MKLocalSearch объекта. Затем результаты возвращаются в объекте MKLocalSearchResponse , содержав массив MKMapItem объектов:

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, которая назначается SearchResultsUpdater свойству нашего searchController раздела "Добавление локального пользовательского интерфейса поиска":

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. Во-первых, он рассмотрел, как MKMapView класс позволяет интерактивным картам включаться в приложение. Затем он продемонстрировал, как дополнительно настроить карты с помощью заметок и наложений. Наконец, она изучила возможности локального поиска, добавленные в Пакет карт с iOS 6.1, в котором показано, как использовать запросы на основе расположения для точек интереса и добавить их на карту.