Mapas no Xamarin.iOS

Os mapas são um recurso comum em todos os sistemas operacionais móveis modernos. O iOS oferece suporte de mapeamento nativamente por meio da estrutura do Kit de Mapa. Com o Map Kit, os aplicativos podem adicionar facilmente mapas avançados e interativos. Esses mapas podem ser personalizados de várias maneiras, como adicionar anotações para marcar locais em um mapa e sobrepor elementos gráficos de formas arbitrárias. O Map Kit ainda tem suporte interno para mostrar a localização atual de um dispositivo.

Adicionando um mapa

A adição de um mapa a um aplicativo é realizada adicionando uma MKMapView instância à hierarquia de exibição, conforme mostrado abaixo:

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

MKMapView é uma UIView subclasse que exibe um mapa. Simplesmente adicionar o mapa usando o código acima produz um mapa interativo:

Um mapa de exemplo

Estilo do Mapa

MKMapView dá suporte a três estilos diferentes de mapas. Para aplicar um estilo de mapa, basta definir a MapType propriedade como um valor da MKMapType enumeração :

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

A captura de tela a seguir mostra os diferentes estilos de mapa disponíveis:

Esta captura de tela mostra os diferentes estilos de mapa disponíveis

Movimento panorâmico e zoom

MKMapView inclui suporte para recursos de interatividade do mapa, como:

  • Zoom por meio de um gesto de pinçagem
  • Movimento panorâmico por meio de um gesto de movimento panorâmico

Esses recursos podem ser habilitados ou desabilitados simplesmente definindo as ZoomEnabled propriedades e ScrollEnabled da MKMapView instância, em que o valor padrão é true para ambos. Por exemplo, para exibir um mapa estático, basta definir as propriedades apropriadas como false:

map.ZoomEnabled = false;
map.ScrollEnabled = false;

Local do usuário

Além da interação do usuário, MKMapView também tem suporte interno para exibir o local do dispositivo. Ele faz isso usando a estrutura Localização Principal . Antes de acessar a localização do usuário, você deve solicitar ao usuário. Para fazer isso, crie uma instância de CLLocationManager e chame RequestWhenInUseAuthorization.

CLLocationManager locationManager = new CLLocationManager();
locationManager.RequestWhenInUseAuthorization();
//locationManager.RequestAlwaysAuthorization(); //requests permission for access to location data while running in the background

Observe que, em versões do iOS anteriores à 8.0, tentar chamar RequestWhenInUseAuthorization resultará em um erro. Certifique-se de marcar a versão do iOS antes de fazer essa chamada se você pretende dar suporte a versões anteriores à 8.

Acessar a localização do usuário também requer modificações em Info.plist. As seguintes chaves relacionadas a dados locais devem ser definidas:

  • NSLocationWhenInUseUsageDescription – para acessar o local do usuário enquanto eles estiverem interagindo com seu aplicativo.
  • NSLocationAlwaysUsageDescription – para quando o aplicativo acessa o local do usuário em segundo plano.

Você pode adicionar essas chaves abrindo Info.plist e selecionando Fonte na parte inferior do editor.

Depois de atualizar o Info.plist e solicitar ao usuário permissão para acessar sua localização, você poderá mostrar a localização do usuário no mapa definindo a ShowsUserLocation propriedade como true:

map.ShowsUserLocation = true;

O alerta permitir acesso à localização

Anotações

MKMapView também dá suporte à exibição de imagens, conhecidas como anotações, em um mapa. Elas podem ser imagens personalizadas ou pinos definidos pelo sistema de várias cores. Por exemplo, a captura de tela a seguir mostra um mapa com um pin e uma imagem personalizada:

Esta captura de tela mostra um mapa com um pin e uma imagem personalizada

Adicionando uma anotação

Uma anotação em si tem duas partes:

  • O MKAnnotation objeto , que inclui dados de modelo sobre a anotação, como o título e o local da anotação.
  • O MKAnnotationView , que contém a imagem a ser exibida e, opcionalmente, um texto explicativo que é mostrado quando o usuário toca na anotação.

