¿Te has fijado en la cantidad de aplicaciones que nos mustran un mapa en el que nos sitúan, nos indican lugares interesantes cercanos, marcan rutas…? En este artículo te explicaré cómo se construye un aplicación de mapas y rutas con MapKit.

Pero ¿qué es MapKit? MapKit es un framework Apple que basa su funcionamiento en la API y los datos de Apple Maps, de forma que se pueden añadir fácilmente mapas a las aplicaciones desarrolladas, en este caso, para iOS.

Un poco de Swift

Este proyecto lo puedes encontrar completo en GitHub.

Diseño de la interfaz

Este proyecto constará básicamente de un componente MKMapView, que será el que nos muestre el mapa, al que iremos añadiendo diferentes componentes según las funcionalidades que le queramos añadir a la aplicación. Además, en este proyecto, todo esto se hará mediante código, sin utilizar storyboards o ficheros .xib.

App design

Creación del proyecto

Para trabajar sin storyboards al establecer un proyecto en Xcode 11 hemos de hacer unos pasos tras haberlo creado:

  • Eliminamos el fichero Main.storyboard.
  • En la pestaña General (TARGETS), vamos al selector Main interface y eliminamos Main, dejando el campo en blanco.
Hay que borrar Main y dejar el campo en blanco.
  • Finalmente, en la pestaña Info (TARGETS) vamos a Application Scene Manifest > Scene Configuration > Application Session Role > Item 0 (Default Configuration) y eliminamos el campo Storyboard Name.
Borrado del campo Storyboard Name en el fichero Info.plist.

Como ahora ya no llamaremos al Main.storyboard para iniciar el proyecto, vamos al fichero SceneDelegate.swift, y en la función scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) y sustituimos su contenido por el siguiente código:

Añadimos un mapa a nuestra aplicación

Para añadir un mapa a la pantalla, simplemente hemos de crear una instancia de MKMapView y añadirla a la vista de pantalla. Para ello, en la clase ViewController, en primer lugar hemos de importar la librería MapKit, luego creamos una instancia de MKMapView y la presentamos:

Si ejecutamos la aplicación podremos ver en pantalla un mapa de aproximado de dónde estamos situados.

Presentación del mapa.

Para que este mapa nos muestre exactamente, debemos utilizar la clase CLLocationManager, que tal como Apple indica, permite iniciar y finalizar el envío de eventos de localización a nuestra aplicación:

  • Detectar cambios en la posición del usuario.
  • Ver cambios en la dirección de la brújula.
  • Monitorizar regiones de interés.
  • Detectar la posición de balizas (beacons) cercanas.

Permisos

Hay que tener en cuenta que para poder usar las funciones de localización, antes hemos de pedir permiso al usuario. Para ello, añadimos en el fichero Info.plist, una serie de parámetros (tal como mostré también para utilizar notificaciones):

  • Privacy – Location Always and When In Use Usage Description
  • Privacy – Location Always Usage Description
  • Privacy – Location When In Use Usage Description

A los que les damos como valor el mensaje que queremos mostrar al usuario para pedirle permiso (en este ejemplo, ‘Allow location access in order to use this app.‘).

Modificación del fichero Info.plist para la petición de permisos.

Una vez modificado el fichero Info.plist, vamos a hacer que la aplicación compruebe, primero, si esán habilitados los servicios de localización y, después, si el ususario ha dado permiso y qué tipo de permiso. Lo que hacemos en primer lugar es crear una instancia de la clase CLLocationManager:

Y luego, creamos el método checkLocationService, en el que comprobaremos si están habilitados los servicios de localización en el dispositivo y, en caso afirmativo, estableceremos el delegado (delegate) para esta clase tal como se indica en la documentación e indicaremos con que precisión queremos que trabaje la localización:

Esta función la llamaremos desde el método viewDidLoad. A la vez que establecemos el delegado, adoptamos un par de métodos de este delegado que nos permitirán conocer si cambia el permiso dado por el usuario sobre el uso de la localización (locationManager(_:didChangeAuthorization:)) y cuando se actualiza la localización del usuario (locationManager(_:didUpdateLocations:)) (esto lo hacemos en una extension de la clase ViewController para tener el código organizado):

Ahora podemos seguir completando la clase ViewController añadiendo el método que mirará si la aplicación tiene permiso, y qué tipo de permiso, para utilizar la localización. En este método lo que hacemos es llamar al método authorizationStatus de la clase CLLocationManager y comprobar que valor obtenemos:

Tal como se puede observar, existen diferentes posibilidades respecto a la autorización del uso de la localización:

  • authorizedWhenInUse. El usuario autorizó la aplicación para iniciar los servicios de ubicación mientras está en uso.
  • authorizedAlways. El usuario autorizó la aplicación para iniciar los servicios de ubicación en cualquier momento.
  • denied. El usuario rechazó el uso de los servicios de ubicación para la aplicación o están deshabilitados globalmente en Ajustes.
  • notDetermined. El usuario no ha elegido si la aplicación puede usar servicios de ubicación.
  • restricted. La aplicación no está autorizada para usar los servicios de ubicación.

