Implement location tracking using MapKit in Swift

Swarnendu De September 2, 2015

Location-based services have always looked like a very cool feature to me. Showing the user their covered or targeted location and route made the process of moving around so much better. In this blog, we will try to implement MapKit, a very simple and easy framework provided by Apple, to work upon with map and location requirements. Our aim will be to implement location tracking services and show the covered route on Map View. This will be done using Swift.

mapkit_practice

 

Create Xcode project

To start off, open Xcode, and select the option Create a new Xcode project. From there, select iOS->Application->Single View Application, and click on Next. After entering your project name choose language as Swift.

To work with location services and MapKit, you will have to add MapKit and CoreLocation frameworks from Project Settings->Build Phases->Link binary with Libraries.

Now go to file ViewController.swift and add these two lines to import MapKit and CoreLocation framework through the view controller.

import MapKit
import CoreLocation

Initial Setup

Now go to storyboard file and add map view to your view controller, present in Object library. Create a property of your mapview on view controller file and name it mapView.

Assign your view Controller to CLLocationManagerDelegate and MKMapViewDelegate.

For authorizing app to location services, add NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription keys to your project’s Info.plist. Add the following string against your authorization keys, “Your Project wants to use your present location”.

Time to start Coding…!! 🙂

First create a property of CLLocationManager by adding the following line:

var locationManager: CLLocationManager!

After that lets do some setup for locationManager to get it working. Add the following lines of code in func viewDidLoad()

locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate = self;

To complete the authorization process for enabling location services, add the following lines of code

// user activated automatic authorization info mode
var status = CLLocationManager.authorizationStatus()
if status == .NotDetermined || status == .Denied || status == .AuthorizedWhenInUse {
       // present an alert indicating location authorization required
       // and offer to take the user to Settings for the app via
       // UIApplication -openUrl: and UIApplicationOpenSettingsURLString
       locationManager.requestAlwaysAuthorization()
       locationManager.requestWhenInUseAuthorization()
   }
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()

Now, run your project. As soon as your application launches, you will see an authorization alert on the application screen. Allowing that will enable the application to get location of user.

But where to get that location from???

In order to get the user’s current location, implement the CLLocationManager delegate:

func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!)

Then, add the following lines to print the obtained user location

println("present location : (newLocation.coordinate.latitude), (newLocation.coordinate.longitude)")

Now run your project and you will see logs for changing location of user.

NOTE : If you are not getting any location, click on the Simulator and select Debug->Location->City Bicycle Ride. This will produce a simulated route for a location. The route calculation is done by Apple.

Map Setup

So, how about displaying live user location on the Map?

To get the visible user location from the map, add the following lines in func viewDidLoad()

//mapview setup to show user location
mapView.delegate = self
mapView.showsUserLocation = true
mapView.mapType = MKMapType(rawValue: 0)!
mapView.userTrackingMode = MKUserTrackingMode(rawValue: 2)!

Now run your project and you will see the user location change in the map.

Showing Route On Map

Now we will show the route covered by a user.
To do that, add the following lines in CLLocationManager delegate

func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!)

//drawing path or route covered
if let oldLocationNew = oldLocation as CLLocation?{
     let oldCoordinates = oldLocationNew.coordinate
     let newCoordinates = newLocation.coordinate
     var area = [oldCoordinates, newCoordinates]
     var polyline = MKPolyline(coordinates: &area, count: area.count)
     mapView.addOverlay(polyline)
 }

But that isn’t enough. The above code will just add overlay as route on map. But to render overlay on the map, another MKMapView delegate func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! is required.

Add the following lines inside the delegate mentioned above

if (overlay is MKPolyline) {
    var pr = MKPolylineRenderer(overlay: overlay)
    pr.strokeColor = UIColor.redColor()
    pr.lineWidth = 5
    return pr
}
return nil

Run the project and you will be able to see a trail of red colour, behind the user location i.e. the route.

Finish off by adding annotations to the route

To add annotations, add the following function that takes CLLocation as argument and draws annotation at required location. It is done after getting the location name from the coordinates through CLGeocoder and applying it to title of annotation.

//function to add annotation to map view
func addAnnotationsOnMap(locationToPoint : CLLocation){

    var annotation = MKPointAnnotation()
    annotation.coordinate = locationToPoint.coordinate
    var geoCoder = CLGeocoder ()
    geoCoder.reverseGeocodeLocation(locationToPoint, completionHandler: { (placemarks, error) -> Void in
        if let placemarks = placemarks as? [CLPlacemark] where placemarks.count > 0 {
            var placemark = placemarks[0]
            var addressDictionary = placemark.addressDictionary;
            annotation.title = addressDictionary["Name"] as? String
            self.mapView.addAnnotation(annotation)
        }
    })
}

Now when you’re calling this function directly from CLLocationManager delegate, it will add annotations on the map. But by doing so it makes the map clusterized.

To solve this problem we will add the annotations every time we cover a distance of 200 metres.

For that remove the call to the function func addAnnotationsOnMap(locationToPoint : CLLocation) and add the following lines in CLLocationManager delegate.

//calculation for location selection and pointing annotation
if let previousLocationNew = previousLocation as CLLocation?{
    //case if previous location exists
    if previousLocation.distanceFromLocation(newLocation) > 200 {
        addAnnotationsOnMap(newLocation)
        previousLocation = newLocation
    }
}else{
    //in case previous location doesn't exist
    addAnnotationsOnMap(newLocation)
    previousLocation = newLocation
}

As soon as you add these lines you will get an error for unknown variable previousLocation. To solve the issue add the following property

var previousLocation : CLLocation!

Now run your project and you can see the route as well as annotations (not clusterized), along with user location, being tracked on map.

Thats all folks!

To find a sample project of the above tutorial, check GitHub.

I hope the blog was simple and help you learn MapKit better. Feel free to comment in case of queries and any other advice.