O Map Kit usa o padrão de delegação do iOS para adicionar anotações a um mapa, em que a Delegate propriedade do MKMapView é definida como uma instância de um MKMapViewDelegate. É a implementação desse delegado responsável por retornar o MKAnnotationView para uma anotação.

Para adicionar uma anotação, primeiro a anotação é adicionada chamando AddAnnotations na MKMapView instância :

// add an annotation
map.AddAnnotations (new MKPointAnnotation (){
    Title="MyAnnotation",
    Coordinate = new CLLocationCoordinate2D (42.364260, -71.120824)
});

Quando o local da anotação se tornar visível no mapa, o chamará o MKMapView método de GetViewForAnnotation seu delegado para obter o MKAnnotationView a ser exibido.

Por exemplo, o código a seguir retorna um fornecido pelo MKPinAnnotationViewsistema:

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;
}

Reutilizando anotações

Para conservar a memória, MKMapView permite que os modos de exibição de anotação sejam agrupados para reutilização, semelhante à maneira como as células da tabela são reutilizados. A obtenção de uma exibição de anotação do pool é feita com uma chamada para DequeueReusableAnnotation:

MKAnnotationView pinView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);

Mostrando textos explicativos

Conforme mencionado anteriormente, uma anotação pode, opcionalmente, mostrar um texto explicativo. Para mostrar um texto explicativo, basta definir CanShowCallout como true no MKAnnotationView. Isso resulta na exibição do título da anotação quando a anotação é tocada, conforme mostrado:

O título de anotações que está sendo exibido

Personalizando o texto explicativo

O texto explicativo também pode ser personalizado para mostrar exibições acessórios à esquerda e à direita, conforme mostrado abaixo:

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

Esse código resulta no seguinte texto explicativo:

Um texto explicativo de exemplo

Para manipular o usuário tocando no acessório certo, basta implementar o CalloutAccessoryControlTapped método no MKMapViewDelegate:

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

Sobreposições

Outra maneira de colocar elementos gráficos em camadas em um mapa é usando sobreposições. As sobreposições são suporte para elaborar conteúdos gráficos que são dimensionados com o mapa conforme ele é ampliado e reduzido. O iOS dá suporte a vários tipos de sobreposições, incluindo:

  • Polígonos – comumente usados para realçar alguma região em um mapa.
  • Polilinha – geralmente vistas ao mostrar uma rota.
  • Círculos – usado para realçar uma área circular de um mapa.

Além disso, as sobreposições personalizadas podem ser criadas para mostrar geometrias arbitrárias com código de desenho granular e personalizado. Por exemplo, o radar meteorológico seria um bom candidato para uma sobreposição personalizada.

Adicionando uma sobreposição

Semelhante a anotações, adicionar uma sobreposição envolve duas partes:

  • Criando um objeto de modelo para a sobreposição e adicionando-o ao MKMapView .
  • Criando uma exibição para a sobreposição no MKMapViewDelegate .

O modelo para a sobreposição pode ser qualquer MKShape subclasse. O Xamarin.iOS inclui MKShape subclasses para polígonos, polilinha e círculos, por meio das MKPolygonclasses e MKCircle , MKPolyline respectivamente.

Por exemplo, o código a seguir é usado para adicionar um MKCircle:

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

A exibição de uma sobreposição é uma MKOverlayView instância retornada pelo GetViewForOverlay no MKMapViewDelegate. Cada MKShape um tem um correspondente MKOverlayView que sabe como exibir a forma fornecida. Pois MKPolygonMKPolygonView. Da mesma forma, MKPolyline corresponde a MKPolylineViewe para MKCircleMKCircleView.

Por exemplo, o código a seguir retorna um MKCircleView para um 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;
}

Isso exibe um círculo no mapa, conforme mostrado:

Um círculo exibido no mapa

O iOS inclui uma API de pesquisa local com o Kit de Mapa, que permite pesquisas assíncronas para pontos de interesse em uma região geográfica especificada.

