How to Build an App to Display Maps and Routes With Apple’s MapKit
Show your users exactly how to get somewhere
Have you noticed the number of applications that show us a map in which they place us, indicate interesting places nearby, mark routes …? In this article, I will explain how to build a maps-and-routes application with MapKit.
But what is MapKit? MapKit is an Apple framework that bases its operation on the API and data from Apple Maps, so that you can easily add maps to the applications developed, in this case, for iOS.
This project can be found in full on GitHub:
And much more stuff at Think in Swift:
UI Design
This project will basically consist of an MKMapView
component, which will be the one that shows us the map, to which we will be adding different components according to the functionalities that we want to add to the application. In addition, in this project, all this will be done through code, without using storyboards or .xib
files.
Creation of the Project
To work without storyboards when establishing a project in Xcode 11, we have to do some steps after creating it:
- Delete the file
Main.storyboard
. - In the General tab (TARGETS), we go to the Main interface selector and eliminate “Main,” leaving the field blank.
- Finally, in the Info tab (TARGETS) we go to Application Scene Manifest > Scene Configuration > Application Session Role > Item 0 (Default Configuration) and delete the Storyboard Name field.
Since we will no longer call the Main.storyboard
to start the project, we go to the SceneDelegate.swift
file, and in the function scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
, replace its contents with the following code:
Add a Map to Our Application
To add a map to the screen, simply create an instance of MKMapView
and add it to the screen view. To do this, in the ViewController
class, first we have to import the MapKit
library, then we create an instance of MKMapView
and present it:
If we run the application, we will be able to see an approximate map of where we are located on the screen.
For this map to show us an exact location, we must use the CLLocationManager
class, which, as Apple indicates, allows us to start and end the sending of location events to our application:
- Detect changes in the user’s position
- See changes in compass direction
- Monitor regions of interest
- Detect the position of nearby beacons
Permissions
Keep in mind that to use the location functions, we must first ask the user for permission. To do this, we add in the Info.plist
file a series of parameters (as I have also showed to use notifications):
- Privacy — Location Always and When In Use Usage Description
- Privacy — Location Always Usage Description
- Privacy — Location When In Use Usage Description
To which we give as a value the message that we want to show the user to ask for permission (in this example, “Allow location access in order to use this app”).
Once the Info.plist
file has been modified, we are going to make the application check first if the location services are enabled and later if the user has given permission and what type of permission. What we do first is to instantiate the CLLocationManager
class:
And then, we create the checkLocationService
method, in which we will check if the location services are enabled on the device. If so, we will set the delegate for this class as indicated in the documentation and indicate with what precision we want the location work:
We will call this function from the viewDidLoad
method. At the same time that we set the delegate, we adopt a couple of methods of this delegate that will allow us to know if the permission given by the user on the use of the location changes (locationManager (: didChangeAuthorization:)
) and when the location of the user changes (locationManager (: didUpdateLocations:)
). (We do this in an extension of the ViewController
class to have the code organized.)
Now we can continue to complete the ViewController
class by adding the method that will see if the application has permission, and what type of permission, to use localization. In this method, what we do is call the authorizationStatus
method of the CLLocationManager
class and check what value we get:
As can be seen, there are different possibilities regarding the authorization of the use of the location:
authorizedWhenInUse
. The user authorized the application to start location services while it is in use.authorizedAlways
. The user authorized the application to start location services at any time.denied
. The user has rejected the use of location services for the application, or they are globally disabled in Settings.notDetermined
. The user has not chosen whether the application can use location services.restricted
. The app is not authorized to use location services.
This function, checkAuthorizationForLocation
, we will call at two points:
- In the
checkLocationServices()
method after setting the delegate, after entering the application - In the
locationManager(_: didChangeAuthorization:)
method, in case the user authorization changes while using the application
If we now run the application, we will see how an alert shows, asking us for permission to use the location services.
Once we give the application permission to use the location services, what we have to do is tell it that it can now activate the tracking of the device’s position. To do this within the checkAuthorizationForLocation()
method and within the cases that allow its use, we add the following code:
What we are doing here is saying that the MKMapView
instance has to show the position of the user (mapView.showsUserLocation = true
), to center the view on the user (using a method that we will create now), and to activate the location update.
Show Our Position on the Map
What the centerViewOnUser()
method does is determine the user’s location and establish a centered, rectangular region around this point. For this we use MKCoordinateRegion
.
Here, we first make sure we have the user’s position. Then we establish a region of 10 x 10 km centered on the user. Finally, we establish this region on the map. In this way, we obtain the following image on the device.
Finally, in order to update the user’s position on the map, in the method locationManager(_: didUpdateLocations:)
, we do something similar to what we have done to focus the view on the user:
But in this case, the location is obtained from the last value in the list of locations that the method returns.
The map that we see by default when turning on the application is the standard one. MapKit allows displaying different types of maps by changing the value of the mapType
parameter of the MKMapView
instance:
standard
. A street map showing the position of all roads and some road namessatellite
. Satellite images of the areahybrid
. A satellite image of the area with road information and its name (in a layer above the map)satelliteFlyover
. A satellite image of the area with data from the area (where available)hybridFlyover
. A hybrid satellite image with data from the area (where available)muteStandard
. A street map where our data is highlighted on the map details
In this case, we will only use three types of maps: standard
, satellite
, and hybrid
.
The selection of the type of map that we want to show in the application will be done through a button with a drop-down menu, which can be downloaded as a Swift package (FABButton). To do this, we follow these steps:
- From the Xcode File > Swift Package > Add Package Dependecy … menu, we add the FABButton component. The URL is: https://github.com/raulferrerdev/FABButton.git
- Next, we create an instance of the button and its configuration (the icons used are already included in the project):
- As you can see, we have set the delegate for the
FABView
type, so we must make theViewController
class comply with this protocol. For that we add the following extension to the project:
Finally, in the layoutUI
method, we add the button to the view and indicate its position:
If we run the application, we can see that we can change the type of map:
Show Directions
Now what we are going to do is show on the screen the address of a point on the map (the center) from its coordinates, which we will obtain using the CLLocation
class. To do this, we create a getCenterLocation
function, to which we pass the instance of MKMapView
that we have. It will return the coordinates of the central point:
To know exactly what the center of the map is, we will place an icon in the center of the map. We will do this with a UIImageView
element, with the image of a pin (which we will obtain from Apple’s SF Symbols library).
So that the bottom of the pin is exactly in the center of the map, we move this icon up half of its height (-14.5px).
In addition, to show the address, we will place a label at the top of the screen, as shown in the design. To do this, we create an instance of UILabel
, configure it, and place it on the screen:
Obtain the Address
To obtain the address of a place from its coordinates, we will use the CLGeocoder
class, which, as indicated by the Apple documentation, allows us to obtain a user-friendly representation of that location from the longitude and latitude of a point:
“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
)
In order to know the coordinates of the center point of the map, we will implement the MKMapView
delegate and the method it collects each time we move the map.
Within this method, we do the following:
- First, we get the coordinates of the center of the map (thanks to the
getCenterLocation
function, which we have seen previously). - The next step is to know if there is a previous position (
previousLocation
, which we have instantiated at the beginning) and, in that case, check that the difference in the distance to the new position is greater, in this case, 25 m. If these conditions are met, the value of the new coordinates is assigned to thepreviousLocation
variable. - Next, we take an instance of the
CLGeocoder
function and call thereverseGeocodeLocation
method, to which we pass the coordinates of the center of the screen. - This function returns a block with two parameters:
- Of these two values, we check that no error occurred and that it returned a position. A
CLPlacemark
object stores data regarding a specific latitude and longitude (such as country, state, city and street address, POIs, and geographically related data). - From this information, we are interested in two parameters:
thoroughfare
(which is the street address associated with the indicated position) andsubThoroughfare
(which gives additional information about that address). - Finally, and in the main thread, we add this information to the label.
Set Routes
Now what we have left to do in this project is to implement a route system. That is, starting from a point of origin and another of destination, establish the optimal routes for the path.
We can do this in a simple way thanks to MapKit. In this case, we will use the MKDirections.Request
class, which allows us to establish a request in which we indicate the point of origin, the destination, the type of transport, and if we want to be shown alternative routes.
var source: MKMapItem?
. It is the starting point of the routes.var destination: MKMapItem?
. It is the destination point of the routes.var transportType: MKDirectionsTransportType
. It is the type of transport that applies to calculate the routes. It can be automobile, walking, transit, or any other.var requestsAlternateRoutes: Bool
. Indicates if we want alternative routes if they are available.var departureDate: Date?
. It is the departure date of the trip.var arrivalDate: Date?
. It is the arrival date of the trip.
What we do is create a method that will return an object of type MKDirections.Request
:
In this method, we first obtain the coordinates of the center point of the screen. Then we create MKPlacemark
type objects with the origin (the point where we are) and destination coordinates. Finally, we create an instance of type MKDirections.Request
with indicating the origin, destination, type of transport (automobile), and that we want alternative routes.
To draw the route, what we have to do is start a top MKDirections
object with the request that we have created. As indicated by Apple documentation, this object calculates directions and travel time information based on the route information you provide.
Therefore, we will create a method that, from the creation of a request, gets an object of type MKDirections
and represents the routes:
In this method, once the MKDirections
object has been obtained, we use the method calculate(completionHandler: MKDirections.DirectionsHandler)
, which returns an object of type MKDirections.Response
and a possible error:
Then we check that the answer is valid and take the routes parameter, which is an array of MKRoute
type objects that represent the routes between the origin and destination points.
If we take a look at the Apple documentation, the MKRoute
object presents a parameter called polyline
, which contains the route plot. To represent this plot, what we have done is pass this parameter to the addOverlay(_ overlay: MKOverlay)
method of the MKMapView
object. Then we change the visible part of the map using the method setVisibleMapRect(_ mapRect: MKMapRect, animated animate: Bool)
.
In addition, we have to add a method of the MKMapViewDelegate
protocol so that the routes are drawn (drawing them in green and 5px line width):
Now what we need is to add a button that allows us to start calculating the route. From the interface design that we have shown at the beginning, we add and configure a UIButton
element, to which we will add as a target the drawRoutes()
method:
And the final route image:
Conclusion
The Apple MapKit library allows you to easily develop an application that shows maps, shows our position on the map, shows us directions from a selected point on the map, and shows the routes to get to that address.