Esta función, checkAuthorizationForLocation, la llamaremos en dos puntos:

  • En el método checkLocationServices() tras establecer el delegado, tras entrar en la aplicación.
  • En el método locationManager(_:didChangeAuthorization:), por si cambia la autorización del usuario durante el uso de la aplicación.

Si ahora ejecutamos la aplicación, veremos como nos muestra una alerta pidiendo permiso para utilizar los serviciós de localización.

.

Una vez damos permiso a la apliación para utilizar los servicios de localización, lo que hemos de hacer es decirle que ya puede activar el seguimiento de la posición del dispositivo. Para ello, dentro del método checkAuthorizationForLocation() y dentro de los casos que permiten el uso añadimos el siguiente código:

Lo que estamos haciendo aquí es decir que la instancia de MKMapView ha de mostrar la posición del usuario ( mapView.showsUserLocation = true), que centre la vista en el usuario (método que crearemos ahora) y que se active la actualización de la localización.

Mostrar nuestra posición en el mapa

El método centerViewOnUser(), lo que hace es determinar a partir de la localización del usuario, establecer un región rectangular centrada es punto. Para ello utilizamos MKCoordinateRegion.

Aquí, primero nos aseguramos de que tenemos la posición del usuario. Luego, establecemos una region de 10 x 10 km centrada en el usuario. Finalmente, establecemos esta región en el mapa. De esta forma, obtenemos la siguiente imágen en el dispositivo.

Por último, para poder actulizar la posición del usuario en el mapa, en el método locationManager(_:didUpdateLocations:) hacemos algo parecido a lo que hemos hecho para centrar la vista en el usuario:

Pero en este caso, la localización la obtenemos del último valor de la lista de localizaciones que devuelve el método.

Selecciona el tipo de mapa

El mapa que vemos por defecto al encender la aplicación es el de tipo standard. MapKit permite mostrar diferentes tipos de mapas cambiando el valor del parámetro mapType de la instancia de MKMapView:

  • standard. Un mapa de calles que muestra la posición de todas las carreteras y algunos nombres de carreteras.
  • satellite. Imágenes vía satélite de la zona.
  • hybrid. Una imagen vía satélite del área con información sobre la carreteras ysu nombre (en una capa por encima del mapa).
  • satelliteFlyover. Una imagen vía satélite del área con datos de la zona (donde estén disponibles).
  • hybridFlyover. Una imagen vía satélite híbrida con datos de la zona (donde estén disponibles).
  • muteStandard. Un mapa de calles donde se resaltan nuestros datos sobre los detalles del mapa.

En este caso, solo utilizaremos tres tipos de mapas: standard, satellite y hybrid.

La selección del tipo de mapa que queremos mostrar en la aplicación lo haremos mediante un botón con menú desplegable, que se pude descargar como paquete Swift (FABbutton). Para ello seguimos estos pasos:

  • Desde el menú de Xcode File > Swift Package > Add Package Dependecy… añadimos el componente FABButton. La URL es: https://github.com/raulferrerdev/FABButton.git
  • Seguidamente, creamos una instancia del botón y y su configuración (los iconos utilizados están ya incluidos en el proyecto):
  • Como se puede observar, hemos establecido el delegado para el tipo FABView, por lo que deberemos hacer que la clase ViewController cumpla este protocolo. Para eso añadimos las siguiente extensión al proyecto:
  • Finalmente, en el método layoutUI añadimos el botón a la vista e indicamos su posicón:

Si ejectutamos la aplicación, podremos comprobar que podemos ir cambiando de tipo de mapa:

Mostrar direcciones

Ahora, lo que vamos a hacer es mostrar en pantalla la dirección de un punto del mapa (el centro) a parir de sus coordenadas, que obtendremos mediante la clase CLLocation. Para ello creamos una función getCenterLocation, a la que pasamos la instancia de MKMapView que tenemos y nos devolverá las coordenadas del punto central:

Para saber exactamente cuál es el centro del mapa, colocaremos un icono en el centro del mapa. Esto lo haremos con un elemento UIImageView, con la imagen de un pin (que obtendremos de la librería SF Symbols de Apple).

Para que el la parte inferior del pin quede exactamente en el centro del mapa, desplazamos este icono hacia arriba la mitad de su altura (-14.5px).

Además, para mostrar la dirección colocaremos una etiqueta en la parte superior de la pantalla, tal como se ha mostrado en el diseño. Para ello, creamos una instancia de UILabel, la configuramos y la situamos en pantalla:

Obtención de la dirección

Para obtener la dirección de un lugar a partir de sus coordenadas, utilizaremos la clase CLGeocoder, que tal como indica la documentación de Apple, permite obtener a partir de la longitud y la latitud de un punto una representación ‘user-friendly’ de esa localización:

The CLGeododer class provides services for converting between a coordinate (specified as a latitude and longitude) and the user-friendly representation of that coordinate. A user-friendly representation of the coordinate typically consists of the street, city, state, and country information corresponding to the given location, but it may also contain a relevant point of interest, landmarks, or other identifying information.