Para executar uma pesquisa local, um aplicativo deve seguir estas etapas:

  1. Criar MKLocalSearchRequest objeto.
  2. Crie um MKLocalSearch objeto do MKLocalSearchRequest .
  3. Chame o Start método no MKLocalSearch objeto .
  4. Recupere o MKLocalSearchResponse objeto em um retorno de chamada.

A própria API de pesquisa local não fornece nenhuma interface do usuário. Ele nem precisa de um mapa para ser usado. No entanto, para fazer uso prático da pesquisa local, um aplicativo precisa fornecer alguma maneira de especificar uma consulta de pesquisa e exibir resultados. Além disso, como os resultados conterão dados de localização, muitas vezes fará sentido mostrá-los em um mapa.

Adicionando uma interface do usuário de pesquisa local

Uma maneira de aceitar a entrada de pesquisa é com um UISearchBar, que é fornecido por um UISearchController e exibirá os resultados em uma tabela.

O código a seguir adiciona o UISearchController (que tem uma propriedade de barra de pesquisa) no ViewDidLoad método de 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;

Observe que você é responsável por incorporar o objeto da barra de pesquisa na interface do usuário. Neste exemplo, o atribuimos ao TitleView da barra de navegação, mas se você não usar um controlador de navegação em seu aplicativo, precisará encontrar outro local para exibi-lo.

Neste snippet de código, criamos outro controlador de exibição personalizado – searchResultsController – que exibe os resultados da pesquisa e, em seguida, usamos esse objeto para criar nosso objeto de controlador de pesquisa. Também criamos um novo atualizador de pesquisa, que se torna ativo quando o usuário interage com a barra de pesquisa. Ele recebe notificações sobre pesquisas com cada pressionamento de tecla e é responsável por atualizar a interface do usuário. Vamos dar uma olhada em como implementar o searchResultsController e o searchResultsUpdater posterior neste guia.

Isso resulta em uma barra de pesquisa exibida sobre o mapa, conforme mostrado abaixo:

Uma barra de pesquisa exibida no mapa

Exibindo os resultados da pesquisa

Para exibir os resultados da pesquisa, precisamos criar um Controlador de Exibição personalizado; normalmente um UITableViewController. Conforme mostrado acima, o searchResultsController é passado para o construtor do searchController quando ele está sendo criado. O código a seguir é um exemplo de como criar este Controlador de Exibição personalizado:

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

    }
}

Atualizando os resultados da pesquisa

O SearchResultsUpdater atua como um mediador entre a barra de pesquisa e os searchControllerresultados da pesquisa.

Neste exemplo, precisamos primeiro criar o método de pesquisa no SearchResultsViewController. Para fazer isso, devemos criar um MKLocalSearch objeto e usá-lo para emitir uma pesquisa para um MKLocalSearchRequest, os resultados são recuperados em um retorno de chamada passado para o Start método do MKLocalSearch objeto . Em seguida, os resultados são retornados em um MKLocalSearchResponse objeto que contém uma matriz de MKMapItem objetos:

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

}

Em seguida, em nosso MapViewController , criaremos uma implementação personalizada de UISearchResultsUpdating, que é atribuída à SearchResultsUpdater propriedade de nossa searchController na seção Adicionar uma interface do usuário de pesquisa local:

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

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

A implementação acima adiciona uma anotação ao mapa quando um item é selecionado nos resultados, conforme mostrado abaixo:

Uma anotação adicionada ao mapa quando um item é selecionado nos resultados

Importante

UISearchController foi implementado no iOS 8. Se você quiser dar suporte a dispositivos anteriores a isso, precisará usar UISearchDisplayController.

Resumo

Este artigo examinou a estrutura do Kit de Mapa para iOS. Primeiro, ele analisou como a MKMapView classe permite que mapas interativos sejam incluídos em um aplicativo. Em seguida, ele demonstrou como personalizar ainda mais mapas usando anotações e sobreposições. Por fim, ele examinou os recursos de pesquisa locais que foram adicionados ao Map Kit com o iOS 6.1, mostrando como usar o desempenho de consultas baseadas em localização para pontos de interesse e adicioná-las a um mapa.