Core Location — Integration with MapKit
Exordium
In this post, I’ll be taking you through the process of integrating Core Location and MapKit. We will be piggy-backing off of the series started here. You can find the code to this tutorial on GitHub.
Core Location is a part of Cocoa Touch — and is the framework used for obtaining user location details. The location details can be used in many different ways — and how you use this information will depend on your app’s functionality. Whether you’re building a food delivery app, concert booking system, or a tool to find the nearest bathroom — Core Location is going to be a key component to your product. In this case, the user location will be displayed in the middle of a view controller that hosts a map.
Let’s begin.
Adding the Map
Start with the mapkit
branch of the repo mentioned above. You will see a new button in the ViewController
and a new view controller named MapViewController.
(I have added both of these components for you, as you can see when compared to the geocoder
branch).
The first thing to do in the MapViewController
is to add the map view, which will be an MKMapView
object. Add this property to the MapViewController
class:
private var mapView: MKMapView!
The next thing to do is create the layout of the MapViewController.
The ViewController
class is connected to the Main.storyboard
file, but I am going to build the MapViewController
programmatically. When building view controller interfaces with code, I like to keep all of the UI work in a setupView
method that ends up calling another method that applies all of the Auto Layout constraints.
Here is the simple setup of the MapViewController
:
import UIKit
import CoreLocation
import MapKit
class MapViewController: UIViewController {
// MARK: - Properties
private var mapView: MKMapView!
// MARK: - Methods
// 1. Complete any user interface work.
func setupView() {
mapView = MKMapView()
mapView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mapView)
applyAutoConstraints()
}
// 2. Complete any Auto Layout work.
func applyAutoConstraints() {
NSLayoutConstraint.activate([
mapView.topAnchor.constraint(equalTo: view.topAnchor),
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
// MARK: - Overrides
override func viewDidLoad() {
super.viewDidLoad()
// 3. Call the method that sets up the user interface.
setupView()
}
}
Here, I have imported Core Location
and MapKit
. The first thing to do after importing the frameworks is to set up the user interface.
1.) The setupView
method will hold any UI work that involves setting up components and their attributes. In this case, we instantiate the MKMapView
object and set it's translatesAutoresizingMaskIntoConstraints
to false
. Setting this property to false
lets the UI component be affected by Auto Layout. As stated in Apple's docs 'If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false, and then provide a non ambiguous, nonconflicting set of constraints for the view.' Only objects that inherit from UIView
will have access to the translatesAutoresizingMaskIntoConstraints
property. The final thing to do with the mapView
object is to add it to the view as a subview.
2.) Any Auto Layout work that needs to be done for the UI components will be done in the applyAutoConstraints
method. Here, mapView
will fill the entire screen since the anchors are all equal to the view's corresponding anchors.
3.) To get any of this code to do anything, call the setupView
method in the viewDidLoad
method.
The last bit of UI set up to do is adding a title and cancel button to the navigation bar of the MapViewController
. Add the following to the beginning of the viewDidLoad
method, before the call to setupView
.
title = "View Your Location"
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismissToMainView))
The dismissToMainView
selector is an Objective-C selector hosted in the MapViewController
. It simply dismisses the current view controller.
@objc func dismissToMainView() {
dismiss(animated: true, completion: nil)
}
Passing the User’s Location
To get the user’s location to display on the map, it must be passed from the ViewController
to the MapViewController
. Right now, the MapViewController
does not contain a property where we can store the location - so add one with a default internal
access level.
var userLocation: CLLocation!
Then, in the ViewController
, class locate the IBAction
named viewLocationOnMapButtonPressed
and:
1.) Create an instance of MapViewController
.
2.) Pass the current location to the mapViewController
's userLocation
property.
3.) Embed the mapViewController
in a UINavigationController
and present the navigationController
.
@IBAction func viewLocationOnMapButtonPressed(_ sender: Any) {
// 1
let mapViewController = MapViewController()
// 2
mapViewController.userLocation = currentLocation
// 3
let navigationController = UINavigationController(rootViewController: mapViewController)
present(navigationController, animated: true, completion: nil)
}
Display the User’s Location on the Map
Before the location is displayed on the map:
1.) The showsUserLocation
property of the map must be set to true
.
2.) The delegate will be set to self
(make sure the MapViewController
conforms to the MKMapViewDelegate
).
3.) The region will be based on the user's location that is passed in from the ViewController
. The region of a map is the part of the map that is currently displayed on the screen. It is of the type MKCoordinateRegion
, which will be created with the initializer that contains the center
and span
parameters. The center
will be the user's coordinate, and the span
is of the type MKCoordinateSpan
. This property defines the height and width of the region. Lower delta values will increase the zoom level on the map.
The setUpView
method should now look like this:
func setupView() {
mapView = MKMapView()
mapView.translatesAutoresizingMaskIntoConstraints = false
// 1
mapView.showsUserLocation = true
// 2
mapView.delegate = self
// 3
mapView.region = MKCoordinateRegion(center: userLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
view.addSubview(mapView)
applyAutoConstraints()
}
The last thing to do is implement the regionDidChangeAnimated
method in the MKMapViewDelegate
and set the map's region after the region has been updated. This is not necessary, but it helps re-center the map when the user scrolls away from their location with their finger.
extension MapViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
mapView.setRegion(MKCoordinateRegion(center: userLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)), animated: true)
}
}
If the app is run, the location is obtained, and the ‘View Location on Map’ button is tapped — the MapViewController
will be presented, and you will see your location in the center of the map. There is much more you can do with MapKit, and this blog post really just touches the basics of what MapKit is and how it can be used.
Core Location integrates nicely with MapKit, and is a key component to any location-based iOS app. Core Location also gives developers the ability to reverse-geocode GPS coordinates and create human-readable addresses. Learn more about reverse-geocoding with Core Location on our blog.
You can read more articles related to iOS Development, Freelancing, Software Engineering and more at rustynailsoftware.com. Feel free to connect on Twitter. Or, get in touch if you’re looking for a dependable iOS Developer for your next project.