Apple documentation (CLGeocoder)

Para poder conocer las coordenadas del punto central del mapa, implementaremos el delegado de MKMapView y el método que recoge cada vez que desplazamos el mapa.

Dentro de este método hacemos lo siguiente:

  • En primer lugar obtenemos las coordenadas del centro del mapa (gracias a la función getCenterLocation, que hemos visto anteriormente).
  • El siguiente paso es saber si hay una posición previa (previousLocation, que hemos instanciado al principio) y, en ese caso, comprobar que la diferencia en la distancia a la nueva posición es superior, en este caso, a 25 m. Si se cumplen estas condiciones, se asigna el valor de las nuevas coordenadas a la variable previousLocation.
  • Seguidamente, tomamos una instancia de la función CLGeocoder y llamamos al método reverseGeocodeLocation, al que pasamos las coordenadas del centro de la pantalla.
  • Esta función devuelve un bloque con dos parámetros:
  • De estos dos valores, comprobamos que no se ha producido ningún error y que ha devuelto una posición. Un objeto CLPlacemark almacena datos referentes a una latitud y longitud determinadas (como, por ejemplo, el país, el estado, la ciudad y la dirección de la calle, puntos de interés y datos geográficamente relacionados).
  • De esta información nos interesa dos parámetros: thoroughfare (que es la dirección de la calle asociada a la posición indicada) y subThoroughfare (que da información adicional sobre esa dirección).
  • Finalmente, y en el hilo principal, añadimos esta información a la etiqueta.

Establecer rutas

Ahora, lo que nos queda por hacer en este proyecto es implementar un sistema de rutas. Es decir, a partir de un punto de origen y otro de destino, establecer las rutas óptimas para el recorrido.

Esto podemos hacerlo de manera sencilla gracias a MapKit. En este caso utilizaremos la clase MKDirections.Request, que nos permite establecer una petición (request) en la que indicamos el punto de origen, el de destino, el tipo de transporte, si queremos que se nos muestren rutas alternativas…

  • var source: MKMapItem?. Es el punto de partida de las rutas.
  • var destination: MKMapItem?. Es el puntod destino de las rutas.
  • var transportType: MKDirectionsTransportType. Es el tipo de transporte que aplica para calcular las rutas. Puede ser automobile, walking, transit or any.
  • var requestsAlternateRoutes: Bool. Indica si queremos rutas alternativas, en caso que estén disponibles.
  • var departureDate: Date?. Es la fecha de partida del viaje.
  • var arrivalDate: Date?. Es la fecha de llegada del viaje.

Lo que hacemos es crear un método que nos devolverá un objeto del tipo MKDirections.Request:

En este método, primero obtenemos las coordenadas del punto central de la pantalla. Luego creamos objetos tipo MKPlacemark con las coordenadas de origen (el punto en el que nos encontramos) y de destino. Finalmente, creamos una instancia de tipo MKDirections.Request con indicando el origen, el destino, el tipo de transporte (automóvil) y que queremos rutas alternativas.

Para dibujar la ruta, lo he hemos de hacer es iniciar un objeto de top MKDirections con la petición (request) que hemos creado. Tal como indica la documentación de Apple, este objeto calcula direcciones e información de tiempo de viaje en función de la información de ruta que proporcione.

Por tanto, crearemos un método que a partir de la creación de una petición (request), obtenga un objeto de tipo MKDirections y represente las rutas:

En este método, una vez obtenido el objeto MKDirections, utilizamos el método func calculate(completionHandler: MKDirections.DirectionsHandler), que nos devuelve un objeto tipo MKDirections.Response y un posible error:

Entonces comprobamos que la respuesta es válida y tomamos el parámetros routes, que es un lista de objetos tipo MKRoute que representan los recorridos entre los puntos de origen y destino.

Si nos fijamos en la documentación de Apple, el objeto MKRoute presenta un parámetro denominado polyline, que contiene el trazado de la ruta. Para representar este trazado, lo que hemos hecho es pasar este parámetro al método addOverlay(_ overlay: MKOverlay) del objeto MKMapView. Después cambiamos la parte visible del mapa mediante el método setVisibleMapRect(_ mapRect: MKMapRect, animated animate: Bool).

Además, hemos de añadir un método del protocolo MKMapViewDelegate para que se dibujen las rutas (dibujándolas en color verde y de un grosor de 5px):

Ahora, lo que nos falta es añadir un botón que nos permita iniciar el cálculo de la ruta. A partir del diseño de la interfaz que hemos mostrado al principio, añadimos y configuramos un elemento UIButton, al que añadiremos como target el método drawRoutes():

Conclusiones

La librería MapKit de Apple permite desarrollar de forma sencilla una aplicación que muestre mapas, muestre nuestra posición en el mapa, nos muestre direcciones a partir de un punto seleccionado en el mapa y las rutas para llegar a esa dirección.


0 comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Sígueme en Feedly
shares